mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 20:36:26 +02:00
Backport Make generate-root and generate-operation-token endpoints authenticated by default into ce/main (#13544)
Also allow root tokens to be used in DR requests. Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>
This commit is contained in:
parent
4901e602bd
commit
329dff0225
6
changelog/_12856.txt
Normal file
6
changelog/_12856.txt
Normal file
@ -0,0 +1,6 @@
|
||||
```release-note:change
|
||||
core: sys/generate-root and sys/replication/dr/secondary/generate-operation-token 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 "generate-root" or "generate-operation-token" respectively.
|
||||
```
|
||||
```release-note:change
|
||||
core: secondary DR requests can now be authenticated using a root token generated on the primary.
|
||||
```
|
||||
@ -4,6 +4,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
@ -83,8 +84,8 @@ func TestCustomResponseHeaders(t *testing.T) {
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseHeader(t, resp, customHeader200)
|
||||
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/update")
|
||||
testResponseStatus(t, resp, 400)
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/rekey/verify")
|
||||
testResponseStatus(t, resp, http.StatusBadRequest)
|
||||
testResponseHeader(t, resp, defaultCustomHeaders)
|
||||
testResponseHeader(t, resp, customHeader4xx)
|
||||
testResponseHeader(t, resp, customHeader400)
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
@ -234,22 +235,52 @@ func (h HandlerFunc) Handler(props *vault.HandlerProperties) http.Handler {
|
||||
|
||||
var _ vault.HandlerHandler = HandlerFunc(func(props *vault.HandlerProperties) http.Handler { return nil })
|
||||
|
||||
type handlerSettings struct {
|
||||
unauthRekey bool
|
||||
unauthGenerateRoot bool
|
||||
unauthDROperationToken bool
|
||||
}
|
||||
|
||||
func getHandlerSettings(core *vault.Core) handlerSettings {
|
||||
return handlerSettings{
|
||||
unauthRekey: core.GetEnableUnauthRekey(),
|
||||
unauthGenerateRoot: core.GetEnableUnauthGenerateRoot(),
|
||||
unauthDROperationToken: core.GetEnableUnauthDROperationToken(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
var l sync.RWMutex
|
||||
settings := getHandlerSettings(props.Core)
|
||||
hws := handlerWithSettings(props, settings)
|
||||
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {
|
||||
if props.Core.GetEnableUnauthRekey() {
|
||||
handlerUnauth.ServeHTTP(writer, req)
|
||||
} else {
|
||||
handlerAuth.ServeHTTP(writer, req)
|
||||
newSettings := getHandlerSettings(props.Core)
|
||||
|
||||
l.RLock()
|
||||
changed := settings != newSettings
|
||||
// Create a local copy so that we don't have to hold the lock while
|
||||
// running the handler
|
||||
myhws := hws
|
||||
l.RUnlock()
|
||||
|
||||
if changed {
|
||||
l.Lock()
|
||||
if settings != newSettings {
|
||||
settings = newSettings
|
||||
hws = handlerWithSettings(props, settings)
|
||||
myhws = hws
|
||||
}
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
myhws.ServeHTTP(writer, req)
|
||||
})
|
||||
}
|
||||
|
||||
func handlerWithUnauthRekey(props *vault.HandlerProperties, unauthRekey bool) http.Handler {
|
||||
func handlerWithSettings(props *vault.HandlerProperties, settings handlerSettings) http.Handler {
|
||||
core := props.Core
|
||||
|
||||
// Create the muxer to handle the actual endpoints
|
||||
@ -285,14 +316,19 @@ func handlerWithUnauthRekey(props *vault.HandlerProperties, unauthRekey bool) ht
|
||||
WithRedactClusterName(props.ListenerConfig.RedactClusterName),
|
||||
WithRedactVersion(props.ListenerConfig.RedactVersion)))
|
||||
mux.Handle("/v1/sys/monitor", handleLogicalNoForward(core, chrootNamespace))
|
||||
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core,
|
||||
handleAuditNonLogical(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy))))
|
||||
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core,
|
||||
handleAuditNonLogical(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy))))
|
||||
|
||||
// Register generate-root endpoints as unauthenticated handlers only if unauthGenerateRoot is true.
|
||||
// When false, these endpoints will be handled by the sys backend as authenticated endpoints.
|
||||
if settings.unauthGenerateRoot {
|
||||
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core,
|
||||
handleAuditNonLogical(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy))))
|
||||
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core,
|
||||
handleAuditNonLogical(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy))))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if settings.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)))
|
||||
@ -347,7 +383,9 @@ func handlerWithUnauthRekey(props *vault.HandlerProperties, unauthRekey bool) ht
|
||||
} else {
|
||||
mux.Handle("/v1/sys/in-flight-req", handleLogicalNoForward(core, chrootNamespace))
|
||||
}
|
||||
entAdditionalRoutes(mux, core)
|
||||
if settings.unauthDROperationToken {
|
||||
entDROperationRoutes(mux, core)
|
||||
}
|
||||
}
|
||||
|
||||
// Build up a chain of wrapping handlers.
|
||||
@ -421,10 +459,7 @@ func (w *copyResponseWriter) WriteHeader(code int) {
|
||||
|
||||
func handleAuditNonLogical(core *vault.Core, h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origBody := new(bytes.Buffer)
|
||||
reader := io.NopCloser(io.TeeReader(r.Body, origBody))
|
||||
r.Body = reader
|
||||
req, _, status, err := buildLogicalRequestNoAuth(core.PerfStandby(), core.RouterAccess(), w, r)
|
||||
req, origBody, status, err := buildLogicalRequestNoAuth(core.PerfStandby(), core.RouterAccess(), w, r)
|
||||
if err != nil || status != 0 {
|
||||
respondError(w, status, err)
|
||||
return
|
||||
|
||||
@ -4,14 +4,9 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/go-secure-stdlib/base62"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
@ -34,108 +29,36 @@ func handleSysGenerateRootAttemptGet(core *vault.Core, w http.ResponseWriter, r
|
||||
ctx, cancel := core.GetContext()
|
||||
defer cancel()
|
||||
|
||||
// Get the current seal configuration
|
||||
barrierConfig, err := core.SealAccess().BarrierConfig(ctx)
|
||||
status, code, err := vault.HandleSysGenerateRootAttemptGet(ctx, core, otp, true)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
respondError(w, code, 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
|
||||
var req vault.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)
|
||||
otp, code, err := vault.HandleSysGenerateRootAttemptPut(core, generateStrategy, &req, true)
|
||||
if err != nil {
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
|
||||
if genned {
|
||||
handleSysGenerateRootAttemptGet(core, w, r, req.OTP)
|
||||
return
|
||||
}
|
||||
|
||||
handleSysGenerateRootAttemptGet(core, w, r, "")
|
||||
handleSysGenerateRootAttemptGet(core, w, r, otp)
|
||||
}
|
||||
|
||||
func handleSysGenerateRootAttemptDelete(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||
err := core.GenerateRootCancel()
|
||||
code, err := vault.HandleSysGenerateRootAttemptDelete(core, true)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, err)
|
||||
respondError(w, code, err)
|
||||
return
|
||||
}
|
||||
respondOk(w, nil)
|
||||
@ -144,81 +67,21 @@ func handleSysGenerateRootAttemptDelete(core *vault.Core, w http.ResponseWriter,
|
||||
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
|
||||
var req vault.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)
|
||||
resp, code, err := vault.HandleSysGenerateRootUpdate(ctx, core, generateStrategy, &req, true)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, err)
|
||||
respondError(w, code, 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
|
||||
}
|
||||
|
||||
@ -26,90 +26,201 @@ import (
|
||||
|
||||
var tokenLength string = fmt.Sprintf("%d", vault.TokenLength+vault.TokenPrefixLength)
|
||||
|
||||
func TestSysGenerateRootAttempt_Status(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp, err := http.Get(addr + "/v1/sys/generate-root/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
// TestSysGenerateRoot_Status verifies the initial output of sys/generate-root/attempt,
|
||||
// prior to actually starting a root token generation.
|
||||
func TestSysGenerateRoot_Status(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableUnauthGenerateRoot bool
|
||||
useToken bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "default-unauthenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "default-authenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required-without-token",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "auth-required-with-token",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": false,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"nonce": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["otp"] = actual["otp"]
|
||||
if diff := deep.Equal(actual, expected); diff != nil {
|
||||
t.Fatal(diff)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf := &vault.CoreConfig{}
|
||||
if tc.enableUnauthGenerateRoot {
|
||||
conf.EnableUnauthenticatedAccess = []string{"generate-root"}
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cl := cluster.Cores[0].Client
|
||||
|
||||
if tc.useToken {
|
||||
cl.SetToken(cluster.RootToken)
|
||||
} else {
|
||||
cl.SetToken("")
|
||||
}
|
||||
|
||||
resp, err := cl.Logical().Read("sys/generate-root/attempt")
|
||||
|
||||
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,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"nonce": "",
|
||||
"otp": "",
|
||||
"otp_length": json.Number("28"),
|
||||
"pgp_fingerprint": "",
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysGenerateRootAttempt_Setup_OTP(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", nil)
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if actual["nonce"].(string) == "" {
|
||||
t.Fatalf("nonce was empty")
|
||||
}
|
||||
expected["nonce"] = actual["nonce"]
|
||||
expected["otp"] = actual["otp"]
|
||||
if diff := deep.Equal(actual, expected); diff != nil {
|
||||
t.Fatal(diff)
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableUnauthGenerateRoot bool
|
||||
useToken bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "authenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required-no-token",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: false,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt")
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf := &vault.CoreConfig{}
|
||||
if tc.enableUnauthGenerateRoot {
|
||||
conf.EnableUnauthenticatedAccess = []string{"generate-root"}
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
|
||||
DisableTLS: true,
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cl := cluster.Cores[0].Client
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"otp": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if actual["nonce"].(string) == "" {
|
||||
t.Fatalf("nonce was empty")
|
||||
}
|
||||
expected["nonce"] = actual["nonce"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
reqToken := ""
|
||||
if tc.useToken {
|
||||
reqToken = cl.Token()
|
||||
}
|
||||
addr := cl.Address()
|
||||
|
||||
resp := testHttpPut(t, reqToken, addr+"/v1/sys/generate-root/attempt", nil)
|
||||
|
||||
if tc.expectError {
|
||||
testResponseStatus(t, resp, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
|
||||
var actual map[string]interface{}
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
|
||||
if actual["nonce"].(string) == "" {
|
||||
t.Fatalf("nonce was empty")
|
||||
}
|
||||
expected["nonce"] = actual["nonce"]
|
||||
expected["otp"] = actual["otp"]
|
||||
|
||||
if diff := deep.Equal(actual, expected); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, reqToken, addr+"/v1/sys/generate-root/attempt")
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"otp": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if actual["nonce"].(string) == "" {
|
||||
t.Fatalf("nonce was empty")
|
||||
}
|
||||
expected["nonce"] = actual["nonce"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +233,7 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/generate-root/attempt")
|
||||
|
||||
@ -138,7 +249,7 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
|
||||
"otp": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if actual["nonce"].(string) == "" {
|
||||
t.Fatalf("nonce was empty")
|
||||
@ -149,64 +260,6 @@ func TestSysGenerateRootAttempt_Setup_PGP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", nil)
|
||||
|
||||
var actual map[string]interface{}
|
||||
expected := map[string]interface{}{
|
||||
"started": true,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if actual["nonce"].(string) == "" {
|
||||
t.Fatalf("nonce was empty")
|
||||
}
|
||||
expected["nonce"] = actual["nonce"]
|
||||
expected["otp"] = actual["otp"]
|
||||
if diff := deep.Equal(actual, expected); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
||||
resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt")
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp, err := http.Get(addr + "/v1/sys/generate-root/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"started": false,
|
||||
"progress": json.Number("0"),
|
||||
"required": json.Number("3"),
|
||||
"complete": false,
|
||||
"encoded_token": "",
|
||||
"encoded_root_token": "",
|
||||
"pgp_fingerprint": "",
|
||||
"nonce": "",
|
||||
"otp": "",
|
||||
"otp_length": json.Number(tokenLength),
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func enableNoopAudit(t *testing.T, token string, core *vault.Core) {
|
||||
t.Helper()
|
||||
auditReq := &logical.Request{
|
||||
@ -270,7 +323,7 @@ func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", nil)
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
|
||||
resp = testHttpDelete(t, token, addr+"/v1/sys/generate-root/attempt")
|
||||
testResponseStatus(t, resp, 204)
|
||||
@ -279,199 +332,295 @@ func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestSysGenerateRoot_Update_OTP(t *testing.T) {
|
||||
var records *[][]byte
|
||||
ln, addr, token, keys := testServerWithAudit(t, &records)
|
||||
defer ln.Close()
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableUnauthGenerateRoot bool
|
||||
useToken bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "authenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required-no-token",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: false,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{})
|
||||
var rootGenerationStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &rootGenerationStatus)
|
||||
otp := rootGenerationStatus["otp"].(string)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf := &vault.CoreConfig{}
|
||||
if tc.enableUnauthGenerateRoot {
|
||||
conf.EnableUnauthenticatedAccess = []string{"generate-root"}
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
|
||||
DisableTLS: true,
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cl := cluster.Cores[0].Client
|
||||
reqToken := ""
|
||||
if tc.useToken {
|
||||
reqToken = cl.Token()
|
||||
}
|
||||
addr := cl.Address()
|
||||
|
||||
var actual map[string]interface{}
|
||||
var expected map[string]interface{}
|
||||
for i, key := range keys {
|
||||
resp = testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(key),
|
||||
resp := testHttpPut(t, reqToken, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{})
|
||||
var rootGenerationStatus map[string]interface{}
|
||||
if tc.expectError {
|
||||
testResponseStatus(t, resp, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &rootGenerationStatus)
|
||||
otp := rootGenerationStatus["otp"].(string)
|
||||
|
||||
var actual map[string]interface{}
|
||||
var expected map[string]interface{}
|
||||
for i, key := range cluster.BarrierKeys {
|
||||
resp = testHttpPut(t, reqToken, addr+"/v1/sys/generate-root/update", map[string]interface{}{
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(key),
|
||||
})
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"complete": false,
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"progress": json.Number(fmt.Sprintf("%d", i+1)),
|
||||
"required": json.Number(fmt.Sprintf("%d", len(cluster.BarrierKeys))),
|
||||
"started": true,
|
||||
"pgp_fingerprint": "",
|
||||
"otp": "",
|
||||
"otp_length": json.Number("0"),
|
||||
}
|
||||
if i+1 == len(cluster.BarrierKeys) {
|
||||
expected["complete"] = true
|
||||
}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &actual)
|
||||
}
|
||||
|
||||
if actual["encoded_token"] == nil || actual["encoded_token"] == "" {
|
||||
t.Fatalf("no encoded token found in response")
|
||||
}
|
||||
if actual["encoded_root_token"] == nil || actual["encoded_root-token"] == "" {
|
||||
t.Fatalf("no encoded root token found in response")
|
||||
}
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
expected["encoded_root_token"] = actual["encoded_root_token"]
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
|
||||
tokenBytes, err := base64.RawStdEncoding.DecodeString(expected["encoded_token"].(string))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tokenBytes, err = xor.XORBytes(tokenBytes, []byte(otp))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newRootToken := string(tokenBytes)
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"id": newRootToken,
|
||||
"display_name": "root",
|
||||
"meta": interface{}(nil),
|
||||
"num_uses": json.Number("0"),
|
||||
"policies": []interface{}{"root"},
|
||||
"orphan": true,
|
||||
"creation_ttl": json.Number("0"),
|
||||
"ttl": json.Number("0"),
|
||||
"path": "auth/token/root",
|
||||
"explicit_max_ttl": json.Number("0"),
|
||||
"expire_time": nil,
|
||||
"entity_id": "",
|
||||
"type": "service",
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"]
|
||||
expected["accessor"] = actual["data"].(map[string]interface{})["accessor"]
|
||||
|
||||
if !reflect.DeepEqual(actual["data"], expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"])
|
||||
}
|
||||
})
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"complete": false,
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"progress": json.Number(fmt.Sprintf("%d", i+1)),
|
||||
"required": json.Number(fmt.Sprintf("%d", len(keys))),
|
||||
"started": true,
|
||||
"pgp_fingerprint": "",
|
||||
"otp": "",
|
||||
"otp_length": json.Number("0"),
|
||||
}
|
||||
if i+1 == len(keys) {
|
||||
expected["complete"] = true
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
}
|
||||
|
||||
if actual["encoded_token"] == nil || actual["encoded_token"] == "" {
|
||||
t.Fatalf("no encoded token found in response")
|
||||
}
|
||||
if actual["encoded_root_token"] == nil || actual["encoded_root-token"] == "" {
|
||||
t.Fatalf("no encoded root token found in response")
|
||||
}
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
expected["encoded_root_token"] = actual["encoded_root_token"]
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
|
||||
tokenBytes, err := base64.RawStdEncoding.DecodeString(expected["encoded_token"].(string))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tokenBytes, err = xor.XORBytes(tokenBytes, []byte(otp))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newRootToken := string(tokenBytes)
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"id": newRootToken,
|
||||
"display_name": "root",
|
||||
"meta": interface{}(nil),
|
||||
"num_uses": json.Number("0"),
|
||||
"policies": []interface{}{"root"},
|
||||
"orphan": true,
|
||||
"creation_ttl": json.Number("0"),
|
||||
"ttl": json.Number("0"),
|
||||
"path": "auth/token/root",
|
||||
"explicit_max_ttl": json.Number("0"),
|
||||
"expire_time": nil,
|
||||
"entity_id": "",
|
||||
"type": "service",
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"]
|
||||
expected["accessor"] = actual["data"].(map[string]interface{})["accessor"]
|
||||
|
||||
if !reflect.DeepEqual(actual["data"], expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"])
|
||||
}
|
||||
|
||||
for _, r := range *records {
|
||||
t.Log(string(r))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysGenerateRoot_Update_PGP(t *testing.T) {
|
||||
core, keys, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
// We need to get the nonce first before we update
|
||||
resp, err := http.Get(addr + "/v1/sys/generate-root/attempt")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
testCases := []struct {
|
||||
name string
|
||||
enableUnauthGenerateRoot bool
|
||||
useToken bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "authenticated",
|
||||
enableUnauthGenerateRoot: true,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "auth-required-no-token",
|
||||
enableUnauthGenerateRoot: false,
|
||||
useToken: false,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
var rootGenerationStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &rootGenerationStatus)
|
||||
|
||||
var actual map[string]interface{}
|
||||
var expected map[string]interface{}
|
||||
for i, key := range keys {
|
||||
resp = testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(key),
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
conf := &vault.CoreConfig{}
|
||||
if tc.enableUnauthGenerateRoot {
|
||||
conf.EnableUnauthenticatedAccess = []string{"generate-root"}
|
||||
}
|
||||
cluster := vault.NewTestCluster(t, conf, &vault.TestClusterOptions{
|
||||
DisableTLS: true,
|
||||
HandlerFunc: Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
cl := cluster.Cores[0].Client
|
||||
reqToken := ""
|
||||
if tc.useToken {
|
||||
reqToken = cl.Token()
|
||||
}
|
||||
addr := cl.Address()
|
||||
|
||||
resp := testHttpPut(t, reqToken, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{
|
||||
"pgp_key": pgpkeys.TestPubKey1,
|
||||
})
|
||||
if tc.expectError {
|
||||
testResponseStatus(t, resp, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
|
||||
// We need to get the nonce first before we update
|
||||
resp = testHttpGet(t, reqToken, addr+"/v1/sys/generate-root/attempt")
|
||||
var rootGenerationStatus map[string]interface{}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &rootGenerationStatus)
|
||||
|
||||
var actual map[string]interface{}
|
||||
var expected map[string]interface{}
|
||||
for i, key := range cluster.BarrierKeys {
|
||||
resp = testHttpPut(t, reqToken, addr+"/v1/sys/generate-root/update", map[string]interface{}{
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"key": hex.EncodeToString(key),
|
||||
})
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"complete": false,
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"progress": json.Number(fmt.Sprintf("%d", i+1)),
|
||||
"required": json.Number(fmt.Sprintf("%d", len(cluster.BarrierKeys))),
|
||||
"started": true,
|
||||
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
|
||||
"otp": "",
|
||||
"otp_length": json.Number("0"),
|
||||
}
|
||||
if i+1 == len(cluster.BarrierKeys) {
|
||||
expected["complete"] = true
|
||||
}
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &actual)
|
||||
}
|
||||
|
||||
if actual["encoded_token"] == nil || actual["encoded_token"] == "" {
|
||||
t.Fatalf("no encoded token found in response")
|
||||
}
|
||||
if actual["encoded_root_token"] == nil || actual["encoded_root-token"] == "" {
|
||||
t.Fatalf("no encoded root token found in response")
|
||||
}
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
expected["encoded_root_token"] = actual["encoded_root_token"]
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
|
||||
decodedTokenBuf, err := pgpkeys.DecryptBytes(actual["encoded_token"].(string), pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decodedTokenBuf == nil {
|
||||
t.Fatal("decoded root token buffer is nil")
|
||||
}
|
||||
|
||||
newRootToken := decodedTokenBuf.String()
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"id": newRootToken,
|
||||
"display_name": "root",
|
||||
"meta": interface{}(nil),
|
||||
"num_uses": json.Number("0"),
|
||||
"policies": []interface{}{"root"},
|
||||
"orphan": true,
|
||||
"creation_ttl": json.Number("0"),
|
||||
"ttl": json.Number("0"),
|
||||
"path": "auth/token/root",
|
||||
"explicit_max_ttl": json.Number("0"),
|
||||
"expire_time": nil,
|
||||
"entity_id": "",
|
||||
"type": "service",
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||
testResponseStatus(t, resp, http.StatusOK)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"]
|
||||
expected["accessor"] = actual["data"].(map[string]interface{})["accessor"]
|
||||
|
||||
if diff := deep.Equal(actual["data"], expected); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
})
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"complete": false,
|
||||
"nonce": rootGenerationStatus["nonce"].(string),
|
||||
"progress": json.Number(fmt.Sprintf("%d", i+1)),
|
||||
"required": json.Number(fmt.Sprintf("%d", len(keys))),
|
||||
"started": true,
|
||||
"pgp_fingerprint": "816938b8a29146fbe245dd29e7cbaf8e011db793",
|
||||
"otp": "",
|
||||
"otp_length": json.Number("0"),
|
||||
}
|
||||
if i+1 == len(keys) {
|
||||
expected["complete"] = true
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
}
|
||||
|
||||
if actual["encoded_token"] == nil || actual["encoded_token"] == "" {
|
||||
t.Fatalf("no encoded token found in response")
|
||||
}
|
||||
if actual["encoded_root_token"] == nil || actual["encoded_root-token"] == "" {
|
||||
t.Fatalf("no encoded root token found in response")
|
||||
}
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
expected["encoded_root_token"] = actual["encoded_root_token"]
|
||||
expected["encoded_token"] = actual["encoded_token"]
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual)
|
||||
}
|
||||
|
||||
decodedTokenBuf, err := pgpkeys.DecryptBytes(actual["encoded_token"].(string), pgpkeys.TestPrivKey1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decodedTokenBuf == nil {
|
||||
t.Fatal("decoded root token buffer is nil")
|
||||
}
|
||||
|
||||
newRootToken := decodedTokenBuf.String()
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"id": newRootToken,
|
||||
"display_name": "root",
|
||||
"meta": interface{}(nil),
|
||||
"num_uses": json.Number("0"),
|
||||
"policies": []interface{}{"root"},
|
||||
"orphan": true,
|
||||
"creation_ttl": json.Number("0"),
|
||||
"ttl": json.Number("0"),
|
||||
"path": "auth/token/root",
|
||||
"explicit_max_ttl": json.Number("0"),
|
||||
"expire_time": nil,
|
||||
"entity_id": "",
|
||||
"type": "service",
|
||||
}
|
||||
|
||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
|
||||
expected["creation_time"] = actual["data"].(map[string]interface{})["creation_time"]
|
||||
expected["accessor"] = actual["data"].(map[string]interface{})["accessor"]
|
||||
|
||||
if diff := deep.Equal(actual["data"], expected); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ func entWrapGenericHandler(core *vault.Core, in http.Handler, props *vault.Handl
|
||||
return wrapGenericHandler(core, in, props)
|
||||
}
|
||||
|
||||
func entAdditionalRoutes(mux *http.ServeMux, core *vault.Core) {}
|
||||
func entDROperationRoutes(mux *http.ServeMux, core *vault.Core) {}
|
||||
|
||||
func entAdjustResponse(core *vault.Core, w http.ResponseWriter, req *logical.Request) {
|
||||
}
|
||||
|
||||
@ -279,6 +279,16 @@ type Core struct {
|
||||
// endpoints (false, default).
|
||||
enableUnauthRekey *atomic.Bool
|
||||
|
||||
// enableUnauthGenerateRoot controls whether generate-root endpoints are registered as
|
||||
// unauthenticated endpoints (true) or as authenticated sys backend
|
||||
// endpoints (false, default).
|
||||
enableUnauthGenerateRoot *atomic.Bool
|
||||
|
||||
// enableUnauthDROperationToken controls whether DR operation token endpoints are registered as
|
||||
// unauthenticated endpoints (true) or as authenticated sys backend
|
||||
// endpoints (false, default).
|
||||
enableUnauthDROperationToken *atomic.Bool
|
||||
|
||||
// HABackend may be available depending on the physical backend
|
||||
ha physical.HABackend
|
||||
|
||||
@ -1171,6 +1181,8 @@ func CreateCore(conf *CoreConfig) (*Core, error) {
|
||||
rpcLastSuccessfulHeartbeat: new(atomic.Value),
|
||||
reportingScanDirectory: conf.ReportingScanDirectory,
|
||||
enableUnauthRekey: new(atomic.Bool),
|
||||
enableUnauthGenerateRoot: new(atomic.Bool),
|
||||
enableUnauthDROperationToken: new(atomic.Bool),
|
||||
}
|
||||
|
||||
c.certCountManager = cert_count.InitCertificateCountManager(c.logger)
|
||||
@ -1454,11 +1466,15 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
||||
c.clusterAddrBridge = conf.ClusterAddrBridge
|
||||
c.licenseReloadCh = conf.LicenseReload
|
||||
|
||||
// Check if "rekey" is in the EnableUnauthenticatedAccess list
|
||||
// Check if endpoints are in the EnableUnauthenticatedAccess list
|
||||
for _, endpoint := range conf.EnableUnauthenticatedAccess {
|
||||
if endpoint == "rekey" {
|
||||
switch endpoint {
|
||||
case "rekey":
|
||||
c.enableUnauthRekey.Store(true)
|
||||
break
|
||||
case "generate-root":
|
||||
c.enableUnauthGenerateRoot.Store(true)
|
||||
case "generate-operation-token":
|
||||
c.enableUnauthDROperationToken.Store(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4465,16 +4481,24 @@ func (c *Core) ReloadEnableUnauthenticatedAccess() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if "rekey" is in the EnableUnauthenticatedAccess list
|
||||
// Check which endpoints are in the EnableUnauthenticatedAccess list
|
||||
enableRekey := false
|
||||
enableGenerateRoot := false
|
||||
enableDROperationToken := false
|
||||
for _, endpoint := range conf.(*server.Config).EnableUnauthenticatedAccess {
|
||||
if endpoint == "rekey" {
|
||||
switch endpoint {
|
||||
case "rekey":
|
||||
enableRekey = true
|
||||
break
|
||||
case "generate-root":
|
||||
enableGenerateRoot = true
|
||||
case "generate-operation-token":
|
||||
enableDROperationToken = true
|
||||
}
|
||||
}
|
||||
|
||||
c.enableUnauthRekey.Store(enableRekey)
|
||||
c.enableUnauthGenerateRoot.Store(enableGenerateRoot)
|
||||
c.enableUnauthDROperationToken.Store(enableDROperationToken)
|
||||
}
|
||||
|
||||
type PeerNode struct {
|
||||
@ -4969,3 +4993,19 @@ func (c *Core) GetEnableUnauthRekey() bool {
|
||||
func (c *Core) SetEnableUnauthRekey(val bool) {
|
||||
c.enableUnauthRekey.Store(val)
|
||||
}
|
||||
|
||||
func (c *Core) GetEnableUnauthGenerateRoot() bool {
|
||||
return c.enableUnauthGenerateRoot.Load()
|
||||
}
|
||||
|
||||
func (c *Core) SetEnableUnauthGenerateRoot(val bool) {
|
||||
c.enableUnauthGenerateRoot.Store(val)
|
||||
}
|
||||
|
||||
func (c *Core) GetEnableUnauthDROperationToken() bool {
|
||||
return c.enableUnauthDROperationToken.Load()
|
||||
}
|
||||
|
||||
func (c *Core) SetEnableUnauthDROperationToken(val bool) {
|
||||
c.enableUnauthDROperationToken.Store(val)
|
||||
}
|
||||
|
||||
@ -405,6 +405,7 @@ func TestRaft_LogStore_Migration_Snapshot(t *testing.T) {
|
||||
|
||||
// caching the old cluster's barrier keys
|
||||
oldBarrierKeys := cluster.GetBarrierKeys()
|
||||
oldRootToken := cluster.GetRootToken()
|
||||
// clean up the old cluster as there is no further use to it
|
||||
cluster.Cleanup()
|
||||
|
||||
@ -432,6 +433,7 @@ func TestRaft_LogStore_Migration_Snapshot(t *testing.T) {
|
||||
testcluster.WaitForActiveNode(ctx, newCluster)
|
||||
|
||||
// generate a root token as the unseal keys have changed
|
||||
newCluster.SetRootToken(oldRootToken)
|
||||
rootToken, err := testcluster.GenerateRoot(newCluster, testcluster.GenerateRootRegular)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@ -7,9 +7,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/go-secure-stdlib/base62"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/pgpkeys"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
@ -90,9 +93,11 @@ type GenerateRootResult struct {
|
||||
}
|
||||
|
||||
// GenerateRootProgress is used to return the root generation progress (num shares)
|
||||
func (c *Core) GenerateRootProgress() (int, error) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) GenerateRootProgress(grabLock bool) (int, error) {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() && !c.recoveryMode {
|
||||
return 0, consts.ErrSealed
|
||||
}
|
||||
@ -108,9 +113,11 @@ func (c *Core) GenerateRootProgress() (int, error) {
|
||||
|
||||
// GenerateRootConfiguration is used to read the root generation configuration
|
||||
// It stubbornly refuses to return the OTP if one is there.
|
||||
func (c *Core) GenerateRootConfiguration() (*GenerateRootConfig, error) {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) GenerateRootConfiguration(grabLock bool) (*GenerateRootConfig, error) {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() && !c.recoveryMode {
|
||||
return nil, consts.ErrSealed
|
||||
}
|
||||
@ -133,7 +140,7 @@ func (c *Core) GenerateRootConfiguration() (*GenerateRootConfig, error) {
|
||||
}
|
||||
|
||||
// GenerateRootInit is used to initialize the root generation settings
|
||||
func (c *Core) GenerateRootInit(otp, pgpKey string, strategy GenerateRootStrategy) error {
|
||||
func (c *Core) GenerateRootInit(otp, pgpKey string, strategy GenerateRootStrategy, grabLock bool) error {
|
||||
var fingerprint string
|
||||
switch {
|
||||
case len(otp) > 0:
|
||||
@ -156,8 +163,10 @@ func (c *Core) GenerateRootInit(otp, pgpKey string, strategy GenerateRootStrateg
|
||||
return fmt.Errorf("otp or pgp_key parameter must be provided")
|
||||
}
|
||||
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() && !c.recoveryMode {
|
||||
return consts.ErrSealed
|
||||
}
|
||||
@ -209,7 +218,7 @@ func (c *Core) GenerateRootInit(otp, pgpKey string, strategy GenerateRootStrateg
|
||||
}
|
||||
|
||||
// GenerateRootUpdate is used to provide a new key part
|
||||
func (c *Core) GenerateRootUpdate(ctx context.Context, key []byte, nonce string, strategy GenerateRootStrategy) (*GenerateRootResult, error) {
|
||||
func (c *Core) GenerateRootUpdate(ctx context.Context, key []byte, nonce string, strategy GenerateRootStrategy, grabLock bool) (*GenerateRootResult, error) {
|
||||
// Verify the key length
|
||||
min, max := c.barrier.KeyLength()
|
||||
max += shamir.ShareOverhead
|
||||
@ -241,8 +250,10 @@ func (c *Core) GenerateRootUpdate(ctx context.Context, key []byte, nonce string,
|
||||
}
|
||||
|
||||
// Ensure we are already unsealed
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() && !c.recoveryMode {
|
||||
return nil, consts.ErrSealed
|
||||
}
|
||||
@ -362,9 +373,11 @@ func (c *Core) GenerateRootUpdate(ctx context.Context, key []byte, nonce string,
|
||||
}
|
||||
|
||||
// GenerateRootCancel is used to cancel an in-progress root generation
|
||||
func (c *Core) GenerateRootCancel() error {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
func (c *Core) GenerateRootCancel(grabLock bool) error {
|
||||
if grabLock {
|
||||
c.stateLock.RLock()
|
||||
defer c.stateLock.RUnlock()
|
||||
}
|
||||
if c.Sealed() && !c.recoveryMode {
|
||||
return consts.ErrSealed
|
||||
}
|
||||
@ -380,3 +393,176 @@ func (c *Core) GenerateRootCancel() error {
|
||||
c.generateRootProgress = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateRootStatusResponse is the response structure for generate root status
|
||||
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"`
|
||||
}
|
||||
|
||||
// GenerateRootInitRequest is the request structure for initializing generate root
|
||||
type GenerateRootInitRequest struct {
|
||||
OTP string `json:"otp"`
|
||||
PGPKey string `json:"pgp_key"`
|
||||
}
|
||||
|
||||
// GenerateRootUpdateRequest is the request structure for updating generate root
|
||||
type GenerateRootUpdateRequest struct {
|
||||
Nonce string `json:"nonce"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// HandleSysGenerateRootAttemptGet returns the status of a generate root attempt.
|
||||
// In the event of an error, we return both the error and the HTTP status code to
|
||||
// return that error with.
|
||||
func HandleSysGenerateRootAttemptGet(ctx context.Context, core *Core, otp string, grabLock bool) (*GenerateRootStatusResponse, int, error) {
|
||||
// Get the current seal configuration
|
||||
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")
|
||||
}
|
||||
|
||||
sealConfig := barrierConfig
|
||||
if core.SealAccess().RecoveryKeySupported() {
|
||||
sealConfig, err = core.SealAccess().RecoveryConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the generation configuration
|
||||
generationConfig, err := core.GenerateRootConfiguration(grabLock)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Get the progress
|
||||
progress, err := core.GenerateRootProgress(grabLock)
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
var otpLength int
|
||||
if core.DisableSSCTokens() {
|
||||
otpLength = TokenLength + OldTokenPrefixLength
|
||||
} else {
|
||||
otpLength = TokenLength + 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
|
||||
}
|
||||
|
||||
return status, 0, nil
|
||||
}
|
||||
|
||||
// HandleSysGenerateRootAttemptPut initializes a new generate root attempt.
|
||||
// In the event of an error, we return both the error and the HTTP status code to
|
||||
// return that error with.
|
||||
func HandleSysGenerateRootAttemptPut(core *Core, strategy GenerateRootStrategy, req *GenerateRootInitRequest, grabLock bool) (string, int, error) {
|
||||
var err error
|
||||
var genned bool
|
||||
otp := req.OTP
|
||||
|
||||
switch {
|
||||
case len(req.PGPKey) > 0, len(req.OTP) > 0:
|
||||
default:
|
||||
genned = true
|
||||
if core.DisableSSCTokens() {
|
||||
otp, err = base62.Random(TokenLength + OldTokenPrefixLength)
|
||||
} else {
|
||||
otp, err = base62.Random(TokenLength + TokenPrefixLength)
|
||||
}
|
||||
if err != nil {
|
||||
return "", http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the generation
|
||||
if err := core.GenerateRootInit(otp, req.PGPKey, strategy, grabLock); err != nil {
|
||||
return "", http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
if genned {
|
||||
return otp, 0, nil
|
||||
}
|
||||
|
||||
return "", 0, nil
|
||||
}
|
||||
|
||||
// HandleSysGenerateRootAttemptDelete cancels any in-progress generate root attempt.
|
||||
// In the event of an error, we return both the error and the HTTP status code to
|
||||
// return that error with.
|
||||
func HandleSysGenerateRootAttemptDelete(core *Core, grabLock bool) (int, error) {
|
||||
err := core.GenerateRootCancel(grabLock)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// HandleSysGenerateRootUpdate processes a key share for generate root.
|
||||
// In the event of an error, we return both the error and the HTTP status code to
|
||||
// return that error with.
|
||||
func HandleSysGenerateRootUpdate(ctx context.Context, core *Core, strategy GenerateRootStrategy, req *GenerateRootUpdateRequest, grabLock bool) (*GenerateRootStatusResponse, int, error) {
|
||||
if req.Key == "" {
|
||||
return nil, http.StatusBadRequest, fmt.Errorf("'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, fmt.Errorf("'key' must be a valid hex or base64 string")
|
||||
}
|
||||
}
|
||||
|
||||
// Use the key to make progress on root generation
|
||||
result, err := core.GenerateRootUpdate(ctx, key, req.Nonce, strategy, grabLock)
|
||||
if err != nil {
|
||||
return nil, http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
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 strategy == GenerateStandardRootTokenStrategy {
|
||||
resp.EncodedRootToken = result.EncodedToken
|
||||
}
|
||||
|
||||
return resp, 0, nil
|
||||
}
|
||||
|
||||
@ -20,12 +20,12 @@ func TestCore_GenerateRoot_Lifecycle(t *testing.T) {
|
||||
|
||||
func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte) {
|
||||
// Verify update not allowed
|
||||
if _, err := c.GenerateRootUpdate(namespace.RootContext(nil), keys[0], "", GenerateStandardRootTokenStrategy); err == nil {
|
||||
if _, err := c.GenerateRootUpdate(namespace.RootContext(nil), keys[0], "", GenerateStandardRootTokenStrategy, true); err == nil {
|
||||
t.Fatalf("no root generation in progress")
|
||||
}
|
||||
|
||||
// Should be no progress
|
||||
num, err := c.GenerateRootProgress()
|
||||
num, err := c.GenerateRootProgress(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -34,7 +34,7 @@ func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.GenerateRootConfiguration()
|
||||
conf, err := c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -43,7 +43,7 @@ func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte
|
||||
}
|
||||
|
||||
// Cancel should be idempotent
|
||||
err = c.GenerateRootCancel()
|
||||
err = c.GenerateRootCancel(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -54,25 +54,25 @@ func testCore_GenerateRoot_Lifecycle_Common(t *testing.T, c *Core, keys [][]byte
|
||||
}
|
||||
|
||||
// Start a root generation
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should get config
|
||||
conf, err = c.GenerateRootConfiguration()
|
||||
conf, err = c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Cancel should be clear
|
||||
err = c.GenerateRootCancel()
|
||||
err = c.GenerateRootCancel(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err = c.GenerateRootConfiguration()
|
||||
conf, err = c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -97,13 +97,13 @@ func testCore_GenerateRoot_Init_Common(t *testing.T, c *Core) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Second should fail
|
||||
err = c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy)
|
||||
err = c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy, true)
|
||||
if err == nil {
|
||||
t.Fatalf("should fail")
|
||||
}
|
||||
@ -122,13 +122,13 @@ func testCore_GenerateRoot_InvalidMasterNonce_Common(t *testing.T, c *Core, keys
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rgconf, err := c.GenerateRootConfiguration()
|
||||
rgconf, err := c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -137,14 +137,14 @@ func testCore_GenerateRoot_InvalidMasterNonce_Common(t *testing.T, c *Core, keys
|
||||
}
|
||||
|
||||
// Provide the nonce (invalid)
|
||||
_, err = c.GenerateRootUpdate(namespace.RootContext(nil), keys[0], "abcd", GenerateStandardRootTokenStrategy)
|
||||
_, err = c.GenerateRootUpdate(namespace.RootContext(nil), keys[0], "abcd", GenerateStandardRootTokenStrategy, true)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
|
||||
// Provide the master (invalid)
|
||||
for _, key := range keys {
|
||||
_, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rgconf.Nonce, GenerateStandardRootTokenStrategy)
|
||||
_, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rgconf.Nonce, GenerateStandardRootTokenStrategy, true)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
@ -163,13 +163,13 @@ func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
}
|
||||
|
||||
// Start a root generation
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy)
|
||||
err = c.GenerateRootInit(otp, "", GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err := c.GenerateRootConfiguration()
|
||||
rkconf, err := c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -180,7 +180,7 @@ func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
// Provide the keys
|
||||
var result *GenerateRootResult
|
||||
for _, key := range keys {
|
||||
result, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rkconf.Nonce, GenerateStandardRootTokenStrategy)
|
||||
result, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rkconf.Nonce, GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -195,7 +195,7 @@ func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
encodedToken := result.EncodedToken
|
||||
|
||||
// Should be no progress
|
||||
num, err := c.GenerateRootProgress()
|
||||
num, err := c.GenerateRootProgress(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -204,7 +204,7 @@ func testCore_GenerateRoot_Update_OTP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.GenerateRootConfiguration()
|
||||
conf, err := c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -245,13 +245,13 @@ func TestCore_GenerateRoot_Update_PGP(t *testing.T) {
|
||||
|
||||
func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byte) {
|
||||
// Start a root generation
|
||||
err := c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy)
|
||||
err := c.GenerateRootInit("", pgpkeys.TestPubKey1, GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Fetch new config with generated nonce
|
||||
rkconf, err := c.GenerateRootConfiguration()
|
||||
rkconf, err := c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -262,7 +262,7 @@ func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
// Provide the keys
|
||||
var result *GenerateRootResult
|
||||
for _, key := range keys {
|
||||
result, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rkconf.Nonce, GenerateStandardRootTokenStrategy)
|
||||
result, err = c.GenerateRootUpdate(namespace.RootContext(nil), key, rkconf.Nonce, GenerateStandardRootTokenStrategy, true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -277,7 +277,7 @@ func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
encodedToken := result.EncodedToken
|
||||
|
||||
// Should be no progress
|
||||
num, err := c.GenerateRootProgress()
|
||||
num, err := c.GenerateRootProgress(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
@ -286,7 +286,7 @@ func testCore_GenerateRoot_Update_PGP_Common(t *testing.T, c *Core, keys [][]byt
|
||||
}
|
||||
|
||||
// Should be no config
|
||||
conf, err := c.GenerateRootConfiguration()
|
||||
conf, err := c.GenerateRootConfiguration(true)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
@ -109,8 +109,7 @@ 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).
|
||||
// Build the unauthenticated paths list.
|
||||
unauthenticatedPaths := []string{
|
||||
"wrapping/lookup",
|
||||
"wrapping/pubkey",
|
||||
@ -140,16 +139,14 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
|
||||
"unseal",
|
||||
"leader",
|
||||
"health",
|
||||
"generate-root/attempt",
|
||||
"generate-root/update",
|
||||
"decode-token",
|
||||
"mfa/validate",
|
||||
}
|
||||
|
||||
// Note that while rekeyPaths are not part of unauthenticatedPaths, that's
|
||||
// Note that while rekeyPaths and generateRootPaths 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
|
||||
// only when Core.enableUnauthRekey or Core.enableUnauthGenerateRoot is true, and being more specific paths
|
||||
// than the v1/sys mux path they take precedence when enabled.
|
||||
rekeyPaths := []string{
|
||||
"rekey/init",
|
||||
@ -160,6 +157,11 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
|
||||
"rekey-recovery-key/verify",
|
||||
}
|
||||
|
||||
generateRootPaths := []string{
|
||||
"generate-root/attempt",
|
||||
"generate-root/update",
|
||||
}
|
||||
|
||||
b.Backend = &framework.Backend{
|
||||
RunningVersion: versions.DefaultBuiltinVersion,
|
||||
Help: strings.TrimSpace(sysHelpRoot),
|
||||
@ -218,7 +220,7 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
|
||||
managedKeyRegistrySubPath,
|
||||
},
|
||||
|
||||
Binary: rekeyPaths,
|
||||
Binary: append(append(rekeyPaths, generateRootPaths...), entBinaryPaths()...),
|
||||
},
|
||||
}
|
||||
b.Backend.PathsSpecial.Unauthenticated = append(b.Backend.PathsSpecial.Unauthenticated, entUnauthenticatedPaths()...)
|
||||
@ -1788,6 +1790,62 @@ func (b *SystemBackend) handleRekeyUpdateRecovery(ctx context.Context, req *logi
|
||||
return b.handleRekeyUpdate(ctx, true)
|
||||
}
|
||||
|
||||
// handleGenerateRootAttempt handles the generate-root/attempt endpoint
|
||||
func (b *SystemBackend) handleGenerateRootAttempt(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
switch req.Operation {
|
||||
case logical.ReadOperation:
|
||||
return b.handleGenerateRootAttemptGet(ctx)
|
||||
case logical.UpdateOperation:
|
||||
return b.handleGenerateRootAttemptPut(ctx)
|
||||
case logical.DeleteOperation:
|
||||
return b.handleGenerateRootAttemptDelete(ctx)
|
||||
default:
|
||||
return nil, logical.ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleGenerateRootAttemptGet(ctx context.Context) (*logical.Response, error) {
|
||||
status, code, err := HandleSysGenerateRootAttemptGet(ctx, b.Core, "", false)
|
||||
return nonLogicalResponse(status, code, err)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleGenerateRootAttemptPut(ctx context.Context) (*logical.Response, error) {
|
||||
var req GenerateRootInitRequest
|
||||
resp, err := getJSONBody(ctx, &req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
otp, code, err := HandleSysGenerateRootAttemptPut(b.Core, GenerateStandardRootTokenStrategy, &req, false)
|
||||
if err != nil {
|
||||
return nonLogicalError(code, err)
|
||||
}
|
||||
|
||||
// Return status with OTP if generated
|
||||
status, code, err := HandleSysGenerateRootAttemptGet(ctx, b.Core, otp, false)
|
||||
return nonLogicalResponse(status, code, err)
|
||||
}
|
||||
|
||||
func (b *SystemBackend) handleGenerateRootAttemptDelete(ctx context.Context) (*logical.Response, error) {
|
||||
code, err := HandleSysGenerateRootAttemptDelete(b.Core, false)
|
||||
if err != nil {
|
||||
return nonLogicalError(code, err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// handleGenerateRootUpdate handles the generate-root/update endpoint
|
||||
func (b *SystemBackend) handleGenerateRootUpdate(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
|
||||
var updateReq GenerateRootUpdateRequest
|
||||
resp, err := getJSONBody(ctx, &updateReq)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
result, code, err := HandleSysGenerateRootUpdate(ctx, b.Core, GenerateStandardRootTokenStrategy, &updateReq, false)
|
||||
return nonLogicalResponse(result, code, err)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
@ -295,6 +295,8 @@ func (b *SystemBackend) configPaths() []*framework.Path {
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleGenerateRootAttempt,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "read",
|
||||
OperationSuffix: "progress2|progress",
|
||||
@ -349,7 +351,9 @@ func (b *SystemBackend) configPaths() []*framework.Path {
|
||||
},
|
||||
},
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Summary: "Initializes a new root generation attempt.",
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleGenerateRootAttempt,
|
||||
Summary: "Initializes a new root generation attempt.",
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "initialize",
|
||||
OperationSuffix: "2|",
|
||||
@ -404,6 +408,8 @@ func (b *SystemBackend) configPaths() []*framework.Path {
|
||||
},
|
||||
},
|
||||
logical.DeleteOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleGenerateRootAttempt,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationVerb: "cancel",
|
||||
OperationSuffix: "2|",
|
||||
@ -434,6 +440,8 @@ func (b *SystemBackend) configPaths() []*framework.Path {
|
||||
},
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.UpdateOperation: &framework.PathOperation{
|
||||
ForwardPerformanceStandby: true,
|
||||
Callback: b.handleGenerateRootUpdate,
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: "root-token-generation",
|
||||
OperationVerb: "update",
|
||||
|
||||
@ -22,6 +22,10 @@ func entUnauthenticatedPaths() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func entBinaryPaths() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (s *SystemBackend) entInit() {}
|
||||
|
||||
func (s *SystemBackend) makeSnapshotSource(ctx context.Context, _ *framework.FieldData) (snapshots.Source, error) {
|
||||
|
||||
@ -758,6 +758,16 @@ type TokenStore struct {
|
||||
func NewTokenStore(ctx context.Context, logger log.Logger, core *Core, config *logical.BackendConfig) (*TokenStore, error) {
|
||||
// Create a sub-view
|
||||
view := core.systemBarrierView.SubView(tokenSubPath)
|
||||
if core.IsDRSecondary() {
|
||||
// Generally speaking, DR secondaries do not handle requests using tokens
|
||||
// from the TokenStore. Requests to DR secondaries should either be
|
||||
// unauthenticated, or use a batch token, or use a DR operation token
|
||||
// created using the generate-operation-token api. With the change to
|
||||
// make generate-operation-token authenticated by default, we're now also
|
||||
// allowing regular root tokens from the primary cluster to be used.
|
||||
//
|
||||
view.setReadOnlyErr(logical.ErrReadOnly)
|
||||
}
|
||||
|
||||
// Initialize the store
|
||||
t := &TokenStore{
|
||||
@ -1745,6 +1755,11 @@ func (ts *TokenStore) lookupInternal(ctx context.Context, id string, salted, tai
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
if entry.IsRoot() {
|
||||
// We don't need to check for persistence or expiration for root tokens.
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// Perform these checks on upgraded fields, but before persisting
|
||||
|
||||
// If we are still restoring the expiration manager, we want to ensure the
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user