From 06809930a36f507e52ad26b104884ebfecf0b390 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Tue, 20 Apr 2021 15:25:04 -0700 Subject: [PATCH] Add HTTP response headers for hostname and raft node ID (if applicable) (#11289) --- changelog/11289.txt | 3 + command/server.go | 64 ++++++++-------- command/server/config.go | 32 ++++++++ command/server/config_test_helpers.go | 41 +++++----- command/server/test-fixtures/config.hcl | 2 + http/handler.go | 21 +++++- http/handler_test.go | 69 ++++++++++++++++- http/sys_config_state_test.go | 46 ++++++------ vault/core.go | 43 ++++++++--- vault/external_tests/raft/raft_test.go | 79 +++++++++++++++++--- vault/raft.go | 11 +++ vault/testing.go | 7 +- website/content/docs/configuration/index.mdx | 15 ++++ 13 files changed, 336 insertions(+), 97 deletions(-) create mode 100644 changelog/11289.txt diff --git a/changelog/11289.txt b/changelog/11289.txt new file mode 100644 index 000000000..29e9087f5 --- /dev/null +++ b/changelog/11289.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +http: Add optional HTTP response headers for hostname and raft node ID +``` diff --git a/command/server.go b/command/server.go index 974c914f9..8740ee684 100644 --- a/command/server.go +++ b/command/server.go @@ -1284,37 +1284,39 @@ func (c *ServerCommand) Run(args []string) int { } coreConfig := &vault.CoreConfig{ - RawConfig: config, - Physical: backend, - RedirectAddr: config.Storage.RedirectAddr, - StorageType: config.Storage.Type, - HAPhysical: nil, - ServiceRegistration: configSR, - Seal: barrierSeal, - UnwrapSeal: unwrapSeal, - AuditBackends: c.AuditBackends, - CredentialBackends: c.CredentialBackends, - LogicalBackends: c.LogicalBackends, - Logger: c.logger, - DisableSentinelTrace: config.DisableSentinelTrace, - DisableCache: config.DisableCache, - DisableMlock: config.DisableMlock, - MaxLeaseTTL: config.MaxLeaseTTL, - DefaultLeaseTTL: config.DefaultLeaseTTL, - ClusterName: config.ClusterName, - CacheSize: config.CacheSize, - PluginDirectory: config.PluginDirectory, - EnableUI: config.EnableUI, - EnableRaw: config.EnableRawEndpoint, - DisableSealWrap: config.DisableSealWrap, - DisablePerformanceStandby: config.DisablePerformanceStandby, - DisableIndexing: config.DisableIndexing, - AllLoggers: c.allLoggers, - BuiltinRegistry: builtinplugins.Registry, - DisableKeyEncodingChecks: config.DisablePrintableCheck, - MetricsHelper: metricsHelper, - MetricSink: metricSink, - SecureRandomReader: secureRandomReader, + RawConfig: config, + Physical: backend, + RedirectAddr: config.Storage.RedirectAddr, + StorageType: config.Storage.Type, + HAPhysical: nil, + ServiceRegistration: configSR, + Seal: barrierSeal, + UnwrapSeal: unwrapSeal, + AuditBackends: c.AuditBackends, + CredentialBackends: c.CredentialBackends, + LogicalBackends: c.LogicalBackends, + Logger: c.logger, + DisableSentinelTrace: config.DisableSentinelTrace, + DisableCache: config.DisableCache, + DisableMlock: config.DisableMlock, + MaxLeaseTTL: config.MaxLeaseTTL, + DefaultLeaseTTL: config.DefaultLeaseTTL, + ClusterName: config.ClusterName, + CacheSize: config.CacheSize, + PluginDirectory: config.PluginDirectory, + EnableUI: config.EnableUI, + EnableRaw: config.EnableRawEndpoint, + DisableSealWrap: config.DisableSealWrap, + DisablePerformanceStandby: config.DisablePerformanceStandby, + DisableIndexing: config.DisableIndexing, + AllLoggers: c.allLoggers, + BuiltinRegistry: builtinplugins.Registry, + DisableKeyEncodingChecks: config.DisablePrintableCheck, + MetricsHelper: metricsHelper, + MetricSink: metricSink, + SecureRandomReader: secureRandomReader, + EnableResponseHeaderHostname: config.EnableResponseHeaderHostname, + EnableResponseHeaderRaftNodeID: config.EnableResponseHeaderRaftNodeID, } if c.flagDev { coreConfig.EnableRaw = true diff --git a/command/server/config.go b/command/server/config.go index 2cebf277b..f53097fec 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -68,6 +68,12 @@ type Config struct { DisableSentinelTrace bool `hcl:"-"` DisableSentinelTraceRaw interface{} `hcl:"disable_sentinel_trace"` + + EnableResponseHeaderHostname bool `hcl:"-"` + EnableResponseHeaderHostnameRaw interface{} `hcl:"enable_response_header_hostname"` + + EnableResponseHeaderRaftNodeID bool `hcl:"-"` + EnableResponseHeaderRaftNodeIDRaw interface{} `hcl:"enable_response_header_raft_node_id"` } // DevConfig is a Config that is used for dev mode of Vault. @@ -245,6 +251,16 @@ func (c *Config) Merge(c2 *Config) *Config { result.DisableIndexing = c2.DisableIndexing } + result.EnableResponseHeaderHostname = c.EnableResponseHeaderHostname + if c2.EnableResponseHeaderHostname { + result.EnableResponseHeaderHostname = c2.EnableResponseHeaderHostname + } + + result.EnableResponseHeaderRaftNodeID = c.EnableResponseHeaderRaftNodeID + if c2.EnableResponseHeaderRaftNodeID { + result.EnableResponseHeaderRaftNodeID = c2.EnableResponseHeaderRaftNodeID + } + // Use values from top-level configuration for storage if set if storage := result.Storage; storage != nil { if result.APIAddr != "" { @@ -406,6 +422,18 @@ func ParseConfig(d string) (*Config, error) { } } + if result.EnableResponseHeaderHostnameRaw != nil { + if result.EnableResponseHeaderHostname, err = parseutil.ParseBool(result.EnableResponseHeaderHostnameRaw); err != nil { + return nil, err + } + } + + if result.EnableResponseHeaderRaftNodeIDRaw != nil { + if result.EnableResponseHeaderRaftNodeID, err = parseutil.ParseBool(result.EnableResponseHeaderRaftNodeIDRaw); err != nil { + return nil, err + } + } + list, ok := obj.Node.(*ast.ObjectList) if !ok { return nil, fmt.Errorf("error parsing: file doesn't contain a root object") @@ -742,6 +770,10 @@ func (c *Config) Sanitized() map[string]interface{} { "disable_sealwrap": c.DisableSealWrap, "disable_indexing": c.DisableIndexing, + + "enable_response_header_hostname": c.EnableResponseHeaderHostname, + + "enable_response_header_raft_node_id": c.EnableResponseHeaderRaftNodeID, } for k, v := range sharedResult { result[k] = v diff --git a/command/server/config_test_helpers.go b/command/server/config_test_helpers.go index ff2326c59..b026d13ee 100644 --- a/command/server/config_test_helpers.go +++ b/command/server/config_test_helpers.go @@ -441,6 +441,11 @@ func testLoadConfigFile(t *testing.T) { MaxLeaseTTLRaw: "10h", DefaultLeaseTTL: 10 * time.Hour, DefaultLeaseTTLRaw: "10h", + + EnableResponseHeaderHostname: true, + EnableResponseHeaderHostnameRaw: true, + EnableResponseHeaderRaftNodeID: true, + EnableResponseHeaderRaftNodeIDRaw: true, } addExpectedEntConfig(expected, []string{}) @@ -606,23 +611,25 @@ func testConfig_Sanitized(t *testing.T) { sanitizedConfig := config.Sanitized() expected := map[string]interface{}{ - "api_addr": "top_level_api_addr", - "cache_size": 0, - "cluster_addr": "top_level_cluster_addr", - "cluster_cipher_suites": "", - "cluster_name": "testcluster", - "default_lease_ttl": 10 * time.Hour, - "default_max_request_duration": 0 * time.Second, - "disable_cache": true, - "disable_clustering": false, - "disable_indexing": false, - "disable_mlock": true, - "disable_performance_standby": false, - "disable_printable_check": false, - "disable_sealwrap": true, - "raw_storage_endpoint": true, - "disable_sentinel_trace": true, - "enable_ui": true, + "api_addr": "top_level_api_addr", + "cache_size": 0, + "cluster_addr": "top_level_cluster_addr", + "cluster_cipher_suites": "", + "cluster_name": "testcluster", + "default_lease_ttl": 10 * time.Hour, + "default_max_request_duration": 0 * time.Second, + "disable_cache": true, + "disable_clustering": false, + "disable_indexing": false, + "disable_mlock": true, + "disable_performance_standby": false, + "disable_printable_check": false, + "disable_sealwrap": true, + "raw_storage_endpoint": true, + "disable_sentinel_trace": true, + "enable_ui": true, + "enable_response_header_hostname": false, + "enable_response_header_raft_node_id": false, "ha_storage": map[string]interface{}{ "cluster_addr": "top_level_cluster_addr", "disable_clustering": true, diff --git a/command/server/test-fixtures/config.hcl b/command/server/test-fixtures/config.hcl index c2f5b457a..da83dd9ab 100644 --- a/command/server/test-fixtures/config.hcl +++ b/command/server/test-fixtures/config.hcl @@ -45,3 +45,5 @@ pid_file = "./pidfile" raw_storage_endpoint = true disable_sealwrap = true disable_printable_check = true +enable_response_header_hostname = true +enable_response_header_raft_node_id = true \ No newline at end of file diff --git a/http/handler.go b/http/handler.go index 3002f6398..ac120d15b 100644 --- a/http/handler.go +++ b/http/handler.go @@ -21,8 +21,8 @@ import ( "github.com/NYTimes/gziphandler" assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/hashicorp/errwrap" - cleanhttp "github.com/hashicorp/go-cleanhttp" - sockaddr "github.com/hashicorp/go-sockaddr" + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-sockaddr" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/internalshared/configutil" "github.com/hashicorp/vault/sdk/helper/consts" @@ -293,6 +293,11 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr if maxRequestSize == 0 { maxRequestSize = DefaultMaxRequestSize } + + // Swallow this error since we don't want to pollute the logs and we also don't want to + // return an HTTP error here. This information is best effort. + hostname, _ := os.Hostname() + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Set the Cache-Control header for all the responses returned // by Vault @@ -316,6 +321,18 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr r = r.WithContext(ctx) r = r.WithContext(namespace.ContextWithNamespace(r.Context(), namespace.RootNamespace)) + // Set some response headers with raft node id (if applicable) and hostname, if available + if core.RaftNodeIDHeaderEnabled() { + nodeID := core.GetRaftNodeID() + if nodeID != "" { + w.Header().Set("X-Vault-Raft-Node-ID", nodeID) + } + } + + if core.HostnameHeaderEnabled() && hostname != "" { + w.Header().Set("X-Vault-Hostname", hostname) + } + switch { case strings.HasPrefix(r.URL.Path, "/v1/"): newR, status := adjustRequest(core, r) diff --git a/http/handler_test.go b/http/handler_test.go index 8342c69df..5373fa56c 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -15,8 +15,7 @@ import ( "testing" "github.com/go-test/deep" - - cleanhttp "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" @@ -191,6 +190,72 @@ func TestHandler_cors(t *testing.T) { } } +func TestHandler_HostnameHeader(t *testing.T) { + t.Parallel() + testCases := []struct { + description string + config *vault.CoreConfig + headerPresent bool + }{ + { + description: "with no header configured", + config: nil, + headerPresent: false, + }, + { + description: "with header configured", + config: &vault.CoreConfig{ + EnableResponseHeaderHostname: true, + }, + headerPresent: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + var core *vault.Core + + if tc.config == nil { + core, _, _ = vault.TestCoreUnsealed(t) + } else { + core, _, _ = vault.TestCoreUnsealedWithConfig(t, tc.config) + } + + ln, addr := TestServer(t, core) + defer ln.Close() + + req, err := http.NewRequest("GET", addr+"/v1/sys/seal-status", nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + client := cleanhttp.DefaultClient() + resp, err := client.Do(req) + if err != nil { + t.Fatalf("err: %s", err) + } + + if resp == nil { + t.Fatal("nil response") + } + + hnHeader := resp.Header.Get("X-Vault-Hostname") + if tc.headerPresent && hnHeader == "" { + t.Logf("header configured = %t", core.HostnameHeaderEnabled()) + t.Fatal("missing 'X-Vault-Hostname' header entry in response") + } + if !tc.headerPresent && hnHeader != "" { + t.Fatal("didn't expect 'X-Vault-Hostname' header but it was present anyway") + } + + rniHeader := resp.Header.Get("X-Vault-Raft-Node-ID") + if rniHeader != "" { + t.Fatalf("no raft node ID header was expected, since we're not running a raft cluster. instead, got %s", rniHeader) + } + }) + } +} + func TestHandler_CacheControlNoStore(t *testing.T) { core, _, token := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) diff --git a/http/sys_config_state_test.go b/http/sys_config_state_test.go index 53a605b42..543809ce2 100644 --- a/http/sys_config_state_test.go +++ b/http/sys_config_state_test.go @@ -24,28 +24,30 @@ func TestSysConfigState_Sanitized(t *testing.T) { var expected map[string]interface{} configResp := map[string]interface{}{ - "api_addr": "", - "cache_size": json.Number("0"), - "cluster_addr": "", - "cluster_cipher_suites": "", - "cluster_name": "", - "default_lease_ttl": json.Number("0"), - "default_max_request_duration": json.Number("0"), - "disable_cache": false, - "disable_clustering": false, - "disable_indexing": false, - "disable_mlock": false, - "disable_performance_standby": false, - "disable_printable_check": false, - "disable_sealwrap": false, - "raw_storage_endpoint": false, - "disable_sentinel_trace": false, - "enable_ui": false, - "log_format": "", - "log_level": "", - "max_lease_ttl": json.Number("0"), - "pid_file": "", - "plugin_directory": "", + "api_addr": "", + "cache_size": json.Number("0"), + "cluster_addr": "", + "cluster_cipher_suites": "", + "cluster_name": "", + "default_lease_ttl": json.Number("0"), + "default_max_request_duration": json.Number("0"), + "disable_cache": false, + "disable_clustering": false, + "disable_indexing": false, + "disable_mlock": false, + "disable_performance_standby": false, + "disable_printable_check": false, + "disable_sealwrap": false, + "raw_storage_endpoint": false, + "disable_sentinel_trace": false, + "enable_ui": false, + "log_format": "", + "log_level": "", + "max_lease_ttl": json.Number("0"), + "pid_file": "", + "plugin_directory": "", + "enable_response_header_hostname": false, + "enable_response_header_raft_node_id": false, } expected = map[string]interface{}{ diff --git a/vault/core.go b/vault/core.go index 27e24067c..d983d78a5 100644 --- a/vault/core.go +++ b/vault/core.go @@ -26,8 +26,6 @@ import ( "sync/atomic" "time" - "github.com/hashicorp/vault/physical/raft" - "github.com/armon/go-metrics" "github.com/hashicorp/errwrap" log "github.com/hashicorp/go-hclog" @@ -41,6 +39,7 @@ import ( "github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/internalshared/reloadutil" + "github.com/hashicorp/vault/physical/raft" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/jsonutil" @@ -567,6 +566,10 @@ type Core struct { // disableAutopilot is used to disable the autopilot subsystem in raft storage disableAutopilot bool + + // enable/disable identifying response headers + enableResponseHeaderHostname bool + enableResponseHeaderRaftNodeID bool } // CoreConfig is used to parameterize a core @@ -675,6 +678,10 @@ type CoreConfig struct { // DisableAutopilot is used to disable autopilot subsystem in raft storage DisableAutopilot bool + + // Whether to send headers in the HTTP response showing hostname or raft node ID + EnableResponseHeaderHostname bool + EnableResponseHeaderRaftNodeID bool } // GetServiceRegistration returns the config's ServiceRegistration, or nil if it does @@ -813,15 +820,17 @@ func NewCore(conf *CoreConfig) (*Core, error) { requests: new(uint64), syncInterval: syncInterval, }, - recoveryMode: conf.RecoveryMode, - postUnsealStarted: new(uint32), - raftJoinDoneCh: make(chan struct{}), - clusterHeartbeatInterval: clusterHeartbeatInterval, - activityLogConfig: conf.ActivityLogConfig, - keyRotateGracePeriod: new(int64), - numExpirationWorkers: conf.NumExpirationWorkers, - raftFollowerStates: raft.NewFollowerStates(), - disableAutopilot: conf.DisableAutopilot, + recoveryMode: conf.RecoveryMode, + postUnsealStarted: new(uint32), + raftJoinDoneCh: make(chan struct{}), + clusterHeartbeatInterval: clusterHeartbeatInterval, + activityLogConfig: conf.ActivityLogConfig, + keyRotateGracePeriod: new(int64), + numExpirationWorkers: conf.NumExpirationWorkers, + raftFollowerStates: raft.NewFollowerStates(), + disableAutopilot: conf.DisableAutopilot, + enableResponseHeaderHostname: conf.EnableResponseHeaderHostname, + enableResponseHeaderRaftNodeID: conf.EnableResponseHeaderRaftNodeID, } c.standbyStopCh.Store(make(chan struct{})) atomic.StoreUint32(c.sealed, 1) @@ -1004,6 +1013,18 @@ func NewCore(conf *CoreConfig) (*Core, error) { return c, nil } +// HostnameHeaderEnabled determines whether to add the X-Vault-Hostname header +// to HTTP responses. +func (c *Core) HostnameHeaderEnabled() bool { + return c.enableResponseHeaderHostname +} + +// RaftNodeIDHeaderEnabled determines whether to add the X-Vault-Raft-Node-ID header +// to HTTP responses. +func (c *Core) RaftNodeIDHeaderEnabled() bool { + return c.enableResponseHeaderRaftNodeID +} + // Shutdown is invoked when the Vault instance is about to be terminated. It // should not be accessible as part of an API call as it will cause an availability // problem. It is only used to gracefully quit in the case of HA so that failover diff --git a/vault/external_tests/raft/raft_test.go b/vault/external_tests/raft/raft_test.go index 6ec1c6088..13db5736e 100644 --- a/vault/external_tests/raft/raft_test.go +++ b/vault/external_tests/raft/raft_test.go @@ -12,28 +12,28 @@ import ( "testing" "time" - "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/sdk/logical" - "github.com/hashicorp/go-cleanhttp" - uuid "github.com/hashicorp/go-uuid" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/vault/api" credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/testhelpers" "github.com/hashicorp/vault/helper/testhelpers/teststorage" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/physical/raft" + "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault" "github.com/stretchr/testify/require" "golang.org/x/net/http2" ) type RaftClusterOpts struct { - DisableFollowerJoins bool - InmemCluster bool - EnableAutopilot bool - PhysicalFactoryConfig map[string]interface{} - DisablePerfStandby bool + DisableFollowerJoins bool + InmemCluster bool + EnableAutopilot bool + PhysicalFactoryConfig map[string]interface{} + DisablePerfStandby bool + EnableResponseHeaderRaftNodeID bool } func raftCluster(t testing.TB, ropts *RaftClusterOpts) *vault.TestCluster { @@ -45,7 +45,8 @@ func raftCluster(t testing.TB, ropts *RaftClusterOpts) *vault.TestCluster { CredentialBackends: map[string]logical.Factory{ "userpass": credUserpass.Factory, }, - DisableAutopilot: !ropts.EnableAutopilot, + DisableAutopilot: !ropts.EnableAutopilot, + EnableResponseHeaderRaftNodeID: ropts.EnableResponseHeaderRaftNodeID, } opts := vault.TestClusterOptions{ @@ -287,6 +288,64 @@ func TestRaft_RemovePeer(t *testing.T) { }) } +func TestRaft_NodeIDHeader(t *testing.T) { + t.Parallel() + testCases := []struct { + description string + ropts *RaftClusterOpts + headerPresent bool + }{ + { + description: "with no header configured", + ropts: nil, + headerPresent: false, + }, + { + description: "with header configured", + ropts: &RaftClusterOpts{ + EnableResponseHeaderRaftNodeID: true, + }, + headerPresent: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + cluster := raftCluster(t, tc.ropts) + defer cluster.Cleanup() + + for i, c := range cluster.Cores { + if c.Core.Sealed() { + t.Fatalf("failed to unseal core %d", i) + } + + client := c.Client + req := client.NewRequest("GET", "/v1/sys/seal-status") + resp, err := client.RawRequest(req) + if err != nil { + t.Fatalf("err: %s", err) + } + if resp == nil { + t.Fatalf("nil response") + } + + rniHeader := resp.Header.Get("X-Vault-Raft-Node-ID") + nodeID := c.Core.GetRaftNodeID() + + if tc.headerPresent && rniHeader == "" { + t.Fatal("missing 'X-Vault-Raft-Node-ID' header entry in response") + } + if tc.headerPresent && rniHeader != nodeID { + t.Fatalf("got the wrong raft node id. expected %s to equal %s", rniHeader, nodeID) + } + if !tc.headerPresent && rniHeader != "" { + t.Fatal("didn't expect 'X-Vault-Raft-Node-ID' header but it was present anyway") + } + } + }) + } +} + func TestRaft_Configuration(t *testing.T) { t.Parallel() cluster := raftCluster(t, nil) diff --git a/vault/raft.go b/vault/raft.go index 2c5dec5bd..f663d854d 100644 --- a/vault/raft.go +++ b/vault/raft.go @@ -40,6 +40,17 @@ var ( TestingUpdateClusterAddr uint32 ) +// GetRaftNodeID returns the raft node ID if there is one, or an empty string if there's not +func (c *Core) GetRaftNodeID() string { + rb := c.getRaftBackend() + + if rb == nil { + return "" + } else { + return rb.NodeID() + } +} + func (c *Core) GetRaftIndexes() (committed uint64, applied uint64) { c.stateLock.RLock() defer c.stateLock.RUnlock() diff --git a/vault/testing.go b/vault/testing.go index cb28f4ec2..6361292d4 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -27,7 +27,7 @@ import ( "time" "github.com/armon/go-metrics" - cleanhttp "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/go-cleanhttp" log "github.com/hashicorp/go-hclog" raftlib "github.com/hashicorp/raft" "github.com/hashicorp/vault/api" @@ -49,7 +49,7 @@ import ( "github.com/hashicorp/vault/vault/cluster" "github.com/hashicorp/vault/vault/seal" "github.com/mitchellh/copystructure" - testing "github.com/mitchellh/go-testing-interface" + "github.com/mitchellh/go-testing-interface" "golang.org/x/crypto/ed25519" "golang.org/x/net/http2" ) @@ -159,6 +159,7 @@ func TestCoreWithSealAndUI(t testing.T, opts *CoreConfig) *Core { conf.MetricSink = opts.MetricSink conf.NumExpirationWorkers = numExpirationWorkersTest conf.RawConfig = opts.RawConfig + conf.EnableResponseHeaderHostname = opts.EnableResponseHeaderHostname if opts.Logger != nil { conf.Logger = opts.Logger @@ -1518,6 +1519,8 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te coreConfig.RecoveryMode = base.RecoveryMode coreConfig.ActivityLogConfig = base.ActivityLogConfig + coreConfig.EnableResponseHeaderHostname = base.EnableResponseHeaderHostname + coreConfig.EnableResponseHeaderRaftNodeID = base.EnableResponseHeaderRaftNodeID testApplyEntBaseConfig(coreConfig, base) } diff --git a/website/content/docs/configuration/index.mdx b/website/content/docs/configuration/index.mdx index 1d96e6cdd..52d44b076 100644 --- a/website/content/docs/configuration/index.mdx +++ b/website/content/docs/configuration/index.mdx @@ -155,6 +155,21 @@ to specify where the configuration is. - `pid_file` `(string: "")` - Path to the file in which the Vault server's Process ID (PID) should be stored. +- `enable_response_header_hostname` `(bool: false)` - Enables the addition of an HTTP header + in all of Vault's HTTP responses: `X-Vault-Hostname`. This will contain the + host name of the Vault node that serviced the HTTP request. This information + is best effort and is not guaranteed to be present. If this configuration + option is enabled and the `X-Vault-Hostname` header is not present in a response, + it means there was some kind of error retrieving the host name from the + operating system. + +- `enable_response_header_raft_node_id` `(bool: false)` - Enables the addition of an HTTP header + in all of Vault's HTTP responses: `X-Vault-Raft-Node-ID`. If Vault is participating + in a Raft cluster (i.e. using integrated storage), this header will contain the + Raft node ID of the Vault node that serviced the HTTP request. If Vault is not + participating in a Raft cluster, this header will be omitted, whether this configuration + option is enabled or not. + ### High Availability Parameters The following parameters are used on backends that support [high availability][high-availability].