// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package http import ( "encoding/base64" "encoding/hex" "errors" "fmt" "io" "net/http" "github.com/hashicorp/go-secure-stdlib/base62" "github.com/hashicorp/vault/vault" ) func handleSysGenerateRootAttempt(core *vault.Core, generateStrategy vault.GenerateRootStrategy) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": handleSysGenerateRootAttemptGet(core, w, r, "") case "POST", "PUT": handleSysGenerateRootAttemptPut(core, w, r, generateStrategy) case "DELETE": handleSysGenerateRootAttemptDelete(core, w, r) default: respondError(w, http.StatusMethodNotAllowed, nil) } }) } func handleSysGenerateRootAttemptGet(core *vault.Core, w http.ResponseWriter, r *http.Request, otp string) { ctx, cancel := core.GetContext() defer cancel() // Get the current seal configuration barrierConfig, err := core.SealAccess().BarrierConfig(ctx) if err != nil { respondError(w, http.StatusInternalServerError, err) return } if barrierConfig == nil { respondError(w, http.StatusBadRequest, fmt.Errorf("server is not yet initialized")) return } sealConfig := barrierConfig if core.SealAccess().RecoveryKeySupported() { sealConfig, err = core.SealAccess().RecoveryConfig(ctx) if err != nil { respondError(w, http.StatusInternalServerError, err) return } } // Get the generation configuration generationConfig, err := core.GenerateRootConfiguration() if err != nil { respondError(w, http.StatusInternalServerError, err) return } // Get the progress progress, err := core.GenerateRootProgress() if err != nil { respondError(w, http.StatusInternalServerError, err) return } var otpLength int if core.DisableSSCTokens() { otpLength = vault.TokenLength + vault.OldTokenPrefixLength } else { otpLength = vault.TokenLength + vault.TokenPrefixLength } // Format the status status := &GenerateRootStatusResponse{ Started: false, Progress: progress, Required: sealConfig.SecretThreshold, Complete: false, OTPLength: otpLength, OTP: otp, } if generationConfig != nil { status.Nonce = generationConfig.Nonce status.Started = true status.PGPFingerprint = generationConfig.PGPFingerprint } respondOk(w, status) } func handleSysGenerateRootAttemptPut(core *vault.Core, w http.ResponseWriter, r *http.Request, generateStrategy vault.GenerateRootStrategy) { // Parse the request var req GenerateRootInitRequest if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil && err != io.EOF { respondError(w, http.StatusBadRequest, err) return } var err error var genned bool switch { case len(req.PGPKey) > 0, len(req.OTP) > 0: default: genned = true if core.DisableSSCTokens() { req.OTP, err = base62.Random(vault.TokenLength + vault.OldTokenPrefixLength) } else { req.OTP, err = base62.Random(vault.TokenLength + vault.TokenPrefixLength) } if err != nil { respondError(w, http.StatusInternalServerError, err) return } } // Attemptialize the generation if err := core.GenerateRootInit(req.OTP, req.PGPKey, generateStrategy); err != nil { respondError(w, http.StatusBadRequest, err) return } if genned { handleSysGenerateRootAttemptGet(core, w, r, req.OTP) return } handleSysGenerateRootAttemptGet(core, w, r, "") } func handleSysGenerateRootAttemptDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) { err := core.GenerateRootCancel() if err != nil { respondError(w, http.StatusInternalServerError, err) return } respondOk(w, nil) } func handleSysGenerateRootUpdate(core *vault.Core, generateStrategy vault.GenerateRootStrategy) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Parse the request var req GenerateRootUpdateRequest 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 root generation result, err := core.GenerateRootUpdate(ctx, key, req.Nonce, generateStrategy) if err != nil { respondError(w, http.StatusBadRequest, err) return } resp := &GenerateRootStatusResponse{ Complete: result.Progress == result.Required, Nonce: req.Nonce, Progress: result.Progress, Required: result.Required, Started: true, EncodedToken: result.EncodedToken, PGPFingerprint: result.PGPFingerprint, } if generateStrategy == vault.GenerateStandardRootTokenStrategy { resp.EncodedRootToken = result.EncodedToken } respondOk(w, resp) }) } type GenerateRootInitRequest struct { OTP string `json:"otp"` PGPKey string `json:"pgp_key"` } type GenerateRootStatusResponse struct { Nonce string `json:"nonce"` Started bool `json:"started"` Progress int `json:"progress"` Required int `json:"required"` Complete bool `json:"complete"` EncodedToken string `json:"encoded_token"` EncodedRootToken string `json:"encoded_root_token"` PGPFingerprint string `json:"pgp_fingerprint"` OTP string `json:"otp"` OTPLength int `json:"otp_length"` } type GenerateRootUpdateRequest struct { Nonce string Key string }