diff --git a/http/sys_health.go b/http/sys_health.go index 0e1f0ff247..11123243f9 100644 --- a/http/sys_health.go +++ b/http/sys_health.go @@ -57,14 +57,27 @@ func handleSysHealthGet(core *vault.Core, w http.ResponseWriter, r *http.Request return } - opts, err := getOpts(opt...) + var tokenPresent bool + token := r.Header.Get(consts.AuthHeaderName) - if opts.withRedactVersion { - body.Version = opts.withRedactionValue + if token != "" { + // We don't care about the error, we just want to know if the token exists + lock := core.HALock() + lock.Lock() + tokenEntry, err := core.LookupToken(r.Context(), token) + lock.Unlock() + tokenPresent = err == nil && tokenEntry != nil } + opts, _ := getOpts(opt...) - if opts.withRedactClusterName { - body.ClusterName = opts.withRedactionValue + if !tokenPresent { + if opts.withRedactVersion { + body.Version = opts.withRedactionValue + } + + if opts.withRedactClusterName { + body.ClusterName = opts.withRedactionValue + } } w.Header().Set("Content-Type", "application/json") diff --git a/http/sys_leader.go b/http/sys_leader.go index 49eda81621..b6e0f55e93 100644 --- a/http/sys_leader.go +++ b/http/sys_leader.go @@ -4,9 +4,9 @@ package http import ( - "context" "net/http" + "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault" ) @@ -17,19 +17,29 @@ func handleSysLeader(core *vault.Core, opt ...ListenerConfigOption) http.Handler return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": - handleSysLeaderGet(core, w, opt...) + handleSysLeaderGet(core, w, r, opt...) default: respondError(w, http.StatusMethodNotAllowed, nil) } }) } -func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, opt ...ListenerConfigOption) { - ctx := context.Background() +func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, r *http.Request, opt ...ListenerConfigOption) { + var tokenPresent bool + token := r.Header.Get(consts.AuthHeaderName) + ctx := r.Context() - opts, _ := getOpts(opt...) - if opts.withRedactAddresses { - ctx = logical.CreateContextRedactionSettings(ctx, false, true, false) + if token != "" { + // We don't care about the error, we just want to know if token exists + lock := core.HALock() + lock.Lock() + tokenEntry, err := core.LookupToken(ctx, token) + lock.Unlock() + tokenPresent = err == nil && tokenEntry != nil + } + + if tokenPresent { + ctx = logical.CreateContextRedactionSettings(r.Context(), false, false, false) } resp, err := core.GetLeaderStatus(ctx) diff --git a/http/sys_seal.go b/http/sys_seal.go index a17c6eb660..4852d57d5e 100644 --- a/http/sys_seal.go +++ b/http/sys_seal.go @@ -98,7 +98,7 @@ func handleSysUnseal(core *vault.Core) http.Handler { return } core.ResetUnsealProcess() - handleSysSealStatusRaw(core, w) + handleSysSealStatusRaw(core, w, r) return } @@ -148,7 +148,7 @@ func handleSysUnseal(core *vault.Core) http.Handler { } // Return the seal status - handleSysSealStatusRaw(core, w) + handleSysSealStatusRaw(core, w, r) }) } @@ -159,7 +159,7 @@ func handleSysSealStatus(core *vault.Core, opt ...ListenerConfigOption) http.Han return } - handleSysSealStatusRaw(core, w, opt...) + handleSysSealStatusRaw(core, w, r, opt...) }) } @@ -174,25 +174,30 @@ func handleSysSealBackendStatus(core *vault.Core) http.Handler { }) } -func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, opt ...ListenerConfigOption) { - ctx := context.Background() +func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Request, opt ...ListenerConfigOption) { + ctx := r.Context() + + var tokenPresent bool + token := r.Header.Get(consts.AuthHeaderName) + if token != "" { + // We don't care about the error, we just want to know if the token exists + lock := core.HALock() + lock.Lock() + tokenEntry, err := core.LookupToken(ctx, token) + lock.Unlock() + tokenPresent = err == nil && tokenEntry != nil + } + + // If there are is no valid token then we will redact the specified values + if tokenPresent { + ctx = logical.CreateContextRedactionSettings(ctx, false, false, false) + } + status, err := core.GetSealStatus(ctx, true) if err != nil { respondError(w, http.StatusInternalServerError, err) return } - - opts, err := getOpts(opt...) - - if opts.withRedactVersion { - status.Version = opts.withRedactionValue - status.BuildDate = opts.withRedactionValue - } - - if opts.withRedactClusterName { - status.ClusterName = opts.withRedactionValue - } - respondOk(w, status) } @@ -203,7 +208,6 @@ func handleSysSealBackendStatusRaw(core *vault.Core, w http.ResponseWriter, r *h respondError(w, http.StatusInternalServerError, err) return } - respondOk(w, status) } diff --git a/http/sys_seal_test.go b/http/sys_seal_test.go index f11335bac5..866ef46092 100644 --- a/http/sys_seal_test.go +++ b/http/sys_seal_test.go @@ -15,11 +15,18 @@ import ( "testing" "github.com/go-test/deep" + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/vault/audit" + auditFile "github.com/hashicorp/vault/builtin/audit/file" "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/helper/testhelpers/corehelpers" + "github.com/hashicorp/vault/internalshared/configutil" + "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault/seal" "github.com/hashicorp/vault/version" + "github.com/stretchr/testify/assert" ) func TestSysSealStatus(t *testing.T) { @@ -556,3 +563,64 @@ func TestSysStepDown(t *testing.T) { resp := testHttpPut(t, token, addr+"/v1/sys/step-down", nil) testResponseStatus(t, resp, 204) } + +// TestSysSealStatusRedaction tests that the response from a +// a request to sys/seal-status are redacted only if no valid token +// is provided with the request +func TestSysSealStatusRedaction(t *testing.T) { + conf := &vault.CoreConfig{ + EnableUI: false, + EnableRaw: true, + BuiltinRegistry: corehelpers.NewMockBuiltinRegistry(), + AuditBackends: map[string]audit.Factory{ + "file": auditFile.Factory, + }, + } + core, _, token := vault.TestCoreUnsealedWithConfig(t, conf) + + // Setup new custom listener + ln, addr := TestListener(t) + props := &vault.HandlerProperties{ + Core: core, + ListenerConfig: &configutil.Listener{ + RedactVersion: true, + }, + } + TestServerWithListenerAndProperties(t, ln, addr, core, props) + defer ln.Close() + TestServerAuth(t, addr, token) + + client := cleanhttp.DefaultClient() + + // Check seal-status + req, err := http.NewRequest("GET", addr+"/v1/sys/seal-status", nil) + if err != nil { + t.Fatalf("err: %s", err) + } + req.Header.Set(consts.AuthHeaderName, token) + resp, err := client.Do(req) + if err != nil { + t.Fatalf("err: %s", err) + } + testResponseStatus(t, resp, 200) + + // Verify that version exists when provided a valid token + var actual map[string]interface{} + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + assert.NotEmpty(t, actual["version"]) + + // Verify that version is redacted when no token is provided + req, err = http.NewRequest("GET", addr+"/v1/sys/seal-status", nil) + if err != nil { + t.Fatalf("err: %s", err) + } + req.Header.Set(consts.AuthHeaderName, "") + resp, err = client.Do(req) + if err != nil { + t.Fatalf("err: %s", err) + } + testResponseStatus(t, resp, 200) + testResponseBody(t, resp, &actual) + assert.Empty(t, actual["version"]) +} diff --git a/sdk/helper/testcluster/docker/environment.go b/sdk/helper/testcluster/docker/environment.go index 84dedbda7a..a69a5fa8c6 100644 --- a/sdk/helper/testcluster/docker/environment.go +++ b/sdk/helper/testcluster/docker/environment.go @@ -640,6 +640,9 @@ func (n *DockerClusterNode) Start(ctx context.Context, opts *DockerClusterOption listener := cfg["tcp"].(map[string]interface{}) listener["address"] = fmt.Sprintf("%s:%d", "0.0.0.0", config.Port) listener["chroot_namespace"] = config.ChrootNamespace + listener["redact_addresses"] = config.RedactAddresses + listener["redact_cluster_name"] = config.RedactClusterName + listener["redact_version"] = config.RedactVersion listenerConfig = append(listenerConfig, cfg) portStr := fmt.Sprintf("%d/tcp", config.Port) if strutil.StrListContains(ports, portStr) { diff --git a/sdk/helper/testcluster/types.go b/sdk/helper/testcluster/types.go index 03013d89e3..0c04c224c1 100644 --- a/sdk/helper/testcluster/types.go +++ b/sdk/helper/testcluster/types.go @@ -105,8 +105,11 @@ type ClusterOptions struct { } type VaultNodeListenerConfig struct { - Port int - ChrootNamespace string + Port int + ChrootNamespace string + RedactAddresses bool + RedactClusterName bool + RedactVersion bool } type CA struct { diff --git a/vault/core.go b/vault/core.go index cfdb83ec14..062f47288a 100644 --- a/vault/core.go +++ b/vault/core.go @@ -743,6 +743,10 @@ func (c *Core) HAStateWithLock() consts.HAState { return c.HAState() } +func (c *Core) HALock() sync.Locker { + return c.stateLock.RLocker() +} + // CoreConfig is used to parameterize a core type CoreConfig struct { entCoreConfig diff --git a/vault/logical_system.go b/vault/logical_system.go index a3c8ff6232..78d2d976cf 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -5675,6 +5675,17 @@ func (core *Core) GetLeaderStatusLocked(ctx context.Context) (*LeaderResponse, e } func (b *SystemBackend) handleSealStatus(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + var tokenPresent bool + token := req.ClientToken + if token != "" { + // We don't care about the error, we just want to know if the token exists + _, tokenEntry, _, _, err := b.Core.fetchACLTokenEntryAndEntity(ctx, req) + tokenPresent = err == nil && tokenEntry != nil + } + // If there is a valid token then we will not redact any values + if tokenPresent { + ctx = logical.CreateContextRedactionSettings(ctx, false, false, false) + } status, err := b.Core.GetSealStatus(ctx, false) if err != nil { return nil, err @@ -5694,6 +5705,18 @@ func (b *SystemBackend) handleSealStatus(ctx context.Context, req *logical.Reque } func (b *SystemBackend) handleLeaderStatus(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + var tokenPresent bool + token := req.ClientToken + + if token != "" { + // We don't care about the error, we just want to know if token exists + _, tokenEntry, _, _, err := b.Core.fetchACLTokenEntryAndEntity(ctx, req) + tokenPresent = err == nil && tokenEntry != nil + } + + if tokenPresent { + ctx = logical.CreateContextRedactionSettings(ctx, false, false, false) + } status, err := b.Core.GetLeaderStatusLocked(ctx) if err != nil { return nil, err