mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-09 00:33:28 -04:00
Backport Add an authenticated mode to rekey endpoints into ce/main (#12925)
This commit is contained in:
parent
2ef4c50221
commit
99fec82771
24 changed files with 1520 additions and 512 deletions
3
changelog/_12712.txt
Normal file
3
changelog/_12712.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:change
|
||||
core: sys/rekey endpoints are now authenticated by default, with the old unauthenticated behaviour enabled by setting the new HCL config key enable_unauthenticated_access to include the value "rekey".
|
||||
```
|
||||
|
|
@ -2350,6 +2350,9 @@ func (c *ServerCommand) Reload(lock *sync.RWMutex, reloadFuncs *map[string][]rel
|
|||
// Set Introspection Endpoint to enabled with new value in the config after reload
|
||||
core.ReloadIntrospectionEndpointEnabled()
|
||||
|
||||
// Reload unauthenticated endpoints override configuration
|
||||
core.ReloadEnableUnauthenticatedAccess()
|
||||
|
||||
// Send a message that we reloaded. This prevents "guessing" sleep times
|
||||
// in tests.
|
||||
select {
|
||||
|
|
@ -3002,6 +3005,7 @@ func createCoreConfig(c *ServerCommand, config *server.Config, backend physical.
|
|||
AdministrativeNamespacePath: config.AdministrativeNamespacePath,
|
||||
ObservationSystemConfig: config.Observations,
|
||||
ReportingScanDirectory: config.ReportingScanDirectory,
|
||||
EnableUnauthenticatedAccess: config.EnableUnauthenticatedAccess,
|
||||
}
|
||||
|
||||
if c.flagDev {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ type Config struct {
|
|||
|
||||
Experiments []string `hcl:"experiments"`
|
||||
|
||||
EnableUnauthenticatedAccess []string `hcl:"enable_unauthenticated_access"`
|
||||
|
||||
CacheSize int `hcl:"cache_size"`
|
||||
DisableCache bool `hcl:"-"`
|
||||
DisableCacheRaw interface{} `hcl:"disable_cache"`
|
||||
|
|
@ -520,6 +522,11 @@ func (c *Config) Merge(c2 *Config) *Config {
|
|||
|
||||
result.Experiments = mergeExperiments(c.Experiments, c2.Experiments)
|
||||
|
||||
result.EnableUnauthenticatedAccess = c.EnableUnauthenticatedAccess
|
||||
if len(c2.EnableUnauthenticatedAccess) > 0 {
|
||||
result.EnableUnauthenticatedAccess = c2.EnableUnauthenticatedAccess
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -1415,6 +1422,8 @@ func (c *Config) Sanitized() map[string]interface{} {
|
|||
"log_requests_level": c.LogRequestsLevel,
|
||||
"experiments": c.Experiments,
|
||||
|
||||
"enable_unauthenticated_access": c.EnableUnauthenticatedAccess,
|
||||
|
||||
"detect_deadlocks": c.DetectDeadlocks,
|
||||
|
||||
"imprecise_lease_role_tracking": c.ImpreciseLeaseRoleTracking,
|
||||
|
|
|
|||
|
|
@ -943,6 +943,7 @@ func testConfig_Sanitized(t *testing.T) {
|
|||
"post_unseal_trace_directory": "/tmp",
|
||||
"remove_irrevocable_lease_after": (30 * 24 * time.Hour) / time.Second,
|
||||
"allow_audit_log_prefixing": false,
|
||||
"enable_unauthenticated_access": []string(nil),
|
||||
}
|
||||
|
||||
addExpectedEntSanitizedConfig(expected, []string{"http"})
|
||||
|
|
|
|||
|
|
@ -236,6 +236,19 @@ var _ vault.HandlerHandler = HandlerFunc(func(props *vault.HandlerProperties) ht
|
|||
// handler returns an http.Handler for the API. This can be used on
|
||||
// its own to mount the Vault API within another web server.
|
||||
func handler(props *vault.HandlerProperties) http.Handler {
|
||||
handlerUnauth := handlerWithUnauthRekey(props, true)
|
||||
handlerAuth := handlerWithUnauthRekey(props, false)
|
||||
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {
|
||||
if props.Core.GetEnableUnauthRekey() {
|
||||
handlerUnauth.ServeHTTP(writer, req)
|
||||
} else {
|
||||
handlerAuth.ServeHTTP(writer, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func handlerWithUnauthRekey(props *vault.HandlerProperties, unauthRekey bool) http.Handler {
|
||||
core := props.Core
|
||||
|
||||
// Create the muxer to handle the actual endpoints
|
||||
|
|
@ -275,12 +288,18 @@ func handler(props *vault.HandlerProperties) http.Handler {
|
|||
handleAuditNonLogical(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy))))
|
||||
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core,
|
||||
handleAuditNonLogical(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy))))
|
||||
mux.Handle("/v1/sys/rekey/init", handleRequestForwarding(core, handleSysRekeyInit(core, false)))
|
||||
mux.Handle("/v1/sys/rekey/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, false)))
|
||||
mux.Handle("/v1/sys/rekey/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, false)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, true)))
|
||||
|
||||
// Register rekey endpoints as unauthenticated handlers only if unauthRekey is true.
|
||||
// When false (the default), these endpoints will be handled by the sys backend as authenticated endpoints.
|
||||
if unauthRekey {
|
||||
mux.Handle("/v1/sys/rekey/init", handleRequestForwarding(core, handleSysRekeyInit(core, false)))
|
||||
mux.Handle("/v1/sys/rekey/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, false)))
|
||||
mux.Handle("/v1/sys/rekey/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, false)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/init", handleRequestForwarding(core, handleSysRekeyInit(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, true)))
|
||||
mux.Handle("/v1/sys/rekey-recovery-key/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, true)))
|
||||
}
|
||||
|
||||
mux.Handle("/v1/sys/storage/raft/bootstrap", handleSysRaftBootstrap(core))
|
||||
mux.Handle("/v1/sys/storage/raft/join", handleSysRaftJoin(core))
|
||||
mux.Handle("/v1/sys/internal/ui/feature-flags", handleSysInternalFeatureFlags(core))
|
||||
|
|
@ -1482,6 +1501,23 @@ func respondErrorCommon(w http.ResponseWriter, req *logical.Request, resp *logic
|
|||
respondErrorAndData(w, statusCode, data, newErr)
|
||||
return true
|
||||
}
|
||||
if body := resp.Data[logical.HTTPRawBodyError]; body != nil {
|
||||
if code := resp.Data[logical.HTTPStatusCode]; code != nil {
|
||||
if i, ok := code.(int); ok {
|
||||
// Defensively ignore non-int status codes
|
||||
statusCode = i
|
||||
}
|
||||
}
|
||||
switch v := body.(type) {
|
||||
case string:
|
||||
logical.RespondWithBody(w, statusCode, v)
|
||||
case []byte:
|
||||
logical.RespondWithBody(w, statusCode, string(v))
|
||||
default:
|
||||
respondError(w, http.StatusInternalServerError, fmt.Errorf("unable to decode body: %w", newErr))
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
respondError(w, statusCode, newErr)
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ func TestSysConfigState_Sanitized(t *testing.T) {
|
|||
"post_unseal_trace_directory": "",
|
||||
"remove_irrevocable_lease_after": json.Number("0"),
|
||||
"allow_audit_log_prefixing": false,
|
||||
"enable_unauthenticated_access": nil,
|
||||
}
|
||||
|
||||
if tc.expectedHAStorageOutput != nil {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,10 @@ package http
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
|
@ -51,94 +47,25 @@ func handleSysRekeyInit(core *vault.Core, recovery bool) http.Handler {
|
|||
}
|
||||
|
||||
func handleSysRekeyInitGet(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
|
||||
barrierConfig, barrierConfErr := core.SealAccess().BarrierConfig(ctx)
|
||||
if barrierConfErr != nil {
|
||||
respondError(w, http.StatusInternalServerError, barrierConfErr)
|
||||
return
|
||||
}
|
||||
if barrierConfig == nil {
|
||||
respondError(w, http.StatusBadRequest, fmt.Errorf("server is not yet initialized"))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the rekey configuration
|
||||
rekeyConf, err := core.RekeyConfig(recovery)
|
||||
status, code, err := vault.HandleSysRekeyInitGet(ctx, core, recovery, true)
|
||||
if err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
|
||||
sealThreshold, err := core.RekeyThreshold(ctx, recovery)
|
||||
if err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Format the status
|
||||
status := &RekeyStatusResponse{
|
||||
Started: false,
|
||||
T: 0,
|
||||
N: 0,
|
||||
Required: sealThreshold,
|
||||
}
|
||||
if rekeyConf != nil {
|
||||
// Get the progress
|
||||
started, progress, err := core.RekeyProgress(recovery, false)
|
||||
if err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
return
|
||||
}
|
||||
|
||||
status.Nonce = rekeyConf.Nonce
|
||||
status.Started = started
|
||||
status.T = rekeyConf.SecretThreshold
|
||||
status.N = rekeyConf.SecretShares
|
||||
status.Progress = progress
|
||||
status.VerificationRequired = rekeyConf.VerificationRequired
|
||||
status.VerificationNonce = rekeyConf.VerificationNonce
|
||||
if rekeyConf.PGPKeys != nil && len(rekeyConf.PGPKeys) != 0 {
|
||||
pgpFingerprints, err := pgpkeys.GetFingerprints(rekeyConf.PGPKeys, nil)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
status.PGPFingerprints = pgpFingerprints
|
||||
status.Backup = rekeyConf.Backup
|
||||
}
|
||||
}
|
||||
respondOk(w, status)
|
||||
}
|
||||
|
||||
func handleSysRekeyInitPut(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request
|
||||
var req RekeyRequest
|
||||
var req *vault.RekeyRequest
|
||||
if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Backup && len(req.PGPKeys) == 0 {
|
||||
respondError(w, http.StatusBadRequest, fmt.Errorf("cannot request a backup of the new keys without providing PGP keys for encryption"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.PGPKeys) > 0 && len(req.PGPKeys) != req.SecretShares {
|
||||
respondError(w, http.StatusBadRequest, fmt.Errorf("incorrect number of PGP keys for rekey"))
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the rekey
|
||||
err := core.RekeyInit(&vault.SealConfig{
|
||||
SecretShares: req.SecretShares,
|
||||
SecretThreshold: req.SecretThreshold,
|
||||
StoredShares: req.StoredShares,
|
||||
PGPKeys: req.PGPKeys,
|
||||
Backup: req.Backup,
|
||||
VerificationRequired: req.RequireVerification,
|
||||
Created: time.Now().UTC(),
|
||||
}, recovery)
|
||||
code, err := vault.HandleSysRekeyInitPut(core, recovery, req, true)
|
||||
if err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -146,13 +73,13 @@ func handleSysRekeyInitPut(ctx context.Context, core *vault.Core, recovery bool,
|
|||
}
|
||||
|
||||
func handleSysRekeyInitDelete(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
|
||||
var req RekeyDeleteRequest
|
||||
var req vault.RekeyDeleteRequest
|
||||
if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := core.RekeyCancel(recovery, req.Nonce, 10*time.Minute); err != nil {
|
||||
if err := core.RekeyCancel(recovery, req.Nonce, 10*time.Minute, true); err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
return
|
||||
}
|
||||
|
|
@ -168,67 +95,26 @@ func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler {
|
|||
}
|
||||
|
||||
// Parse the request
|
||||
var req RekeyUpdateRequest
|
||||
var req vault.RekeyUpdateRequest
|
||||
if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if req.Key == "" {
|
||||
respondError(
|
||||
w, http.StatusBadRequest,
|
||||
errors.New("'key' must be specified in request body as JSON"))
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the key, which is base64 or hex encoded
|
||||
min, max := core.BarrierKeyLength()
|
||||
key, err := hex.DecodeString(req.Key)
|
||||
// We check min and max here to ensure that a string that is base64
|
||||
// encoded but also valid hex will not be valid and we instead base64
|
||||
// decode it
|
||||
if err != nil || len(key) < min || len(key) > max {
|
||||
key, err = base64.StdEncoding.DecodeString(req.Key)
|
||||
if err != nil {
|
||||
respondError(
|
||||
w, http.StatusBadRequest,
|
||||
errors.New("'key' must be a valid hex or base64 string"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := core.GetContext()
|
||||
defer cancel()
|
||||
|
||||
// Use the key to make progress on rekey
|
||||
result, rekeyErr := core.RekeyUpdate(ctx, key, req.Nonce, recovery)
|
||||
if rekeyErr != nil {
|
||||
respondError(w, rekeyErr.Code(), rekeyErr)
|
||||
result, code, err := vault.HandleSysRekeyUpdatePut(ctx, core, recovery, &req, true)
|
||||
if err != nil {
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
if result != nil {
|
||||
respondOk(w, result)
|
||||
return
|
||||
}
|
||||
|
||||
// Format the response
|
||||
resp := &RekeyUpdateResponse{}
|
||||
if result != nil {
|
||||
resp.Complete = true
|
||||
resp.Nonce = req.Nonce
|
||||
resp.Backup = result.Backup
|
||||
resp.PGPFingerprints = result.PGPFingerprints
|
||||
resp.VerificationRequired = result.VerificationRequired
|
||||
resp.VerificationNonce = result.VerificationNonce
|
||||
|
||||
// Encode the keys
|
||||
keys := make([]string, 0, len(result.SecretShares))
|
||||
keysB64 := make([]string, 0, len(result.SecretShares))
|
||||
for _, k := range result.SecretShares {
|
||||
keys = append(keys, hex.EncodeToString(k))
|
||||
keysB64 = append(keysB64, base64.StdEncoding.EncodeToString(k))
|
||||
}
|
||||
resp.Keys = keys
|
||||
resp.KeysB64 = keysB64
|
||||
respondOk(w, resp)
|
||||
} else {
|
||||
handleSysRekeyInitGet(ctx, core, recovery, w, r)
|
||||
}
|
||||
handleSysRekeyInitGet(r.Context(), core, recovery, w, r)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -266,47 +152,17 @@ func handleSysRekeyVerify(core *vault.Core, recovery bool) http.Handler {
|
|||
}
|
||||
|
||||
func handleSysRekeyVerifyGet(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
|
||||
barrierConfig, barrierConfErr := core.SealAccess().BarrierConfig(ctx)
|
||||
if barrierConfErr != nil {
|
||||
respondError(w, http.StatusInternalServerError, barrierConfErr)
|
||||
return
|
||||
}
|
||||
if barrierConfig == nil {
|
||||
respondError(w, http.StatusBadRequest, fmt.Errorf("server is not yet initialized"))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the rekey configuration
|
||||
rekeyConf, err := core.RekeyConfig(recovery)
|
||||
status, code, err := vault.HandleSysRekeyVerifyGet(ctx, core, recovery, true)
|
||||
if err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
return
|
||||
}
|
||||
if rekeyConf == nil {
|
||||
respondError(w, http.StatusBadRequest, errors.New("no rekey configuration found"))
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the progress
|
||||
started, progress, err := core.RekeyProgress(recovery, true)
|
||||
if err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Format the status
|
||||
status := &RekeyVerificationStatusResponse{
|
||||
Started: started,
|
||||
Nonce: rekeyConf.VerificationNonce,
|
||||
T: rekeyConf.SecretThreshold,
|
||||
N: rekeyConf.SecretShares,
|
||||
Progress: progress,
|
||||
}
|
||||
respondOk(w, status)
|
||||
}
|
||||
|
||||
func handleSysRekeyVerifyDelete(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
|
||||
if err := core.RekeyVerifyRestart(recovery); err != nil {
|
||||
if err := core.RekeyVerifyRestart(recovery, true); err != nil {
|
||||
respondError(w, err.Code(), err)
|
||||
return
|
||||
}
|
||||
|
|
@ -316,112 +172,25 @@ func handleSysRekeyVerifyDelete(ctx context.Context, core *vault.Core, recovery
|
|||
|
||||
func handleSysRekeyVerifyPut(ctx context.Context, core *vault.Core, recovery bool, w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request
|
||||
var req RekeyVerificationUpdateRequest
|
||||
var req vault.RekeyVerificationUpdateRequest
|
||||
if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if req.Key == "" {
|
||||
respondError(
|
||||
w, http.StatusBadRequest,
|
||||
errors.New("'key' must be specified in request body as JSON"))
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the key, which is base64 or hex encoded
|
||||
min, max := core.BarrierKeyLength()
|
||||
key, err := hex.DecodeString(req.Key)
|
||||
// We check min and max here to ensure that a string that is base64
|
||||
// encoded but also valid hex will not be valid and we instead base64
|
||||
// decode it
|
||||
if err != nil || len(key) < min || len(key) > max {
|
||||
key, err = base64.StdEncoding.DecodeString(req.Key)
|
||||
if err != nil {
|
||||
respondError(
|
||||
w, http.StatusBadRequest,
|
||||
errors.New("'key' must be a valid hex or base64 string"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := core.GetContext()
|
||||
defer cancel()
|
||||
|
||||
// Use the key to make progress on rekey
|
||||
result, rekeyErr := core.RekeyVerify(ctx, key, req.Nonce, recovery)
|
||||
if rekeyErr != nil {
|
||||
respondError(w, rekeyErr.Code(), rekeyErr)
|
||||
resp, code, err := vault.HandleSysRekeyVerifyPut(ctx, core, recovery, true, &req)
|
||||
if err != nil {
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Format the response
|
||||
resp := &RekeyVerificationUpdateResponse{}
|
||||
if result != nil {
|
||||
resp.Complete = true
|
||||
resp.Nonce = result.Nonce
|
||||
if resp != nil {
|
||||
respondOk(w, resp)
|
||||
} else {
|
||||
handleSysRekeyVerifyGet(ctx, core, recovery, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
type RekeyRequest struct {
|
||||
SecretShares int `json:"secret_shares"`
|
||||
SecretThreshold int `json:"secret_threshold"`
|
||||
StoredShares int `json:"stored_shares"`
|
||||
PGPKeys []string `json:"pgp_keys"`
|
||||
Backup bool `json:"backup"`
|
||||
RequireVerification bool `json:"require_verification"`
|
||||
}
|
||||
|
||||
type RekeyStatusResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Started bool `json:"started"`
|
||||
T int `json:"t"`
|
||||
N int `json:"n"`
|
||||
Progress int `json:"progress"`
|
||||
Required int `json:"required"`
|
||||
PGPFingerprints []string `json:"pgp_fingerprints"`
|
||||
Backup bool `json:"backup"`
|
||||
VerificationRequired bool `json:"verification_required"`
|
||||
VerificationNonce string `json:"verification_nonce,omitempty"`
|
||||
}
|
||||
|
||||
type RekeyUpdateRequest struct {
|
||||
Nonce string
|
||||
Key string
|
||||
}
|
||||
|
||||
type RekeyUpdateResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Complete bool `json:"complete"`
|
||||
Keys []string `json:"keys"`
|
||||
KeysB64 []string `json:"keys_base64"`
|
||||
PGPFingerprints []string `json:"pgp_fingerprints"`
|
||||
Backup bool `json:"backup"`
|
||||
VerificationRequired bool `json:"verification_required"`
|
||||
VerificationNonce string `json:"verification_nonce,omitempty"`
|
||||
}
|
||||
|
||||
type RekeyVerificationUpdateRequest struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type RekeyVerificationStatusResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Started bool `json:"started"`
|
||||
T int `json:"t"`
|
||||
N int `json:"n"`
|
||||
Progress int `json:"progress"`
|
||||
}
|
||||
|
||||
type RekeyVerificationUpdateResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Complete bool `json:"complete"`
|
||||
}
|
||||
|
||||
type RekeyDeleteRequest struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,20 +7,21 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hashicorp/vault/sdk/helper/testhelpers/schema"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Test to check if the API errors out when wrong number of PGP keys are
|
||||
// supplied for rekey
|
||||
func TestSysRekey_Init_pgpKeysEntriesForRekey(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: Handler,
|
||||
RequestResponseCallback: schema.ResponseValidatingCallback(t),
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
|
@ -37,44 +38,93 @@ func TestSysRekey_Init_pgpKeysEntriesForRekey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSysRekey_Init_Status(t *testing.T) {
|
||||
t.Run("status-barrier-default", func(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: Handler,
|
||||
RequestResponseCallback: schema.ResponseValidatingCallback(t),
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
cl := cluster.Cores[0].Client
|
||||
|
||||
resp, err := cl.Logical().Read("sys/rekey/init")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := resp.Data
|
||||
expected := map[string]interface{}{
|
||||
"started": false,
|
||||
"t": json.Number("0"),
|
||||
"n": json.Number("0"),
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"pgp_fingerprints": interface{}(nil),
|
||||
"backup": false,
|
||||
"nonce": "",
|
||||
"verification_required": false,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
defer cluster.Cleanup()
|
||||
cl := cluster.Cores[0].Client
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableUnauthRekey bool
|
||||
useToken bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "default-unauthenticated",
|
||||
enableUnauthRekey: true,
|
||||
useToken: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "default-authenticated",
|
||||
enableUnauthRekey: true,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required-without-token",
|
||||
enableUnauthRekey: false,
|
||||
useToken: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "auth-required-with-token",
|
||||
enableUnauthRekey: false,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cluster.Cores[0].Core.SetEnableUnauthRekey(tc.enableUnauthRekey)
|
||||
|
||||
if tc.useToken {
|
||||
cl.SetToken(cluster.RootToken)
|
||||
} else {
|
||||
cl.SetToken("")
|
||||
}
|
||||
|
||||
resp, err := cl.Logical().Read("sys/rekey/init")
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := resp.Data
|
||||
expected := map[string]interface{}{
|
||||
"started": false,
|
||||
"t": json.Number("0"),
|
||||
"n": json.Number("0"),
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"pgp_fingerprints": interface{}(nil),
|
||||
"backup": false,
|
||||
"nonce": "",
|
||||
"verification_required": false,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRekey_Init_Setup(t *testing.T) {
|
||||
t.Run("init-barrier-barrier-key", func(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: Handler,
|
||||
RequestResponseCallback: schema.ResponseValidatingCallback(t),
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
|
@ -142,10 +192,9 @@ func TestSysRekey_Init_Setup(t *testing.T) {
|
|||
func TestSysRekey_Init_Cancel(t *testing.T) {
|
||||
t.Run("cancel-barrier-barrier-key", func(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: Handler,
|
||||
RequestResponseCallback: schema.ResponseValidatingCallback(t),
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
cl := cluster.Cores[0].Client
|
||||
|
||||
|
|
@ -198,73 +247,147 @@ func TestSysRekey_badKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSysRekey_Update(t *testing.T) {
|
||||
t.Run("rekey-barrier-barrier-key", func(t *testing.T) {
|
||||
core, keys, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableUnauthRekey bool
|
||||
useToken bool
|
||||
expectInitError bool
|
||||
expectUpdateError bool
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated",
|
||||
enableUnauthRekey: true,
|
||||
useToken: false,
|
||||
expectInitError: false,
|
||||
expectUpdateError: false,
|
||||
},
|
||||
{
|
||||
name: "authenticated",
|
||||
enableUnauthRekey: true,
|
||||
useToken: true,
|
||||
expectInitError: false,
|
||||
expectUpdateError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required",
|
||||
enableUnauthRekey: false,
|
||||
useToken: true,
|
||||
expectInitError: false,
|
||||
expectUpdateError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required-no-token",
|
||||
enableUnauthRekey: false,
|
||||
useToken: false,
|
||||
expectInitError: true,
|
||||
expectUpdateError: true,
|
||||
},
|
||||
}
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/rekey/init", map[string]interface{}{
|
||||
"secret_shares": 5,
|
||||
"secret_threshold": 3,
|
||||
})
|
||||
var rekeyStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &rekeyStatus)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf := &vault.CoreConfig{}
|
||||
if tc.enableUnauthRekey {
|
||||
conf.EnableUnauthenticatedAccess = []string{"rekey"}
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
|
||||
DisableTLS: true,
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
defer cluster.Cleanup()
|
||||
cl := cluster.Cores[0].Client
|
||||
|
||||
var actual map[string]interface{}
|
||||
var expected map[string]interface{}
|
||||
reqToken := ""
|
||||
if tc.useToken {
|
||||
reqToken = cl.Token()
|
||||
}
|
||||
addr := cl.Address()
|
||||
|
||||
for i, key := range keys {
|
||||
resp = testHttpPut(t, token, addr+"/v1/sys/rekey/update", map[string]interface{}{
|
||||
"nonce": rekeyStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(key),
|
||||
resp := testHttpPut(t, reqToken, addr+"/v1/sys/rekey/init", map[string]interface{}{
|
||||
"secret_shares": 5,
|
||||
"secret_threshold": 3,
|
||||
})
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"started": true,
|
||||
"nonce": rekeyStatus["nonce"].(string),
|
||||
"backup": false,
|
||||
"pgp_fingerprints": interface{}(nil),
|
||||
"required": json.Number("3"),
|
||||
"t": json.Number("3"),
|
||||
"n": json.Number("5"),
|
||||
"progress": json.Number(fmt.Sprintf("%d", i+1)),
|
||||
"verification_required": false,
|
||||
if tc.expectInitError {
|
||||
testResponseStatus(t, resp, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var rekeyStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
testResponseBody(t, resp, &rekeyStatus)
|
||||
|
||||
if i+1 == len(keys) {
|
||||
delete(expected, "started")
|
||||
delete(expected, "required")
|
||||
delete(expected, "t")
|
||||
delete(expected, "n")
|
||||
delete(expected, "progress")
|
||||
expected["complete"] = true
|
||||
expected["keys"] = actual["keys"]
|
||||
expected["keys_base64"] = actual["keys_base64"]
|
||||
var actual map[string]interface{}
|
||||
var expected map[string]interface{}
|
||||
|
||||
if !tc.expectUpdateError {
|
||||
// Test with a bad key to ensure that we format errors the same way
|
||||
resp = testHttpPut(t, reqToken, addr+"/v1/sys/rekey/update", map[string]interface{}{
|
||||
"nonce": rekeyStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString([]byte("badkey")),
|
||||
})
|
||||
testResponseStatus(t, resp, http.StatusBadRequest)
|
||||
testResponseBody(t, resp, &actual)
|
||||
require.Equal(t, map[string]any{"errors": []any{"key is shorter than minimum 16 bytes"}}, actual)
|
||||
}
|
||||
|
||||
if i+1 < len(keys) && (actual["nonce"] == nil || actual["nonce"].(string) == "") {
|
||||
t.Fatalf("expected a nonce, i is %d, actual is %#v", i, actual)
|
||||
for i, key := range cluster.BarrierKeys {
|
||||
resp = testHttpPut(t, reqToken, addr+"/v1/sys/rekey/update", map[string]interface{}{
|
||||
"nonce": rekeyStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(key),
|
||||
})
|
||||
|
||||
if tc.expectUpdateError {
|
||||
testResponseStatus(t, resp, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"started": true,
|
||||
"nonce": rekeyStatus["nonce"].(string),
|
||||
"backup": false,
|
||||
"pgp_fingerprints": interface{}(nil),
|
||||
"required": json.Number("3"),
|
||||
"t": json.Number("3"),
|
||||
"n": json.Number("5"),
|
||||
"progress": json.Number(fmt.Sprintf("%d", i+1)),
|
||||
"verification_required": false,
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
if i+1 == len(cluster.BarrierKeys) {
|
||||
delete(expected, "started")
|
||||
delete(expected, "required")
|
||||
delete(expected, "t")
|
||||
delete(expected, "n")
|
||||
delete(expected, "progress")
|
||||
expected["complete"] = true
|
||||
expected["keys"] = actual["keys"]
|
||||
expected["keys_base64"] = actual["keys_base64"]
|
||||
}
|
||||
|
||||
if i+1 < len(cluster.BarrierKeys) && (actual["nonce"] == nil || actual["nonce"].(string) == "") {
|
||||
t.Fatalf("expected a nonce, i is %d, actual is %#v", i, actual)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: \n%#v\nactual: \n%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: \n%#v\nactual: \n%#v", expected, actual)
|
||||
retKeys := actual["keys"].([]interface{})
|
||||
if len(retKeys) != 5 {
|
||||
t.Fatalf("bad: %#v", retKeys)
|
||||
}
|
||||
}
|
||||
|
||||
retKeys := actual["keys"].([]interface{})
|
||||
if len(retKeys) != 5 {
|
||||
t.Fatalf("bad: %#v", retKeys)
|
||||
}
|
||||
keysB64 := actual["keys_base64"].([]interface{})
|
||||
if len(keysB64) != 5 {
|
||||
t.Fatalf("bad: %#v", keysB64)
|
||||
}
|
||||
})
|
||||
keysB64 := actual["keys_base64"].([]interface{})
|
||||
if len(keysB64) != 5 {
|
||||
t.Fatalf("bad: %#v", keysB64)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysRekey_ReInitUpdate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ func (d *Runner) Start(ctx context.Context, addSuffix, forceLocalAddr bool) (*St
|
|||
}
|
||||
|
||||
for from, to := range d.RunOptions.CopyFromTo {
|
||||
if err := copyToContainer(ctx, d.DockerAPI, c.ID, from, to); err != nil {
|
||||
if err := CopyToContainer(ctx, d.DockerAPI, c.ID, from, to); err != nil {
|
||||
_ = d.DockerAPI.ContainerRemove(ctx, c.ID, container.RemoveOptions{})
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -500,7 +500,7 @@ func (d *Runner) Start(ctx context.Context, addSuffix, forceLocalAddr bool) (*St
|
|||
|
||||
func (d *Runner) RefreshFiles(ctx context.Context, containerID string) error {
|
||||
for from, to := range d.RunOptions.CopyFromTo {
|
||||
if err := copyToContainer(ctx, d.DockerAPI, containerID, from, to); err != nil {
|
||||
if err := CopyToContainer(ctx, d.DockerAPI, containerID, from, to); err != nil {
|
||||
// TODO too drastic?
|
||||
_ = d.DockerAPI.ContainerRemove(ctx, containerID, container.RemoveOptions{})
|
||||
return err
|
||||
|
|
@ -555,7 +555,7 @@ func (d *Runner) Restart(ctx context.Context, containerID string) error {
|
|||
return d.DockerAPI.NetworkConnect(ctx, d.RunOptions.NetworkID, containerID, ends)
|
||||
}
|
||||
|
||||
func copyToContainer(ctx context.Context, dapi *client.Client, containerID, from, to string) error {
|
||||
func CopyToContainer(ctx context.Context, dapi *client.Client, containerID, from, to string) error {
|
||||
srcInfo, err := archive.CopyInfoSourcePath(from, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error copying from source %q: %v", from, err)
|
||||
|
|
|
|||
|
|
@ -1021,6 +1021,31 @@ func (n *DockerClusterNode) Restart(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (n *DockerClusterNode) Signal(ctx context.Context, signal string) error {
|
||||
return n.DockerAPI.ContainerKill(ctx, n.Container.ID, signal)
|
||||
}
|
||||
|
||||
func (n *DockerClusterNode) UpdateConfig(ctx context.Context, config *testcluster.VaultNodeConfig) error {
|
||||
// Marshal the config to JSON
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal config: %w", err)
|
||||
}
|
||||
|
||||
// Write the config to the work directory
|
||||
configPath := filepath.Join(n.WorkDir, "user.json")
|
||||
if err := os.WriteFile(configPath, configJSON, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write config file: %w", err)
|
||||
}
|
||||
|
||||
// Copy the updated config to the container
|
||||
if err := dockhelper.CopyToContainer(ctx, n.DockerAPI, n.Container.ID, configPath, "/vault/config/user.json"); err != nil {
|
||||
return fmt.Errorf("failed to copy config to container: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *DockerClusterNode) AddNetworkDelay(ctx context.Context, delay time.Duration, targetIP string) error {
|
||||
ip := net.ParseIP(targetIP)
|
||||
if ip == nil {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ type VaultNodeConfig struct {
|
|||
EnableResponseHeaderRaftNodeID bool `json:"enable_response_header_raft_node_id"`
|
||||
LicensePath string `json:"license_path"`
|
||||
FeatureFlags []string `json:"feature_flags,omitempty"`
|
||||
EnableUnauthenticatedAccess []string `json:"enable_unauthenticated_access,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterNode struct {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ const (
|
|||
// avoided like the HTTPContentType. The value must be a byte slice.
|
||||
HTTPRawBody = "http_raw_body"
|
||||
|
||||
// HTTPRawBodyError is similar to HTTPRawBody. The difference is that
|
||||
// HTTPRawBodyError is specifically intended for endpoints that want to manage
|
||||
// their error response directly. This was added to mitigate the risk of
|
||||
// causing regressions in the error responses of existing HTTPRawBody users.
|
||||
HTTPRawBodyError = "http_raw_body_error"
|
||||
|
||||
// HTTPStatusCode is the response code of the HTTP body that goes with the HTTPContentType.
|
||||
// This can only be specified for non-secrets, and should should be similarly
|
||||
// avoided like the HTTPContentType. The value must be an integer.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package logical
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -196,24 +197,38 @@ func AdjustErrorStatusCode(status *int, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
type errorResponse struct {
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
|
||||
// GenerateNonLogicalErrorResponse returns a struct that can be serialized to
|
||||
// JSON as part of reporting an error to callers. It is used by some older APIs
|
||||
// that live in the http layer which don't use logical.Response, as well as by
|
||||
// some that do but are emulating the older ones.
|
||||
func GenerateNonLogicalErrorResponse(status int, err error) *errorResponse {
|
||||
resp := &errorResponse{Errors: make([]string, 0, 1)}
|
||||
if err != nil {
|
||||
resp.Errors = append(resp.Errors, err.Error())
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func RespondError(w http.ResponseWriter, status int, err error) {
|
||||
AdjustErrorStatusCode(&status, err)
|
||||
|
||||
defer IncrementResponseStatusCodeMetric(status)
|
||||
|
||||
var b bytes.Buffer
|
||||
enc := json.NewEncoder(&b)
|
||||
enc.Encode(GenerateNonLogicalErrorResponse(status, err))
|
||||
RespondWithBody(w, status, b.String())
|
||||
}
|
||||
|
||||
func RespondWithBody(w http.ResponseWriter, status int, body string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
|
||||
type ErrorResponse struct {
|
||||
Errors []string `json:"errors"`
|
||||
}
|
||||
resp := &ErrorResponse{Errors: make([]string, 0, 1)}
|
||||
if err != nil {
|
||||
resp.Errors = append(resp.Errors, err.Error())
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.Encode(resp)
|
||||
w.Write([]byte(body))
|
||||
}
|
||||
|
||||
func RespondErrorAndData(w http.ResponseWriter, status int, data interface{}, err error) {
|
||||
|
|
|
|||
|
|
@ -274,6 +274,11 @@ type Core struct {
|
|||
// the generate-root process simply to talk to the new follower cluster.
|
||||
devToken string
|
||||
|
||||
// enableUnauthRekey controls whether rekey endpoints are registered as
|
||||
// unauthenticated endpoints (true) or as authenticated sys backend
|
||||
// endpoints (false, default).
|
||||
enableUnauthRekey *atomic.Bool
|
||||
|
||||
// HABackend may be available depending on the physical backend
|
||||
ha physical.HABackend
|
||||
|
||||
|
|
@ -971,6 +976,12 @@ type CoreConfig struct {
|
|||
|
||||
// ReportingScanDirectory is where files generated by /sys/reporting/scan will go.
|
||||
ReportingScanDirectory string
|
||||
|
||||
// EnableUnauthenticatedAccess is a list of endpoint names that should be
|
||||
// accessible without authentication, despite them being by default authenticated.
|
||||
// These aren't the actual paths to endpoints, but rather specific values that
|
||||
// identify groups of endpoints, e.g. "rekey" refers to the sys/rekey/* endpoints.
|
||||
EnableUnauthenticatedAccess []string
|
||||
}
|
||||
|
||||
// GetServiceRegistration returns the config's ServiceRegistration, or nil if it does
|
||||
|
|
@ -1155,6 +1166,7 @@ func CreateCore(conf *CoreConfig) (*Core, error) {
|
|||
periodicLeaderRefreshInterval: conf.PeriodicLeaderRefreshInterval,
|
||||
rpcLastSuccessfulHeartbeat: new(atomic.Value),
|
||||
reportingScanDirectory: conf.ReportingScanDirectory,
|
||||
enableUnauthRekey: new(atomic.Bool),
|
||||
}
|
||||
|
||||
c.certCountManager = cert_count.InitCertificateCountManager(c.logger)
|
||||
|
|
@ -1437,6 +1449,15 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||
|
||||
c.clusterAddrBridge = conf.ClusterAddrBridge
|
||||
c.licenseReloadCh = conf.LicenseReload
|
||||
|
||||
// Check if "rekey" is in the EnableUnauthenticatedAccess list
|
||||
for _, endpoint := range conf.EnableUnauthenticatedAccess {
|
||||
if endpoint == "rekey" {
|
||||
c.enableUnauthRekey.Store(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
|
@ -4434,6 +4455,24 @@ func (c *Core) ReloadIntrospectionEndpointEnabled() {
|
|||
c.introspectionEnabled = conf.(*server.Config).EnableIntrospectionEndpoint
|
||||
}
|
||||
|
||||
func (c *Core) ReloadEnableUnauthenticatedAccess() {
|
||||
conf := c.rawConfig.Load()
|
||||
if conf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if "rekey" is in the EnableUnauthenticatedAccess list
|
||||
enableRekey := false
|
||||
for _, endpoint := range conf.(*server.Config).EnableUnauthenticatedAccess {
|
||||
if endpoint == "rekey" {
|
||||
enableRekey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.enableUnauthRekey.Store(enableRekey)
|
||||
}
|
||||
|
||||
type PeerNode struct {
|
||||
Hostname string `json:"hostname"`
|
||||
APIAddress string `json:"api_address"`
|
||||
|
|
@ -4918,3 +4957,11 @@ var errRemovedHANode = errors.New("node has been removed from the HA cluster")
|
|||
func (c *Core) CoreNumber() int {
|
||||
return c.coreNumber
|
||||
}
|
||||
|
||||
func (c *Core) GetEnableUnauthRekey() bool {
|
||||
return c.enableUnauthRekey.Load()
|
||||
}
|
||||
|
||||
func (c *Core) SetEnableUnauthRekey(val bool) {
|
||||
c.enableUnauthRekey.Store(val)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ func TestSysRekey_Verification(t *testing.T) {
|
|||
func testSysRekey_Verification(t *testing.T, recovery bool, legacyShamir bool) {
|
||||
opts := &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
NumCores: 1,
|
||||
}
|
||||
switch {
|
||||
case recovery:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
// Copyright IBM Corp. 2016, 2025
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package system_binary
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/sdk/helper/testcluster"
|
||||
"github.com/hashicorp/vault/sdk/helper/testcluster/docker"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// waitForRekeyInConfig polls sys/config/state/sanitized until the rekey endpoint
|
||||
// appears or disappears from enable_unauthenticated_access based on shouldBePresent.
|
||||
func waitForRekeyInConfig(t *testing.T, client *api.Client, rootToken string, shouldBePresent bool) {
|
||||
clientWithAuth, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientWithAuth.SetToken(rootToken)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
resp, err := clientWithAuth.Logical().Read("sys/config/state/sanitized")
|
||||
if err != nil {
|
||||
t.Logf("error reading config state: %v", err)
|
||||
return false
|
||||
}
|
||||
if resp == nil || resp.Data == nil {
|
||||
t.Logf("nil response or data from config state")
|
||||
return false
|
||||
}
|
||||
|
||||
override, ok := resp.Data["enable_unauthenticated_access"]
|
||||
if !ok {
|
||||
// If the field is not present, rekey is not in the override list
|
||||
return !shouldBePresent
|
||||
}
|
||||
|
||||
// Check if override contains "rekey"
|
||||
rekeyFound := false
|
||||
if overrideSlice, ok := override.([]interface{}); ok {
|
||||
for _, v := range overrideSlice {
|
||||
if str, ok := v.(string); ok && str == "rekey" {
|
||||
rekeyFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shouldBePresent {
|
||||
return rekeyFound
|
||||
}
|
||||
return !rekeyFound
|
||||
}, 10*time.Second, 100*time.Millisecond, "rekey presence in enable_unauthenticated_access did not match expected state")
|
||||
}
|
||||
|
||||
// TestSysRekey_ConfigReload tests that the rekey status endpoint can be toggled
|
||||
// between requiring authentication and not requiring authentication by using
|
||||
// the enable_unauthenticated_access config option and reloading the config.
|
||||
func TestSysRekey_ConfigReload(t *testing.T) {
|
||||
binary := os.Getenv("VAULT_BINARY")
|
||||
if binary == "" {
|
||||
t.Skip("only running docker test when $VAULT_BINARY present")
|
||||
}
|
||||
|
||||
nodeConfig := &testcluster.VaultNodeConfig{
|
||||
LogLevel: "TRACE",
|
||||
}
|
||||
opts := &docker.DockerClusterOptions{
|
||||
ImageRepo: "hashicorp/vault",
|
||||
ImageTag: "latest",
|
||||
VaultBinary: binary,
|
||||
DisableMlock: true,
|
||||
ClusterOptions: testcluster.ClusterOptions{
|
||||
NumCores: 1,
|
||||
VaultNodeConfig: nodeConfig,
|
||||
},
|
||||
}
|
||||
|
||||
cluster := docker.NewTestDockerCluster(t, opts)
|
||||
defer cluster.Cleanup()
|
||||
|
||||
node := cluster.Nodes()[0].(*docker.DockerClusterNode)
|
||||
client := node.APIClient()
|
||||
rootToken := cluster.GetRootToken()
|
||||
|
||||
// Test 1: Without enable_unauthenticated_access, rekey status should require auth
|
||||
t.Run("requires-auth-by-default", func(t *testing.T) {
|
||||
// Try without token - should fail
|
||||
clientNoAuth, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientNoAuth.SetToken("")
|
||||
|
||||
_, err = clientNoAuth.Logical().Read("sys/rekey/init")
|
||||
require.Error(t, err, "expected error when accessing rekey status without token")
|
||||
require.Contains(t, err.Error(), "permission denied", "error should indicate permission denied")
|
||||
|
||||
// Try with token - should succeed
|
||||
clientWithAuth, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientWithAuth.SetToken(rootToken)
|
||||
|
||||
resp, err := clientWithAuth.Logical().Read("sys/rekey/init")
|
||||
require.NoError(t, err, "should succeed with valid token")
|
||||
require.NotNil(t, resp, "response should not be nil")
|
||||
require.NotNil(t, resp.Data, "response data should not be nil")
|
||||
require.False(t, resp.Data["started"].(bool), "rekey should not be started")
|
||||
})
|
||||
|
||||
// Test 2: Update config to enable unauthenticated rekey and reload
|
||||
t.Run("enable-unauthenticated-via-config-reload", func(t *testing.T) {
|
||||
// Create updated config with enable_unauthenticated_access
|
||||
nodeConfig.EnableUnauthenticatedAccess = []string{"rekey"}
|
||||
|
||||
// Update the config and copy it to the container
|
||||
err := node.UpdateConfig(t.Context(), nodeConfig)
|
||||
require.NoError(t, err, "failed to update config")
|
||||
|
||||
// Send SIGHUP to reload the configuration
|
||||
err = node.Signal(t.Context(), "SIGHUP")
|
||||
require.NoError(t, err, "failed to send SIGHUP")
|
||||
|
||||
// Wait for rekey to appear in enable_unauthenticated_access
|
||||
waitForRekeyInConfig(t, client, rootToken, true)
|
||||
|
||||
// Now test that rekey status works without auth
|
||||
clientNoAuth, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientNoAuth.SetToken("")
|
||||
|
||||
resp, err := clientNoAuth.Logical().Read("sys/rekey/init")
|
||||
require.NoError(t, err, "should succeed without token after config reload")
|
||||
require.NotNil(t, resp, "response should not be nil")
|
||||
require.NotNil(t, resp.Data, "response data should not be nil")
|
||||
require.False(t, resp.Data["started"].(bool), "rekey should not be started")
|
||||
|
||||
// Verify it still works with auth
|
||||
clientWithAuth2, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientWithAuth2.SetToken(rootToken)
|
||||
|
||||
resp2, err := clientWithAuth2.Logical().Read("sys/rekey/init")
|
||||
require.NoError(t, err, "should still succeed with valid token")
|
||||
require.NotNil(t, resp2, "response should not be nil")
|
||||
require.NotNil(t, resp2.Data, "response data should not be nil")
|
||||
require.False(t, resp2.Data["started"].(bool), "rekey should not be started")
|
||||
})
|
||||
|
||||
// Test 3: Remove the override and reload to restore auth requirement
|
||||
t.Run("restore-auth-requirement-via-config-reload", func(t *testing.T) {
|
||||
// Create config without enable_unauthenticated_access
|
||||
nodeConfig.EnableUnauthenticatedAccess = nil
|
||||
|
||||
// Update the config and copy it to the container
|
||||
err := node.UpdateConfig(t.Context(), nodeConfig)
|
||||
require.NoError(t, err, "failed to update config")
|
||||
|
||||
// Send SIGHUP to reload the configuration
|
||||
err = node.Signal(t.Context(), "SIGHUP")
|
||||
require.NoError(t, err, "failed to send SIGHUP")
|
||||
|
||||
// Wait for rekey to be removed from enable_unauthenticated_access
|
||||
waitForRekeyInConfig(t, client, rootToken, false)
|
||||
|
||||
// Now test that rekey status requires auth again
|
||||
clientNoAuth, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientNoAuth.SetToken("")
|
||||
|
||||
_, err = clientNoAuth.Logical().Read("sys/rekey/init")
|
||||
require.Error(t, err, "should fail without token after restoring auth requirement")
|
||||
require.Contains(t, err.Error(), "permission denied", "error should indicate permission denied")
|
||||
|
||||
// Verify it still works with auth
|
||||
clientWithAuth2, err := client.Clone()
|
||||
require.NoError(t, err, "failed to clone client")
|
||||
clientWithAuth2.SetToken(rootToken)
|
||||
|
||||
resp, err := clientWithAuth2.Logical().Read("sys/rekey/init")
|
||||
require.NoError(t, err, "should succeed with valid token")
|
||||
require.NotNil(t, resp, "response should not be nil")
|
||||
require.NotNil(t, resp.Data, "response data should not be nil")
|
||||
require.False(t, resp.Data["started"].(bool), "rekey should not be started")
|
||||
})
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
|
|
@ -14,6 +15,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
|
@ -43,6 +45,7 @@ import (
|
|||
"github.com/hashicorp/vault/helper/metricsutil"
|
||||
"github.com/hashicorp/vault/helper/monitor"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/helper/random"
|
||||
"github.com/hashicorp/vault/helper/versions"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
|
|
@ -106,6 +109,57 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
|
|||
raftChallengeLimiter: rate.NewLimiter(rate.Limit(RaftChallengesPerSecond), RaftInitialChallengeLimit),
|
||||
}
|
||||
|
||||
// Build the unauthenticated paths list. Rekey paths are conditionally added based on
|
||||
// the enableUnauthRekey configuration (retrieved from Core).
|
||||
unauthenticatedPaths := []string{
|
||||
"wrapping/lookup",
|
||||
"wrapping/pubkey",
|
||||
"replication/status",
|
||||
"internal/specs/openapi",
|
||||
"internal/ui/authenticated-messages",
|
||||
"internal/ui/unauthenticated-messages",
|
||||
"internal/ui/mounts",
|
||||
"internal/ui/mounts/*",
|
||||
"internal/ui/namespaces",
|
||||
"replication/performance/status",
|
||||
"replication/dr/status",
|
||||
"replication/dr/secondary/promote",
|
||||
"replication/dr/secondary/disable",
|
||||
"replication/dr/secondary/recover",
|
||||
"replication/dr/secondary/update-primary",
|
||||
"replication/dr/secondary/operation-token/delete",
|
||||
"replication/dr/secondary/license",
|
||||
"replication/dr/secondary/license/signed",
|
||||
"replication/dr/secondary/license/status",
|
||||
"replication/dr/secondary/sys/config/reload/license",
|
||||
"replication/dr/secondary/reindex",
|
||||
"storage/raft/bootstrap/challenge",
|
||||
"storage/raft/bootstrap/answer",
|
||||
"init",
|
||||
"seal-status",
|
||||
"unseal",
|
||||
"leader",
|
||||
"health",
|
||||
"generate-root/attempt",
|
||||
"generate-root/update",
|
||||
"decode-token",
|
||||
"mfa/validate",
|
||||
}
|
||||
|
||||
// Note that while rekeyPaths are not part of unauthenticatedPaths, that's
|
||||
// because they are defined both here and in http.handler. The latter ones
|
||||
// are unauthenticated and don't use the logical framework. They are enabled
|
||||
// only when Core.enableUnauthRekey is true, and being more specific paths
|
||||
// than the v1/sys mux path they take precedence when enabled.
|
||||
rekeyPaths := []string{
|
||||
"rekey/init",
|
||||
"rekey/update",
|
||||
"rekey/verify",
|
||||
"rekey-recovery-key/init",
|
||||
"rekey-recovery-key/update",
|
||||
"rekey-recovery-key/verify",
|
||||
}
|
||||
|
||||
b.Backend = &framework.Backend{
|
||||
RunningVersion: versions.DefaultBuiltinVersion,
|
||||
Help: strings.TrimSpace(sysHelpRoot),
|
||||
|
|
@ -147,46 +201,7 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
|
|||
"step-down",
|
||||
},
|
||||
|
||||
Unauthenticated: []string{
|
||||
"wrapping/lookup",
|
||||
"wrapping/pubkey",
|
||||
"replication/status",
|
||||
"internal/specs/openapi",
|
||||
"internal/ui/authenticated-messages",
|
||||
"internal/ui/unauthenticated-messages",
|
||||
"internal/ui/mounts",
|
||||
"internal/ui/mounts/*",
|
||||
"internal/ui/namespaces",
|
||||
"replication/performance/status",
|
||||
"replication/dr/status",
|
||||
"replication/dr/secondary/promote",
|
||||
"replication/dr/secondary/disable",
|
||||
"replication/dr/secondary/recover",
|
||||
"replication/dr/secondary/update-primary",
|
||||
"replication/dr/secondary/operation-token/delete",
|
||||
"replication/dr/secondary/license",
|
||||
"replication/dr/secondary/license/signed",
|
||||
"replication/dr/secondary/license/status",
|
||||
"replication/dr/secondary/sys/config/reload/license",
|
||||
"replication/dr/secondary/reindex",
|
||||
"storage/raft/bootstrap/challenge",
|
||||
"storage/raft/bootstrap/answer",
|
||||
"init",
|
||||
"seal-status",
|
||||
"unseal",
|
||||
"leader",
|
||||
"health",
|
||||
"generate-root/attempt",
|
||||
"generate-root/update",
|
||||
"decode-token",
|
||||
"rekey/init",
|
||||
"rekey/update",
|
||||
"rekey/verify",
|
||||
"rekey-recovery-key/init",
|
||||
"rekey-recovery-key/update",
|
||||
"rekey-recovery-key/verify",
|
||||
"mfa/validate",
|
||||
},
|
||||
Unauthenticated: unauthenticatedPaths,
|
||||
|
||||
LocalStorage: []string{
|
||||
expirationSubPath,
|
||||
|
|
@ -199,6 +214,8 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
|
|||
SealWrapStorage: []string{
|
||||
managedKeyRegistrySubPath,
|
||||
},
|
||||
|
||||
Binary: rekeyPaths,
|
||||
},
|
||||
}
|
||||
b.Backend.PathsSpecial.Unauthenticated = append(b.Backend.PathsSpecial.Unauthenticated, entUnauthenticatedPaths()...)
|
||||
|
|
@ -1373,6 +1390,463 @@ func (b *SystemBackend) handleRekeyDeleteRecovery(ctx context.Context, req *logi
|
|||
return b.handleRekeyDelete(ctx, req, data, true)
|
||||
}
|
||||
|
||||
type RekeyStatusResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Started bool `json:"started"`
|
||||
T int `json:"t"`
|
||||
N int `json:"n"`
|
||||
Progress int `json:"progress"`
|
||||
Required int `json:"required"`
|
||||
PGPFingerprints []string `json:"pgp_fingerprints"`
|
||||
Backup bool `json:"backup"`
|
||||
VerificationRequired bool `json:"verification_required"`
|
||||
VerificationNonce string `json:"verification_nonce,omitempty"`
|
||||
}
|
||||
|
||||
func HandleSysRekeyInitGet(ctx context.Context, core *Core, recovery bool, grabLock bool) (*RekeyStatusResponse, int, error) {
|
||||
barrierConfig, barrierConfErr := core.SealAccess().BarrierConfig(ctx)
|
||||
if barrierConfErr != nil {
|
||||
return nil, http.StatusInternalServerError, barrierConfErr
|
||||
}
|
||||
if barrierConfig == nil {
|
||||
return nil, http.StatusBadRequest, fmt.Errorf("server is not yet initialized")
|
||||
}
|
||||
|
||||
// Get the rekey configuration
|
||||
rekeyConf, err := core.RekeyConfig(recovery, grabLock)
|
||||
if err != nil {
|
||||
return nil, err.Code(), err
|
||||
}
|
||||
|
||||
sealThreshold, err := core.RekeyThreshold(ctx, recovery, grabLock)
|
||||
if err != nil {
|
||||
return nil, err.Code(), err
|
||||
}
|
||||
|
||||
// Format the status
|
||||
status := &RekeyStatusResponse{
|
||||
Started: false,
|
||||
T: 0,
|
||||
N: 0,
|
||||
Required: sealThreshold,
|
||||
}
|
||||
if rekeyConf != nil {
|
||||
// Get the progress
|
||||
started, progress, err := core.RekeyProgress(recovery, false, grabLock)
|
||||
if err != nil {
|
||||
return nil, err.Code(), err
|
||||
}
|
||||
|
||||
status.Nonce = rekeyConf.Nonce
|
||||
status.Started = started
|
||||
status.T = rekeyConf.SecretThreshold
|
||||
status.N = rekeyConf.SecretShares
|
||||
status.Progress = progress
|
||||
status.VerificationRequired = rekeyConf.VerificationRequired
|
||||
status.VerificationNonce = rekeyConf.VerificationNonce
|
||||
if rekeyConf.PGPKeys != nil && len(rekeyConf.PGPKeys) != 0 {
|
||||
pgpFingerprints, err := pgpkeys.GetFingerprints(rekeyConf.PGPKeys, nil)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
status.PGPFingerprints = pgpFingerprints
|
||||
status.Backup = rekeyConf.Backup
|
||||
}
|
||||
}
|
||||
return status, 0, nil
|
||||
}
|
||||
|
||||
type RekeyRequest struct {
|
||||
SecretShares int `json:"secret_shares"`
|
||||
SecretThreshold int `json:"secret_threshold"`
|
||||
StoredShares int `json:"stored_shares"`
|
||||
PGPKeys []string `json:"pgp_keys"`
|
||||
Backup bool `json:"backup"`
|
||||
RequireVerification bool `json:"require_verification"`
|
||||
}
|
||||
|
||||
func HandleSysRekeyInitPut(core *Core, recovery bool, req *RekeyRequest, grabLock bool) (int, error) {
|
||||
if req.Backup && len(req.PGPKeys) == 0 {
|
||||
return http.StatusBadRequest, fmt.Errorf("cannot request a backup of the new keys without providing PGP keys for encryption")
|
||||
}
|
||||
|
||||
if len(req.PGPKeys) > 0 && len(req.PGPKeys) != req.SecretShares {
|
||||
return http.StatusBadRequest, fmt.Errorf("incorrect number of PGP keys for rekey")
|
||||
}
|
||||
|
||||
// Initialize the rekey
|
||||
err := core.RekeyInit(&SealConfig{
|
||||
SecretShares: req.SecretShares,
|
||||
SecretThreshold: req.SecretThreshold,
|
||||
StoredShares: req.StoredShares,
|
||||
PGPKeys: req.PGPKeys,
|
||||
Backup: req.Backup,
|
||||
VerificationRequired: req.RequireVerification,
|
||||
Created: time.Now().UTC(),
|
||||
}, recovery, grabLock)
|
||||
if err != nil {
|
||||
return err.Code(), err
|
||||
}
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
type RekeyUpdateRequest struct {
|
||||
Nonce string
|
||||
Key string
|
||||
}
|
||||
|
||||
type RekeyUpdateResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Complete bool `json:"complete"`
|
||||
Keys []string `json:"keys"`
|
||||
KeysB64 []string `json:"keys_base64"`
|
||||
PGPFingerprints []string `json:"pgp_fingerprints"`
|
||||
Backup bool `json:"backup"`
|
||||
VerificationRequired bool `json:"verification_required"`
|
||||
VerificationNonce string `json:"verification_nonce,omitempty"`
|
||||
}
|
||||
|
||||
func HandleSysRekeyUpdatePut(ctx context.Context, core *Core, recovery bool, req *RekeyUpdateRequest, grabLock bool) (*RekeyUpdateResponse, int, error) {
|
||||
if req.Key == "" {
|
||||
return nil, http.StatusBadRequest, errors.New("'key' must be specified in request body as JSON")
|
||||
}
|
||||
|
||||
// Decode the key, which is base64 or hex encoded
|
||||
min, max := core.BarrierKeyLength()
|
||||
key, err := hex.DecodeString(req.Key)
|
||||
// We check min and max here to ensure that a string that is base64
|
||||
// encoded but also valid hex will not be valid and we instead base64
|
||||
// decode it
|
||||
if err != nil || len(key) < min || len(key) > max {
|
||||
key, err = base64.StdEncoding.DecodeString(req.Key)
|
||||
if err != nil {
|
||||
return nil, http.StatusBadRequest, errors.New("'key' must be a valid hex or base64 string")
|
||||
}
|
||||
}
|
||||
|
||||
// Use the key to make progress on rekey
|
||||
result, rekeyErr := core.RekeyUpdate(ctx, key, req.Nonce, recovery, grabLock)
|
||||
|
||||
if rekeyErr != nil {
|
||||
return nil, rekeyErr.Code(), rekeyErr
|
||||
}
|
||||
|
||||
// Format the response
|
||||
resp := &RekeyUpdateResponse{}
|
||||
if result != nil {
|
||||
resp.Complete = true
|
||||
resp.Nonce = req.Nonce
|
||||
resp.Backup = result.Backup
|
||||
resp.PGPFingerprints = result.PGPFingerprints
|
||||
resp.VerificationRequired = result.VerificationRequired
|
||||
resp.VerificationNonce = result.VerificationNonce
|
||||
|
||||
// Encode the keys
|
||||
keys := make([]string, 0, len(result.SecretShares))
|
||||
keysB64 := make([]string, 0, len(result.SecretShares))
|
||||
for _, k := range result.SecretShares {
|
||||
keys = append(keys, hex.EncodeToString(k))
|
||||
keysB64 = append(keysB64, base64.StdEncoding.EncodeToString(k))
|
||||
}
|
||||
resp.Keys = keys
|
||||
resp.KeysB64 = keysB64
|
||||
return resp, 0, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
type RekeyVerifyStatusResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Started bool `json:"started"`
|
||||
T int `json:"t"`
|
||||
N int `json:"n"`
|
||||
Progress int `json:"progress"`
|
||||
}
|
||||
|
||||
func HandleSysRekeyVerifyGet(ctx context.Context, core *Core, recovery bool, grabLock bool) (*RekeyVerifyStatusResponse, int, error) {
|
||||
barrierConfig, err := core.SealAccess().BarrierConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
if barrierConfig == nil {
|
||||
return nil, http.StatusBadRequest, fmt.Errorf("server is not yet initialized")
|
||||
}
|
||||
|
||||
// Get the rekey configuration
|
||||
rekeyConf, rekeyErr := core.RekeyConfig(recovery, grabLock)
|
||||
if rekeyErr != nil {
|
||||
return nil, rekeyErr.Code(), rekeyErr
|
||||
}
|
||||
if rekeyConf == nil {
|
||||
return nil, http.StatusBadRequest, fmt.Errorf("no rekey configuration found")
|
||||
}
|
||||
|
||||
// Get the progress
|
||||
started, progress, rekeyErr := core.RekeyProgress(recovery, true, grabLock)
|
||||
if rekeyErr != nil {
|
||||
return nil, rekeyErr.Code(), rekeyErr
|
||||
}
|
||||
|
||||
// Format the status
|
||||
status := &RekeyVerifyStatusResponse{
|
||||
Started: started,
|
||||
Nonce: rekeyConf.VerificationNonce,
|
||||
T: rekeyConf.SecretThreshold,
|
||||
N: rekeyConf.SecretShares,
|
||||
Progress: progress,
|
||||
}
|
||||
return status, 0, nil
|
||||
}
|
||||
|
||||
type RekeyVerificationUpdateRequest struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type RekeyVerificationUpdateResponse struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Complete bool `json:"complete"`
|
||||
}
|
||||
|
||||
func HandleSysRekeyVerifyPut(ctx context.Context, core *Core, recovery bool, grabLock bool, req *RekeyVerificationUpdateRequest) (*RekeyVerificationUpdateResponse, int, error) {
|
||||
if req.Key == "" {
|
||||
return nil, http.StatusBadRequest, errors.New("'key' must be specified in request body as JSON")
|
||||
}
|
||||
|
||||
// Decode the key, which is base64 or hex encoded
|
||||
min, max := core.BarrierKeyLength()
|
||||
key, err := hex.DecodeString(req.Key)
|
||||
// We check min and max here to ensure that a string that is base64
|
||||
// encoded but also valid hex will not be valid and we instead base64
|
||||
// decode it
|
||||
if err != nil || len(key) < min || len(key) > max {
|
||||
key, err = base64.StdEncoding.DecodeString(req.Key)
|
||||
if err != nil {
|
||||
return nil, http.StatusBadRequest, errors.New("'key' must be a valid hex or base64 string")
|
||||
}
|
||||
}
|
||||
|
||||
// Use the key to make progress on rekey
|
||||
result, rekeyErr := core.RekeyVerify(ctx, key, req.Nonce, recovery, grabLock)
|
||||
if rekeyErr != nil {
|
||||
return nil, rekeyErr.Code(), rekeyErr
|
||||
}
|
||||
if result != nil {
|
||||
return &RekeyVerificationUpdateResponse{
|
||||
Nonce: result.Nonce,
|
||||
Complete: result.Complete,
|
||||
}, http.StatusOK, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
// handleRekeyInit handles the rekey/init endpoint for both barrier and recovery keys
|
||||
func (b *SystemBackend) handleRekeyInit(
|
||||
ctx context.Context,
|
||||
req *logical.Request,
|
||||
recovery bool,
|
||||
) (*logical.Response, error) {
|
||||
// Check replication state
|
||||
repState := b.Core.ReplicationState()
|
||||
if repState.HasState(consts.ReplicationPerformanceSecondary) {
|
||||
return logical.ErrorResponse("rekeying can only be performed on the primary cluster when replication is activated"), nil
|
||||
}
|
||||
|
||||
// Check if recovery key is supported
|
||||
if recovery && !b.Core.SealAccess().RecoveryKeySupported() {
|
||||
return logical.ErrorResponse("recovery rekeying not supported"), nil
|
||||
}
|
||||
|
||||
switch req.Operation {
|
||||
case logical.ReadOperation:
|
||||
return b.handleRekeyInitGet(ctx, recovery)
|
||||
case logical.UpdateOperation:
|
||||
return b.handleRekeyInitPut(ctx, recovery)
|
||||
case logical.DeleteOperation:
|
||||
return b.handleRekeyInitDelete(ctx, recovery)
|
||||
default:
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
// getJSONBody populates the out struct with the contents of the HTTP request body
|
||||
// and returns (nil, nil), or on error returns values that a handler can return
|
||||
// for failure. This is intended for older APIs that don't use the framework.
|
||||
func getJSONBody(ctx context.Context, out any) (*logical.Response, error) {
|
||||
body, ok := logical.ContextOriginalBodyValue(ctx)
|
||||
if !ok {
|
||||
return nonLogicalError(http.StatusInternalServerError, fmt.Errorf("failed to retrieve request body"))
|
||||
}
|
||||
err := jsonutil.DecodeJSONFromReader(body, out)
|
||||
if err != nil && err != io.EOF {
|
||||
return nonLogicalError(http.StatusBadRequest, fmt.Errorf("failed to parse JSON input: %w", err))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// nonLogicalError creates an error response for older handlers that don't follow
|
||||
// current vault response conventions.
|
||||
func nonLogicalError(code int, err error) (*logical.Response, error) {
|
||||
logical.AdjustErrorStatusCode(&code, err)
|
||||
defer logical.IncrementResponseStatusCodeMetric(code)
|
||||
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(logical.GenerateNonLogicalErrorResponse(code, err))
|
||||
|
||||
resp, _ := logical.RespondWithStatusCode(nil, nil, code)
|
||||
resp.Data[logical.HTTPRawBodyError] = buf.String()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// nonLogicalResponse takes the result of a request handler, and either returns
|
||||
// an error response if err is non nil, or serializes the val into the response.
|
||||
// It uses the HTTP raw body field in response data, since this is for older
|
||||
// APIs that don't follow our usual response format.
|
||||
func nonLogicalResponse(val any, code int, err error) (*logical.Response, error) {
|
||||
if err != nil {
|
||||
return nonLogicalError(code, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(val)
|
||||
resp, _ := logical.RespondWithStatusCode(nil, nil, http.StatusOK)
|
||||
resp.Data[logical.HTTPRawBody] = buf.String()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyInitBarrier(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyInit(ctx, req, false)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyInitRecovery(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyInit(ctx, req, true)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyInitGet(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
status, code, err := HandleSysRekeyInitGet(ctx, b.Core, recovery, false)
|
||||
return nonLogicalResponse(status, code, err)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyInitPut(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
var req RekeyRequest
|
||||
resp, err := getJSONBody(ctx, &req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
code, err := HandleSysRekeyInitPut(b.Core, recovery, &req, false)
|
||||
if err != nil {
|
||||
return nonLogicalError(code, err)
|
||||
}
|
||||
|
||||
return b.handleRekeyInitGet(ctx, recovery)
|
||||
}
|
||||
|
||||
type RekeyDeleteRequest struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyInitDelete(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
var req RekeyDeleteRequest
|
||||
resp, err := getJSONBody(ctx, &req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if err := b.Core.RekeyCancel(recovery, req.Nonce, 10*time.Minute, false); err != nil {
|
||||
return nil, fmt.Errorf("failed to cancel rekey: %w", err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// handleRekeyUpdate handles the rekey/update endpoint for both barrier and recovery keys
|
||||
func (b *SystemBackend) handleRekeyUpdate(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
var req RekeyUpdateRequest
|
||||
resp, err := getJSONBody(ctx, &req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Use the key to make progress on rekey
|
||||
result, code, err := HandleSysRekeyUpdatePut(ctx, b.Core, recovery, &req, false)
|
||||
if err == nil && result == nil {
|
||||
return b.handleRekeyInitGet(ctx, recovery)
|
||||
}
|
||||
return nonLogicalResponse(result, code, err)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyUpdateBarrier(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyUpdate(ctx, false)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyUpdateRecovery(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyUpdate(ctx, true)
|
||||
}
|
||||
|
||||
// handleRekeyVerify handles the rekey/verify endpoint for both barrier and recovery keys
|
||||
func (b *SystemBackend) handleRekeyVerify(ctx context.Context, req *logical.Request, _ *framework.FieldData, recovery bool) (*logical.Response, error) {
|
||||
repState := b.Core.ReplicationState()
|
||||
if repState.HasState(consts.ReplicationPerformanceSecondary) {
|
||||
return logical.ErrorResponse("rekeying can only be performed on the primary cluster when replication is activated"), nil
|
||||
}
|
||||
|
||||
// Check if recovery key is supported
|
||||
if recovery && !b.Core.SealAccess().RecoveryKeySupported() {
|
||||
return logical.ErrorResponse("recovery rekeying not supported"), nil
|
||||
}
|
||||
|
||||
switch req.Operation {
|
||||
case logical.ReadOperation:
|
||||
return b.handleRekeyVerifyGet(ctx, recovery)
|
||||
case logical.UpdateOperation:
|
||||
return b.handleRekeyVerifyPut(ctx, recovery)
|
||||
case logical.DeleteOperation:
|
||||
return b.handleRekeyVerifyDelete(ctx, recovery)
|
||||
default:
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyVerifyBarrier(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyVerify(ctx, req, data, false)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyVerifyRecovery(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
return b.handleRekeyVerify(ctx, req, data, true)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyVerifyGet(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
status, code, err := HandleSysRekeyVerifyGet(ctx, b.Core, recovery, false)
|
||||
return nonLogicalResponse(status, code, err)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyVerifyDelete(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
if err := b.Core.RekeyVerifyRestart(recovery, false); err != nil {
|
||||
return nil, fmt.Errorf("failed to restart rekey verification: %w", err)
|
||||
}
|
||||
|
||||
return b.handleRekeyVerifyGet(ctx, recovery)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleRekeyVerifyPut(ctx context.Context, recovery bool) (*logical.Response, error) {
|
||||
var req RekeyVerificationUpdateRequest
|
||||
resp, err := getJSONBody(ctx, &req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
result, code, err := HandleSysRekeyVerifyPut(ctx, b.Core, recovery, false, &RekeyVerificationUpdateRequest{
|
||||
Nonce: req.Nonce,
|
||||
Key: req.Key,
|
||||
})
|
||||
if err == nil && result == nil {
|
||||
return b.handleRekeyVerifyGet(ctx, recovery)
|
||||
}
|
||||
return nonLogicalResponse(result, code, err)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleGenerateRootDecodeTokenUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
encodedToken := data.Get("encoded_token").(string)
|
||||
otp := data.Get("otp").(string)
|
||||
|
|
|
|||
|
|
@ -769,7 +769,7 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
respFields := map[string]*framework.FieldSchema{
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
Required: false,
|
||||
},
|
||||
"started": {
|
||||
Type: framework.TypeBool,
|
||||
|
|
@ -785,7 +785,7 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
},
|
||||
"progress": {
|
||||
Type: framework.TypeInt,
|
||||
Required: true,
|
||||
Required: false,
|
||||
},
|
||||
"required": {
|
||||
Type: framework.TypeInt,
|
||||
|
|
@ -793,11 +793,11 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
},
|
||||
"verification_required": {
|
||||
Type: framework.TypeBool,
|
||||
Required: true,
|
||||
Required: false,
|
||||
},
|
||||
"verification_nonce": {
|
||||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
Required: false,
|
||||
},
|
||||
"backup": {
|
||||
Type: framework.TypeBool,
|
||||
|
|
@ -815,6 +815,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
OperationPrefix: "rekey-attempt",
|
||||
},
|
||||
|
||||
// Note that since we're a binary path we don't actually use these fields, they exist solely for the sake
|
||||
// of populating the openapi schema.
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"secret_shares": {
|
||||
Type: framework.TypeInt,
|
||||
|
|
@ -824,6 +826,10 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
Type: framework.TypeInt,
|
||||
Description: "Specifies the number of shares required to reconstruct the unseal key. This must be less than or equal secret_shares. If using Vault HSM with auto-unsealing, this value must be the same as secret_shares.",
|
||||
},
|
||||
"stored_shares": {
|
||||
Type: framework.TypeInt,
|
||||
Description: "Specifies the number of shares that should be encrypted by the HSM and stored for auto-unsealing. Currently must be the same as secret_shares.",
|
||||
},
|
||||
"pgp_keys": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Specifies an array of PGP public keys used to encrypt the output unseal keys. Ordering is preserved. The keys must be base64-encoded from their original binary representation. The size of this array must be the same as secret_shares.",
|
||||
|
|
@ -836,10 +842,16 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
Type: framework.TypeBool,
|
||||
Description: "Turns on verification functionality",
|
||||
},
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Specifies the nonce of the rekey operation. If the rekey was initialized within the last 10 minutes, you must provide the nonce to cancel the operation.",
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyInitBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "read",
|
||||
OperationSuffix: "progress",
|
||||
|
|
@ -853,6 +865,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
Summary: "Reads the configuration and progress of the current rekey attempt.",
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyInitBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "initialize",
|
||||
},
|
||||
|
|
@ -866,6 +880,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
Description: "Only a single rekey attempt can take place at a time, and changing the parameters of a rekey requires canceling and starting a new rekey, which will also provide a new nonce.",
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyInitBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "cancel",
|
||||
},
|
||||
|
|
@ -991,6 +1007,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
{
|
||||
Pattern: "rekey/update",
|
||||
|
||||
// Note that since we're a binary path we don't actually use these fields, they exist solely for the sake
|
||||
// of populating the openapi schema.
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"key": {
|
||||
Type: framework.TypeString,
|
||||
|
|
@ -1004,6 +1022,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyUpdateBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: "rekey-attempt",
|
||||
OperationVerb: "update",
|
||||
|
|
@ -1068,6 +1088,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
OperationPrefix: "rekey-verification",
|
||||
},
|
||||
|
||||
// Note that since we're a binary path we don't actually use these fields, they exist solely for the sake
|
||||
// of populating the openapi schema.
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"key": {
|
||||
Type: framework.TypeString,
|
||||
|
|
@ -1081,6 +1103,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyVerifyBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "read",
|
||||
OperationSuffix: "progress",
|
||||
|
|
@ -1115,6 +1139,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
Summary: "Read the configuration and progress of the current rekey verification attempt.",
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyVerifyBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "cancel",
|
||||
},
|
||||
|
|
@ -1149,6 +1175,8 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
Description: "This clears any progress made and resets the nonce. Unlike a `DELETE` against `sys/rekey/init`, this only resets the current verification operation, not the entire rekey atttempt.",
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyVerifyBarrier,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "update",
|
||||
},
|
||||
|
|
@ -1170,6 +1198,246 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Pattern: "rekey-recovery-key/init",
|
||||
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: "rekey-recovery-key-attempt",
|
||||
},
|
||||
|
||||
// Note that since we're a binary path we don't actually use these fields, they exist solely for the sake
|
||||
// of populating the openapi schema.
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"secret_shares": {
|
||||
Type: framework.TypeInt,
|
||||
Description: "Specifies the number of shares to split the recovery key into.",
|
||||
},
|
||||
"stored_shares": {
|
||||
Type: framework.TypeInt,
|
||||
Description: "Specifies the number of shares that should be encrypted by the HSM and stored for auto-unsealing. Currently must be the same as `secret_shares`.",
|
||||
},
|
||||
"secret_threshold": {
|
||||
Type: framework.TypeInt,
|
||||
Description: "Specifies the number of shares required to reconstruct the recovery key.",
|
||||
},
|
||||
"pgp_keys": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Specifies an array of PGP public keys used to encrypt the output recovery keys.",
|
||||
},
|
||||
"backup": {
|
||||
Type: framework.TypeBool,
|
||||
Description: "Specifies if using PGP-encrypted keys, whether Vault should also store a plaintext backup of the PGP-encrypted keys.",
|
||||
},
|
||||
"require_verification": {
|
||||
Type: framework.TypeBool,
|
||||
Description: "Turns on verification functionality",
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyInitRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "read",
|
||||
OperationSuffix: "progress",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
Fields: respFields,
|
||||
}},
|
||||
},
|
||||
Summary: "Reads the configuration and progress of the current recovery key rekey attempt.",
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyInitRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "initialize",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
Fields: respFields,
|
||||
}},
|
||||
},
|
||||
Summary: "Initializes a new recovery key rekey attempt.",
|
||||
Description: "Only a single recovery key rekey attempt can take place at a time.",
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyInitRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "cancel",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
}},
|
||||
},
|
||||
Summary: "Cancels any in-progress recovery key rekey.",
|
||||
Description: "This clears the recovery key rekey settings as well as any progress made.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Pattern: "rekey-recovery-key/update",
|
||||
|
||||
// Note that since we're a binary path we don't actually use these fields, they exist solely for the sake
|
||||
// of populating the openapi schema.
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"key": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Specifies a single recovery key share.",
|
||||
},
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Specifies the nonce of the rekey attempt.",
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyUpdateRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: "rekey-recovery-key-attempt",
|
||||
OperationVerb: "update",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"complete": {
|
||||
Type: framework.TypeBool,
|
||||
},
|
||||
"keys": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
},
|
||||
"keys_base64": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
},
|
||||
"verification_required": {
|
||||
Type: framework.TypeBool,
|
||||
Required: true,
|
||||
},
|
||||
"verification_nonce": {
|
||||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"backup": {
|
||||
Type: framework.TypeBool,
|
||||
},
|
||||
"pgp_fingerprints": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Summary: "Enter a single recovery key share to progress the rekey of the Vault.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Pattern: "rekey-recovery-key/verify",
|
||||
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: "rekey-recovery-key-verification",
|
||||
},
|
||||
|
||||
// Note that since we're a binary path we don't actually use these fields, they exist solely for the sake
|
||||
// of populating the openapi schema.
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"key": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Specifies a single recovery key share from the new set of shares.",
|
||||
},
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Specifies the nonce of the rekey verification operation.",
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyVerifyRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "read",
|
||||
OperationSuffix: "progress",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"started": {
|
||||
Type: framework.TypeBool,
|
||||
Required: true,
|
||||
},
|
||||
"t": {
|
||||
Type: framework.TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
"n": {
|
||||
Type: framework.TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
"progress": {
|
||||
Type: framework.TypeInt,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Summary: "Read the configuration and progress of the current recovery key rekey verification attempt.",
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyVerifyRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "cancel",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
}},
|
||||
},
|
||||
Summary: "Cancel any in-progress recovery key rekey verification operation.",
|
||||
Description: "This clears any progress made and resets the nonce.",
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleRekeyVerifyRecovery,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "update",
|
||||
},
|
||||
Responses: map[int][]framework.Response{
|
||||
http.StatusOK: {{
|
||||
Description: "OK",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"nonce": {
|
||||
Type: framework.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"complete": {
|
||||
Type: framework.TypeBool,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Summary: "Enter a single new recovery key share to progress the rekey verification operation.",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Pattern: "seal$",
|
||||
|
|
|
|||
|
|
@ -271,17 +271,17 @@ func (c *Core) reloadBackendCommon(ctx context.Context, entry *MountEntry, isAut
|
|||
paths := backend.SpecialPaths()
|
||||
if paths != nil {
|
||||
re.rootPaths.Store(pathsToRadix(paths.Root))
|
||||
loginPathsEntry, err := parseUnauthenticatedPaths(paths.Unauthenticated)
|
||||
loginPathsEntry, err := parseSpecialPaths(paths.Unauthenticated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re.loginPaths.Store(loginPathsEntry)
|
||||
binaryPathsEntry, err := parseUnauthenticatedPaths(paths.Binary)
|
||||
binaryPathsEntry, err := parseSpecialPaths(paths.Binary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re.binaryPaths.Store(binaryPathsEntry)
|
||||
allowSnapshotReadPathsEntry, err := parseUnauthenticatedPaths(paths.AllowSnapshotRead)
|
||||
allowSnapshotReadPathsEntry, err := parseSpecialPaths(paths.AllowSnapshotRead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,9 +63,11 @@ type RekeyBackup struct {
|
|||
// the recovery key threshold, depending on whether rekey is being
|
||||
// performed on the recovery key, or whether the seal supports
|
||||
// recovery keys.
|
||||
func (c *Core) RekeyThreshold(ctx context.Context, recovery bool) (int, logical.HTTPCodedError) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) RekeyThreshold(ctx context.Context, recovery bool, grabLock bool) (int, logical.HTTPCodedError) {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return 0, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -97,9 +99,11 @@ func (c *Core) RekeyThreshold(ctx context.Context, recovery bool) (int, logical.
|
|||
}
|
||||
|
||||
// RekeyProgress is used to return the rekey progress (num shares).
|
||||
func (c *Core) RekeyProgress(recovery, verification bool) (bool, int, logical.HTTPCodedError) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) RekeyProgress(recovery, verification, grabLock bool) (bool, int, logical.HTTPCodedError) {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return false, 0, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -128,9 +132,11 @@ func (c *Core) RekeyProgress(recovery, verification bool) (bool, int, logical.HT
|
|||
}
|
||||
|
||||
// RekeyConfig is used to read the rekey configuration
|
||||
func (c *Core) RekeyConfig(recovery bool) (*SealConfig, logical.HTTPCodedError) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) RekeyConfig(recovery bool, grabLock bool) (*SealConfig, logical.HTTPCodedError) {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -158,19 +164,19 @@ func (c *Core) RekeyConfig(recovery bool) (*SealConfig, logical.HTTPCodedError)
|
|||
|
||||
// RekeyInit will either initialize the rekey of barrier or recovery key.
|
||||
// recovery determines whether this is a rekey on the barrier or recovery key.
|
||||
func (c *Core) RekeyInit(config *SealConfig, recovery bool) logical.HTTPCodedError {
|
||||
func (c *Core) RekeyInit(config *SealConfig, recovery bool, grabLock bool) logical.HTTPCodedError {
|
||||
if config.SecretThreshold > config.SecretShares {
|
||||
return logical.CodedError(http.StatusBadRequest, "provided threshold greater than the total shares")
|
||||
}
|
||||
|
||||
if recovery {
|
||||
return c.RecoveryRekeyInit(config)
|
||||
return c.RecoveryRekeyInit(config, grabLock)
|
||||
}
|
||||
return c.BarrierRekeyInit(config)
|
||||
return c.BarrierRekeyInit(config, grabLock)
|
||||
}
|
||||
|
||||
// BarrierRekeyInit is used to initialize the rekey settings for the barrier key
|
||||
func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
||||
func (c *Core) BarrierRekeyInit(config *SealConfig, grabLock bool) logical.HTTPCodedError {
|
||||
switch c.seal.BarrierSealConfigType() {
|
||||
case SealConfigTypeShamir:
|
||||
// As of Vault 1.3 all seals use StoredShares==1. The one exception is
|
||||
|
|
@ -210,8 +216,10 @@ func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
|||
return logical.CodedError(http.StatusInternalServerError, fmt.Errorf("invalid rekey seal configuration: %w", err).Error())
|
||||
}
|
||||
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -239,14 +247,14 @@ func (c *Core) BarrierRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
|||
c.barrierRekeyConfig.Nonce = nonce
|
||||
c.barrierRekeyConfig.Created = time.Now().UTC()
|
||||
|
||||
if c.logger.IsInfo() {
|
||||
c.logger.Info("rekey initialized", "nonce", c.barrierRekeyConfig.Nonce, "shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold, "validation_required", c.barrierRekeyConfig.VerificationRequired)
|
||||
}
|
||||
c.logger.Info("rekey initialized", "nonce", c.barrierRekeyConfig.Nonce,
|
||||
"shares", c.barrierRekeyConfig.SecretShares, "threshold", c.barrierRekeyConfig.SecretThreshold,
|
||||
"validation_required", c.barrierRekeyConfig.VerificationRequired, "backup", c.barrierRekeyConfig.Backup)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecoveryRekeyInit is used to initialize the rekey settings for the recovery key
|
||||
func (c *Core) RecoveryRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
||||
func (c *Core) RecoveryRekeyInit(config *SealConfig, grabLock bool) logical.HTTPCodedError {
|
||||
if config.StoredShares > 0 {
|
||||
return logical.CodedError(http.StatusBadRequest, "stored shares not supported by recovery key")
|
||||
}
|
||||
|
|
@ -261,8 +269,10 @@ func (c *Core) RecoveryRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
|||
return logical.CodedError(http.StatusBadRequest, "recovery keys not supported")
|
||||
}
|
||||
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -297,11 +307,11 @@ func (c *Core) RecoveryRekeyInit(config *SealConfig) logical.HTTPCodedError {
|
|||
}
|
||||
|
||||
// RekeyUpdate is used to provide a new key part for the barrier or recovery key.
|
||||
func (c *Core) RekeyUpdate(ctx context.Context, key []byte, nonce string, recovery bool) (*RekeyResult, logical.HTTPCodedError) {
|
||||
func (c *Core) RekeyUpdate(ctx context.Context, key []byte, nonce string, recovery bool, grabLock bool) (*RekeyResult, logical.HTTPCodedError) {
|
||||
if recovery {
|
||||
return c.RecoveryRekeyUpdate(ctx, key, nonce)
|
||||
return c.RecoveryRekeyUpdate(ctx, key, nonce, grabLock)
|
||||
}
|
||||
return c.BarrierRekeyUpdate(ctx, key, nonce)
|
||||
return c.BarrierRekeyUpdate(ctx, key, nonce, grabLock)
|
||||
}
|
||||
|
||||
// BarrierRekeyUpdate is used to provide a new key part. Barrier rekey can be done
|
||||
|
|
@ -309,10 +319,12 @@ func (c *Core) RekeyUpdate(ctx context.Context, key []byte, nonce string, recove
|
|||
// key.
|
||||
//
|
||||
// N.B.: If recovery keys are used to rekey, the new barrier key shares are not returned.
|
||||
func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string) (*RekeyResult, logical.HTTPCodedError) {
|
||||
func (c *Core) BarrierRekeyUpdate(ctx context.Context, key []byte, nonce string, grabLock bool) (*RekeyResult, logical.HTTPCodedError) {
|
||||
// Ensure we are already unsealed
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -597,10 +609,12 @@ func (c *Core) performBarrierRekey(ctx context.Context, newSealKey []byte) logic
|
|||
}
|
||||
|
||||
// RecoveryRekeyUpdate is used to provide a new key part
|
||||
func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string) (*RekeyResult, logical.HTTPCodedError) {
|
||||
func (c *Core) RecoveryRekeyUpdate(ctx context.Context, key []byte, nonce string, grabLock bool) (*RekeyResult, logical.HTTPCodedError) {
|
||||
// Ensure we are already unsealed
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -798,10 +812,12 @@ func (c *Core) performRecoveryRekey(ctx context.Context, newRootKey []byte) logi
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Core) RekeyVerify(ctx context.Context, key []byte, nonce string, recovery bool) (ret *RekeyVerifyResult, retErr logical.HTTPCodedError) {
|
||||
func (c *Core) RekeyVerify(ctx context.Context, key []byte, nonce string, recovery bool, grabLock bool) (ret *RekeyVerifyResult, retErr logical.HTTPCodedError) {
|
||||
// Ensure we are already unsealed
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return nil, logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -913,9 +929,11 @@ func (c *Core) RekeyVerify(ctx context.Context, key []byte, nonce string, recove
|
|||
}
|
||||
|
||||
// RekeyCancel is used to cancel an in-progress rekey
|
||||
func (c *Core) RekeyCancel(recovery bool, nonce string, requiresNonceDeadline time.Duration) logical.HTTPCodedError {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) RekeyCancel(recovery bool, nonce string, requiresNonceDeadline time.Duration, grabLock bool) logical.HTTPCodedError {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
@ -952,9 +970,11 @@ func (c *Core) RekeyCancel(recovery bool, nonce string, requiresNonceDeadline ti
|
|||
}
|
||||
|
||||
// RekeyVerifyRestart is used to start the verification process over
|
||||
func (c *Core) RekeyVerifyRestart(recovery bool) logical.HTTPCodedError {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) RekeyVerifyRestart(recovery bool, grabLock bool) logical.HTTPCodedError {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() {
|
||||
return logical.CodedError(http.StatusServiceUnavailable, consts.ErrSealed.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func TestCore_Rekey_Lifecycle(t *testing.T) {
|
|||
func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
|
||||
min, _ := c.barrier.KeyLength()
|
||||
// Verify update not allowed
|
||||
_, err := c.RekeyUpdate(context.Background(), make([]byte, min), "", recovery)
|
||||
_, err := c.RekeyUpdate(context.Background(), make([]byte, min), "", recovery, true)
|
||||
expected := "no barrier rekey in progress"
|
||||
if recovery {
|
||||
expected = "no recovery rekey in progress"
|
||||
|
|
@ -46,12 +46,12 @@ func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
|
|||
}
|
||||
|
||||
// Should be no progress
|
||||
if _, _, err := c.RekeyProgress(recovery, false); err == nil {
|
||||
if _, _, err := c.RekeyProgress(recovery, false, true); err == nil {
|
||||
t.Fatal("expected error from RekeyProgress")
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.RekeyConfig(recovery)
|
||||
conf, err := c.RekeyConfig(recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
|
|||
}
|
||||
|
||||
// Cancel should be idempotent
|
||||
err = c.RekeyCancel(false, "", 10*time.Minute)
|
||||
err = c.RekeyCancel(false, "", 10*time.Minute, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -70,13 +70,13 @@ func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
|
|||
SecretThreshold: 3,
|
||||
SecretShares: 5,
|
||||
}
|
||||
err = c.RekeyInit(newConf, recovery)
|
||||
err = c.RekeyInit(newConf, recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should get config
|
||||
conf, err = c.RekeyConfig(recovery)
|
||||
conf, err = c.RekeyConfig(recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -86,13 +86,13 @@ func testCore_Rekey_Lifecycle_Common(t *testing.T, c *Core, recovery bool) {
|
|||
}
|
||||
|
||||
// Cancel should be clear
|
||||
err = c.RekeyCancel(recovery, conf.Nonce, 10*time.Minute)
|
||||
err = c.RekeyCancel(recovery, conf.Nonce, 10*time.Minute, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err = c.RekeyConfig(recovery)
|
||||
conf, err = c.RekeyConfig(recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
|
|||
SecretThreshold: 5,
|
||||
SecretShares: 1,
|
||||
}
|
||||
err := c.RekeyInit(badConf, recovery)
|
||||
err := c.RekeyInit(badConf, recovery, true)
|
||||
if err == nil {
|
||||
t.Fatalf("should fail")
|
||||
}
|
||||
|
|
@ -131,13 +131,13 @@ func testCore_Rekey_Init_Common(t *testing.T, c *Core, recovery bool) {
|
|||
newConf.Type = c.seal.RecoverySealConfigType().String()
|
||||
}
|
||||
|
||||
err = c.RekeyInit(newConf, recovery)
|
||||
err = c.RekeyInit(newConf, recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Second should fail
|
||||
err = c.RekeyInit(newConf, recovery)
|
||||
err = c.RekeyInit(newConf, recovery, true)
|
||||
if err == nil {
|
||||
t.Fatalf("should fail")
|
||||
}
|
||||
|
|
@ -171,13 +171,13 @@ func testCore_Rekey_Update_Common_Error(t *testing.T, c *Core, keys [][]byte, ro
|
|||
SecretThreshold: 3,
|
||||
SecretShares: 5,
|
||||
}
|
||||
hErr := c.RekeyInit(newConf, recovery)
|
||||
hErr := c.RekeyInit(newConf, recovery, true)
|
||||
if hErr != nil {
|
||||
t.Fatalf("err: %v", hErr)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, hErr := c.RekeyConfig(recovery)
|
||||
rkconf, hErr := c.RekeyConfig(recovery, true)
|
||||
if hErr != nil {
|
||||
t.Fatalf("err: %v", hErr)
|
||||
}
|
||||
|
|
@ -188,7 +188,7 @@ func testCore_Rekey_Update_Common_Error(t *testing.T, c *Core, keys [][]byte, ro
|
|||
// Provide the master/recovery keys
|
||||
var result *RekeyResult
|
||||
for _, key := range keys {
|
||||
result, err = c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery)
|
||||
result, err = c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery, true)
|
||||
if err != nil {
|
||||
if !wantRekeyUpdateError {
|
||||
t.Fatalf("err: %v", err)
|
||||
|
|
@ -208,12 +208,12 @@ func testCore_Rekey_Update_Common_Error(t *testing.T, c *Core, keys [][]byte, ro
|
|||
}
|
||||
|
||||
// Should be no progress
|
||||
if _, _, err := c.RekeyProgress(recovery, false); err == nil {
|
||||
if _, _, err := c.RekeyProgress(recovery, false, true); err == nil {
|
||||
t.Fatal("expected error from RekeyProgress")
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, hErr := c.RekeyConfig(recovery)
|
||||
conf, hErr := c.RekeyConfig(recovery, true)
|
||||
if hErr != nil {
|
||||
t.Fatalf("rekey config error: %v", hErr)
|
||||
}
|
||||
|
|
@ -271,13 +271,13 @@ func testCore_Rekey_Update_Common_Error(t *testing.T, c *Core, keys [][]byte, ro
|
|||
SecretThreshold: 1,
|
||||
SecretShares: 1,
|
||||
}
|
||||
err = c.RekeyInit(newConf, recovery)
|
||||
err = c.RekeyInit(newConf, recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err = c.RekeyConfig(recovery)
|
||||
rkconf, err = c.RekeyConfig(recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -288,14 +288,14 @@ func testCore_Rekey_Update_Common_Error(t *testing.T, c *Core, keys [][]byte, ro
|
|||
// Provide the parts master
|
||||
oldResult := result
|
||||
for i := 0; i < 3; i++ {
|
||||
result, err = c.RekeyUpdate(context.Background(), TestKeyCopy(oldResult.SecretShares[i]), rkconf.Nonce, recovery)
|
||||
result, err = c.RekeyUpdate(context.Background(), TestKeyCopy(oldResult.SecretShares[i]), rkconf.Nonce, recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should be progress
|
||||
if i < 2 {
|
||||
_, num, err := c.RekeyProgress(recovery, false)
|
||||
_, num, err := c.RekeyProgress(recovery, false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -367,13 +367,13 @@ func testCore_Rekey_Invalid_Common(t *testing.T, c *Core, keys [][]byte, recover
|
|||
SecretThreshold: 3,
|
||||
SecretShares: 5,
|
||||
}
|
||||
err := c.RekeyInit(newConf, recovery)
|
||||
err := c.RekeyInit(newConf, recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err := c.RekeyConfig(recovery)
|
||||
rkconf, err := c.RekeyConfig(recovery, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -382,7 +382,7 @@ func testCore_Rekey_Invalid_Common(t *testing.T, c *Core, keys [][]byte, recover
|
|||
}
|
||||
|
||||
// Provide the nonce (invalid)
|
||||
_, err = c.RekeyUpdate(context.Background(), keys[0], "abcd", recovery)
|
||||
_, err = c.RekeyUpdate(context.Background(), keys[0], "abcd", recovery, true)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
|
|
@ -392,13 +392,13 @@ func testCore_Rekey_Invalid_Common(t *testing.T, c *Core, keys [][]byte, recover
|
|||
oldkeystr := fmt.Sprintf("%#v", key)
|
||||
key[0]++
|
||||
newkeystr := fmt.Sprintf("%#v", key)
|
||||
ret, err := c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery)
|
||||
ret, err := c.RekeyUpdate(context.Background(), key, rkconf.Nonce, recovery, true)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, ret is %#v\noldkeystr: %s\nnewkeystr: %s", *ret, oldkeystr, newkeystr)
|
||||
}
|
||||
|
||||
// Check progress has been reset
|
||||
_, num, err := c.RekeyProgress(recovery, false)
|
||||
_, num, err := c.RekeyProgress(recovery, false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -466,12 +466,12 @@ func TestCore_Rekey_Standby(t *testing.T) {
|
|||
SecretShares: 1,
|
||||
SecretThreshold: 1,
|
||||
}
|
||||
err = core.RekeyInit(newConf, false)
|
||||
err = core.RekeyInit(newConf, false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err := core.RekeyConfig(false)
|
||||
rkconf, err := core.RekeyConfig(false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -480,7 +480,7 @@ func TestCore_Rekey_Standby(t *testing.T) {
|
|||
}
|
||||
var rekeyResult *RekeyResult
|
||||
for _, key := range keys {
|
||||
rekeyResult, err = core.RekeyUpdate(context.Background(), key, rkconf.Nonce, false)
|
||||
rekeyResult, err = core.RekeyUpdate(context.Background(), key, rkconf.Nonce, false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -499,12 +499,12 @@ func TestCore_Rekey_Standby(t *testing.T) {
|
|||
TestWaitActive(t, core2)
|
||||
|
||||
// Rekey the master key again
|
||||
err = core2.RekeyInit(newConf, false)
|
||||
err = core2.RekeyInit(newConf, false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err = core2.RekeyConfig(false)
|
||||
rkconf, err = core2.RekeyConfig(false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -513,7 +513,7 @@ func TestCore_Rekey_Standby(t *testing.T) {
|
|||
}
|
||||
var rekeyResult2 *RekeyResult
|
||||
for _, key := range rekeyResult.SecretShares {
|
||||
rekeyResult2, err = core2.RekeyUpdate(context.Background(), key, rkconf.Nonce, false)
|
||||
rekeyResult2, err = core2.RekeyUpdate(context.Background(), key, rkconf.Nonce, false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
@ -542,7 +542,7 @@ func TestSysRekey_Verification_Invalid(t *testing.T) {
|
|||
err := core.BarrierRekeyInit(&SealConfig{
|
||||
VerificationRequired: true,
|
||||
StoredShares: 1,
|
||||
})
|
||||
}, true)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
|
|
@ -599,11 +599,11 @@ func TestCancelRekey_Nonce(t *testing.T) {
|
|||
t.Skip(t, "recovery rekey not supported")
|
||||
}
|
||||
|
||||
err := c.RekeyInit(tc.config, tc.recovery)
|
||||
err := c.RekeyInit(tc.config, tc.recovery, true)
|
||||
require.NoError(t, err, "rekey init failed")
|
||||
|
||||
// try to cancel without the nonce
|
||||
err = c.RekeyCancel(tc.recovery, "", 10*time.Minute)
|
||||
err = c.RekeyCancel(tc.recovery, "", 10*time.Minute, true)
|
||||
require.Error(t, err, "cancel should have errored")
|
||||
|
||||
// retrieve the nonce
|
||||
|
|
@ -621,7 +621,7 @@ func TestCancelRekey_Nonce(t *testing.T) {
|
|||
require.NotEmpty(t, nonce, "nonce missing")
|
||||
|
||||
// cancel successfully
|
||||
err = c.RekeyCancel(tc.recovery, nonce, 10*time.Minute)
|
||||
err = c.RekeyCancel(tc.recovery, nonce, 10*time.Minute, true)
|
||||
require.NoError(t, err, "error on rekey cancel")
|
||||
})
|
||||
}
|
||||
|
|
@ -675,14 +675,14 @@ func TestCancelRekey_Regression(t *testing.T) {
|
|||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.RekeyCancel(tc.recovery, "", 10*time.Minute)
|
||||
c.RekeyCancel(tc.recovery, "", 10*time.Minute, true)
|
||||
}()
|
||||
}
|
||||
err := c.RekeyInit(tc.config, tc.recovery)
|
||||
err := c.RekeyInit(tc.config, tc.recovery, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
wg.Wait()
|
||||
happening, keys, err := c.RekeyProgress(tc.recovery, false)
|
||||
happening, keys, err := c.RekeyProgress(tc.recovery, false, true)
|
||||
require.NoError(t, err)
|
||||
require.True(t, happening)
|
||||
require.Equal(t, 0, keys)
|
||||
|
|
@ -731,14 +731,14 @@ func TestCancelRekey_AfterDeadline(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
t.Run(tc.config.Type, func(t *testing.T) {
|
||||
c := tc.core(t)
|
||||
err := c.RekeyInit(tc.config, tc.recovery)
|
||||
err := c.RekeyInit(tc.config, tc.recovery, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// ensure that that 10 ms have passed before we cancel
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// set the deadline to a microsecond, which means we won't need a
|
||||
// nonce to cancel the rekey
|
||||
err = c.RekeyCancel(tc.recovery, "", time.Microsecond)
|
||||
err = c.RekeyCancel(tc.recovery, "", time.Microsecond, true)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,25 +209,25 @@ func (r *Router) Mount(backend logical.Backend, prefix string, mountEntry *Mount
|
|||
}
|
||||
re.tainted.Store(mountEntry.Tainted)
|
||||
re.rootPaths.Store(pathsToRadix(paths.Root))
|
||||
loginPathsEntry, err := parseUnauthenticatedPaths(paths.Unauthenticated)
|
||||
loginPathsEntry, err := parseSpecialPaths(paths.Unauthenticated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re.loginPaths.Store(loginPathsEntry)
|
||||
|
||||
binaryPathsEntry, err := parseUnauthenticatedPaths(paths.Binary)
|
||||
binaryPathsEntry, err := parseSpecialPaths(paths.Binary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re.binaryPaths.Store(binaryPathsEntry)
|
||||
|
||||
limitedPathsEntry, err := parseUnauthenticatedPaths(paths.Limited)
|
||||
limitedPathsEntry, err := parseSpecialPaths(paths.Limited)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re.limitedPaths.Store(limitedPathsEntry)
|
||||
|
||||
allowSnapshotReadPathsEntry, err := parseUnauthenticatedPaths(paths.AllowSnapshotRead)
|
||||
allowSnapshotReadPathsEntry, err := parseSpecialPaths(paths.AllowSnapshotRead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1024,7 +1024,7 @@ func wildcardError(path, msg string) error {
|
|||
return fmt.Errorf("path %q: invalid use of wildcards %s", path, msg)
|
||||
}
|
||||
|
||||
func isValidUnauthenticatedPath(path string) (bool, error) {
|
||||
func isValidSpecialPath(path string) (bool, error) {
|
||||
switch {
|
||||
case strings.Count(path, "*") > 1:
|
||||
return false, wildcardError(path, "(multiple '*' is forbidden)")
|
||||
|
|
@ -1038,13 +1038,13 @@ func isValidUnauthenticatedPath(path string) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// parseUnauthenticatedPaths converts a list of special paths to a
|
||||
// parseSpecialPaths converts a list of special paths to a
|
||||
// specialPathsEntry
|
||||
func parseUnauthenticatedPaths(paths []string) (*specialPathsEntry, error) {
|
||||
func parseSpecialPaths(paths []string) (*specialPathsEntry, error) {
|
||||
var tempPaths []string
|
||||
tempWildcardPaths := make([]wildcardPath, 0)
|
||||
for _, path := range paths {
|
||||
if ok, err := isValidUnauthenticatedPath(path); !ok {
|
||||
if ok, err := isValidSpecialPath(path); !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@ func TestParseUnauthenticatedPaths(t *testing.T) {
|
|||
}
|
||||
allPaths := append(paths, wildcardPaths...)
|
||||
|
||||
p, err := parseUnauthenticatedPaths(allPaths)
|
||||
p, err := parseSpecialPaths(allPaths)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -629,7 +629,7 @@ func TestParseUnauthenticatedPaths_Error(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
_, err := parseUnauthenticatedPaths(tc.paths)
|
||||
_, err := parseSpecialPaths(tc.paths)
|
||||
if err == nil || err != nil && !strings.Contains(err.Error(), tc.err) {
|
||||
t.Fatalf("bad: path: %s expect: %v got %v", tc.paths, tc.err, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1173,6 +1173,7 @@ type TestClusterOptions struct {
|
|||
|
||||
// ABCDLoggerNames names the loggers according to our ABCD convention when generating 4 clusters
|
||||
ABCDLoggerNames bool
|
||||
DisableTLS bool
|
||||
}
|
||||
|
||||
type TestPluginConfig struct {
|
||||
|
|
@ -1374,6 +1375,10 @@ func NewTestCluster(t testing.TB, base *CoreConfig, opts *TestClusterOptions) *T
|
|||
})
|
||||
}
|
||||
|
||||
scheme := "https"
|
||||
if opts.DisableTLS {
|
||||
scheme = "http"
|
||||
}
|
||||
//
|
||||
// Listener setup
|
||||
//
|
||||
|
|
@ -1430,10 +1435,14 @@ func NewTestCluster(t testing.TB, base *CoreConfig, opts *TestClusterOptions) *T
|
|||
tlsConfigs = append(tlsConfigs, tlsConfig)
|
||||
lns := []*TestListener{
|
||||
{
|
||||
Listener: tls.NewListener(ln, tlsConfig),
|
||||
Address: ln.Addr().(*net.TCPAddr),
|
||||
Address: ln.Addr().(*net.TCPAddr),
|
||||
},
|
||||
}
|
||||
if opts.DisableTLS {
|
||||
lns[0].Listener = ln
|
||||
} else {
|
||||
lns[0].Listener = tls.NewListener(ln, tlsConfig)
|
||||
}
|
||||
listeners = append(listeners, lns)
|
||||
var handler http.Handler = http.NewServeMux()
|
||||
handlers = append(handlers, handler)
|
||||
|
|
@ -1461,8 +1470,8 @@ func NewTestCluster(t testing.TB, base *CoreConfig, opts *TestClusterOptions) *T
|
|||
audit.TypeSocket: audit.NewSocketBackend,
|
||||
audit.TypeSyslog: audit.NewSyslogBackend,
|
||||
},
|
||||
RedirectAddr: fmt.Sprintf("https://127.0.0.1:%d", listeners[0][0].Address.Port),
|
||||
ClusterAddr: "https://127.0.0.1:0",
|
||||
RedirectAddr: fmt.Sprintf(scheme+"://127.0.0.1:%d", listeners[0][0].Address.Port),
|
||||
ClusterAddr: scheme + "://127.0.0.1:0",
|
||||
DisableMlock: true,
|
||||
EnableUI: true,
|
||||
EnableRaw: true,
|
||||
|
|
@ -1559,6 +1568,7 @@ func NewTestCluster(t testing.TB, base *CoreConfig, opts *TestClusterOptions) *T
|
|||
coreConfig.PeriodicLeaderRefreshInterval = base.PeriodicLeaderRefreshInterval
|
||||
coreConfig.ClusterAddrBridge = base.ClusterAddrBridge
|
||||
coreConfig.ObservationSystemConfig = base.ObservationSystemConfig
|
||||
coreConfig.EnableUnauthenticatedAccess = base.EnableUnauthenticatedAccess
|
||||
|
||||
testApplyEntBaseConfig(coreConfig, base)
|
||||
}
|
||||
|
|
@ -1856,7 +1866,11 @@ func (testCluster *TestCluster) newCore(t testing.TB, idx int, coreConfig *CoreC
|
|||
firstCoreNumber = opts.FirstCoreNumber
|
||||
}
|
||||
|
||||
localConfig.RedirectAddr = fmt.Sprintf("https://127.0.0.1:%d", listeners[0].Address.Port)
|
||||
scheme := "https"
|
||||
if opts != nil && opts.DisableTLS {
|
||||
scheme = "http"
|
||||
}
|
||||
localConfig.RedirectAddr = fmt.Sprintf(scheme+"://127.0.0.1:%d", listeners[0].Address.Port)
|
||||
|
||||
// if opts.SealFunc is provided, use that to generate a seal for the config instead
|
||||
if opts != nil && opts.SealFunc != nil {
|
||||
|
|
@ -1920,10 +1934,10 @@ func (testCluster *TestCluster) newCore(t testing.TB, idx int, coreConfig *CoreC
|
|||
|
||||
if opts != nil && opts.ClusterLayers != nil {
|
||||
localConfig.ClusterNetworkLayer = opts.ClusterLayers.Layers()[idx]
|
||||
localConfig.ClusterAddr = "https://" + localConfig.ClusterNetworkLayer.Listeners()[0].Addr().String()
|
||||
localConfig.ClusterAddr = scheme + "://" + localConfig.ClusterNetworkLayer.Listeners()[0].Addr().String()
|
||||
}
|
||||
if opts != nil && opts.BaseClusterListenPort != 0 {
|
||||
localConfig.ClusterAddr = fmt.Sprintf("https://127.0.0.1:%d", opts.BaseClusterListenPort+idx)
|
||||
localConfig.ClusterAddr = fmt.Sprintf(scheme+"://127.0.0.1:%d", opts.BaseClusterListenPort+idx)
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
@ -2144,7 +2158,11 @@ func (testCluster *TestCluster) getAPIClient(
|
|||
port int, tlsConfig *tls.Config,
|
||||
) *api.Client {
|
||||
transport := cleanhttp.DefaultPooledTransport()
|
||||
transport.TLSClientConfig = tlsConfig.Clone()
|
||||
scheme := "http"
|
||||
if opts != nil && !opts.DisableTLS {
|
||||
scheme = "https"
|
||||
transport.TLSClientConfig = tlsConfig.Clone()
|
||||
}
|
||||
if err := http2.ConfigureTransport(transport); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -2159,7 +2177,7 @@ func (testCluster *TestCluster) getAPIClient(
|
|||
if config.Error != nil {
|
||||
t.Fatal(config.Error)
|
||||
}
|
||||
config.Address = fmt.Sprintf("https://127.0.0.1:%d", port)
|
||||
config.Address = fmt.Sprintf(scheme+"://127.0.0.1:%d", port)
|
||||
config.HttpClient = client
|
||||
config.MaxRetries = 0
|
||||
apiClient, err := api.NewClient(config)
|
||||
|
|
|
|||
Loading…
Reference in a new issue