| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package http |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "errors" |
| "fmt" |
| "io" |
| "net/http" |
| |
| "github.com/hashicorp/go-secure-stdlib/tlsutil" |
| "github.com/hashicorp/vault/physical/raft" |
| "github.com/hashicorp/vault/vault" |
| ) |
| |
| func handleSysRaftBootstrap(core *vault.Core) http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| switch r.Method { |
| case "POST", "PUT": |
| if core.Sealed() { |
| respondError(w, http.StatusBadRequest, errors.New("node must be unsealed to bootstrap")) |
| } |
| |
| if err := core.RaftBootstrap(context.Background(), false); err != nil { |
| respondError(w, http.StatusInternalServerError, err) |
| return |
| } |
| |
| default: |
| respondError(w, http.StatusBadRequest, nil) |
| } |
| }) |
| } |
| |
| func handleSysRaftJoin(core *vault.Core) http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| switch r.Method { |
| case "POST", "PUT": |
| handleSysRaftJoinPost(core, w, r) |
| default: |
| respondError(w, http.StatusMethodNotAllowed, nil) |
| } |
| }) |
| } |
| |
| func handleSysRaftJoinPost(core *vault.Core, w http.ResponseWriter, r *http.Request) { |
| // Parse the request |
| var req JoinRequest |
| if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil && err != io.EOF { |
| respondError(w, http.StatusBadRequest, err) |
| return |
| } |
| |
| if req.NonVoter && !nonVotersAllowed { |
| respondError(w, http.StatusBadRequest, errors.New("non-voting nodes not allowed")) |
| return |
| } |
| |
| var tlsConfig *tls.Config |
| var err error |
| if len(req.LeaderCACert) != 0 || len(req.LeaderClientCert) != 0 || len(req.LeaderClientKey) != 0 { |
| tlsConfig, err = tlsutil.ClientTLSConfig([]byte(req.LeaderCACert), []byte(req.LeaderClientCert), []byte(req.LeaderClientKey)) |
| if err != nil { |
| respondError(w, http.StatusBadRequest, err) |
| return |
| } |
| tlsConfig.ServerName = req.LeaderTLSServerName |
| } |
| |
| if req.AutoJoinScheme != "" && (req.AutoJoinScheme != "http" && req.AutoJoinScheme != "https") { |
| respondError(w, http.StatusBadRequest, fmt.Errorf("invalid scheme %q; must either be http or https", req.AutoJoinScheme)) |
| return |
| } |
| |
| leaderInfos := []*raft.LeaderJoinInfo{ |
| { |
| AutoJoin: req.AutoJoin, |
| AutoJoinScheme: req.AutoJoinScheme, |
| AutoJoinPort: req.AutoJoinPort, |
| LeaderAPIAddr: req.LeaderAPIAddr, |
| TLSConfig: tlsConfig, |
| Retry: req.Retry, |
| }, |
| } |
| |
| joined, err := core.JoinRaftCluster(context.Background(), leaderInfos, req.NonVoter) |
| if err != nil { |
| respondError(w, http.StatusInternalServerError, err) |
| return |
| } |
| |
| resp := JoinResponse{ |
| Joined: joined, |
| } |
| respondOk(w, resp) |
| } |
| |
| type JoinResponse struct { |
| Joined bool `json:"joined"` |
| } |
| |
| type JoinRequest struct { |
| AutoJoin string `json:"auto_join"` |
| AutoJoinScheme string `json:"auto_join_scheme"` |
| AutoJoinPort uint `json:"auto_join_port"` |
| LeaderAPIAddr string `json:"leader_api_addr"` |
| LeaderCACert string `json:"leader_ca_cert"` |
| LeaderClientCert string `json:"leader_client_cert"` |
| LeaderClientKey string `json:"leader_client_key"` |
| LeaderTLSServerName string `json:"leader_tls_servername"` |
| Retry bool `json:"retry"` |
| NonVoter bool `json:"non_voter"` |
| } |