mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-27 01:50:31 -04:00
* SECVULN-39610 Implement raft retry limit * SECVULN-39610 Implement pr review feedback: - refactor context usage to not have a helper - add test coverage for raft join limiter * SECVULN-39610 Update max raft joins to 20, add godoc for test * SECVULN-39610 Add changelog entry * SECVULN-39610 Update raft retry test to exercise the cap on a single follower core * Update changelog/_14954.txt --------- Co-authored-by: Jason Pilz <jasonpilz@gmail.com> Co-authored-by: Violet Hynes <violet.hynes@hashicorp.com>
120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
// Copyright IBM Corp. 2016, 2025
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package http
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/tlsutil"
|
|
"github.com/hashicorp/vault/internalshared/configutil"
|
|
"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"))
|
|
return
|
|
}
|
|
|
|
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: configutil.NormalizeAddr(req.LeaderAPIAddr),
|
|
TLSConfig: tlsConfig,
|
|
Retry: req.Retry,
|
|
},
|
|
}
|
|
|
|
ctx, cancel := core.GetContext()
|
|
defer cancel()
|
|
joined, err := core.JoinRaftCluster(ctx, 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"`
|
|
}
|