// 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"` }