Add HTTP response headers for hostname and raft node ID (if applicable) (#11289)
This commit is contained in:
parent
45e2bfcad7
commit
06809930a3
|
@ -0,0 +1,3 @@
|
|||
```release-note:enhancement
|
||||
http: Add optional HTTP response headers for hostname and raft node ID
|
||||
```
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{}{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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].
|
||||
|
|
Loading…
Reference in New Issue