mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 11:38:02 +00:00
Add HTTP response headers for hostname and raft node ID (if applicable) (#11289)
This commit is contained in:
3
changelog/11289.txt
Normal file
3
changelog/11289.txt
Normal file
@@ -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{
|
coreConfig := &vault.CoreConfig{
|
||||||
RawConfig: config,
|
RawConfig: config,
|
||||||
Physical: backend,
|
Physical: backend,
|
||||||
RedirectAddr: config.Storage.RedirectAddr,
|
RedirectAddr: config.Storage.RedirectAddr,
|
||||||
StorageType: config.Storage.Type,
|
StorageType: config.Storage.Type,
|
||||||
HAPhysical: nil,
|
HAPhysical: nil,
|
||||||
ServiceRegistration: configSR,
|
ServiceRegistration: configSR,
|
||||||
Seal: barrierSeal,
|
Seal: barrierSeal,
|
||||||
UnwrapSeal: unwrapSeal,
|
UnwrapSeal: unwrapSeal,
|
||||||
AuditBackends: c.AuditBackends,
|
AuditBackends: c.AuditBackends,
|
||||||
CredentialBackends: c.CredentialBackends,
|
CredentialBackends: c.CredentialBackends,
|
||||||
LogicalBackends: c.LogicalBackends,
|
LogicalBackends: c.LogicalBackends,
|
||||||
Logger: c.logger,
|
Logger: c.logger,
|
||||||
DisableSentinelTrace: config.DisableSentinelTrace,
|
DisableSentinelTrace: config.DisableSentinelTrace,
|
||||||
DisableCache: config.DisableCache,
|
DisableCache: config.DisableCache,
|
||||||
DisableMlock: config.DisableMlock,
|
DisableMlock: config.DisableMlock,
|
||||||
MaxLeaseTTL: config.MaxLeaseTTL,
|
MaxLeaseTTL: config.MaxLeaseTTL,
|
||||||
DefaultLeaseTTL: config.DefaultLeaseTTL,
|
DefaultLeaseTTL: config.DefaultLeaseTTL,
|
||||||
ClusterName: config.ClusterName,
|
ClusterName: config.ClusterName,
|
||||||
CacheSize: config.CacheSize,
|
CacheSize: config.CacheSize,
|
||||||
PluginDirectory: config.PluginDirectory,
|
PluginDirectory: config.PluginDirectory,
|
||||||
EnableUI: config.EnableUI,
|
EnableUI: config.EnableUI,
|
||||||
EnableRaw: config.EnableRawEndpoint,
|
EnableRaw: config.EnableRawEndpoint,
|
||||||
DisableSealWrap: config.DisableSealWrap,
|
DisableSealWrap: config.DisableSealWrap,
|
||||||
DisablePerformanceStandby: config.DisablePerformanceStandby,
|
DisablePerformanceStandby: config.DisablePerformanceStandby,
|
||||||
DisableIndexing: config.DisableIndexing,
|
DisableIndexing: config.DisableIndexing,
|
||||||
AllLoggers: c.allLoggers,
|
AllLoggers: c.allLoggers,
|
||||||
BuiltinRegistry: builtinplugins.Registry,
|
BuiltinRegistry: builtinplugins.Registry,
|
||||||
DisableKeyEncodingChecks: config.DisablePrintableCheck,
|
DisableKeyEncodingChecks: config.DisablePrintableCheck,
|
||||||
MetricsHelper: metricsHelper,
|
MetricsHelper: metricsHelper,
|
||||||
MetricSink: metricSink,
|
MetricSink: metricSink,
|
||||||
SecureRandomReader: secureRandomReader,
|
SecureRandomReader: secureRandomReader,
|
||||||
|
EnableResponseHeaderHostname: config.EnableResponseHeaderHostname,
|
||||||
|
EnableResponseHeaderRaftNodeID: config.EnableResponseHeaderRaftNodeID,
|
||||||
}
|
}
|
||||||
if c.flagDev {
|
if c.flagDev {
|
||||||
coreConfig.EnableRaw = true
|
coreConfig.EnableRaw = true
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ type Config struct {
|
|||||||
|
|
||||||
DisableSentinelTrace bool `hcl:"-"`
|
DisableSentinelTrace bool `hcl:"-"`
|
||||||
DisableSentinelTraceRaw interface{} `hcl:"disable_sentinel_trace"`
|
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.
|
// 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.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
|
// Use values from top-level configuration for storage if set
|
||||||
if storage := result.Storage; storage != nil {
|
if storage := result.Storage; storage != nil {
|
||||||
if result.APIAddr != "" {
|
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)
|
list, ok := obj.Node.(*ast.ObjectList)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
|
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_sealwrap": c.DisableSealWrap,
|
||||||
|
|
||||||
"disable_indexing": c.DisableIndexing,
|
"disable_indexing": c.DisableIndexing,
|
||||||
|
|
||||||
|
"enable_response_header_hostname": c.EnableResponseHeaderHostname,
|
||||||
|
|
||||||
|
"enable_response_header_raft_node_id": c.EnableResponseHeaderRaftNodeID,
|
||||||
}
|
}
|
||||||
for k, v := range sharedResult {
|
for k, v := range sharedResult {
|
||||||
result[k] = v
|
result[k] = v
|
||||||
|
|||||||
@@ -441,6 +441,11 @@ func testLoadConfigFile(t *testing.T) {
|
|||||||
MaxLeaseTTLRaw: "10h",
|
MaxLeaseTTLRaw: "10h",
|
||||||
DefaultLeaseTTL: 10 * time.Hour,
|
DefaultLeaseTTL: 10 * time.Hour,
|
||||||
DefaultLeaseTTLRaw: "10h",
|
DefaultLeaseTTLRaw: "10h",
|
||||||
|
|
||||||
|
EnableResponseHeaderHostname: true,
|
||||||
|
EnableResponseHeaderHostnameRaw: true,
|
||||||
|
EnableResponseHeaderRaftNodeID: true,
|
||||||
|
EnableResponseHeaderRaftNodeIDRaw: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
addExpectedEntConfig(expected, []string{})
|
addExpectedEntConfig(expected, []string{})
|
||||||
@@ -606,23 +611,25 @@ func testConfig_Sanitized(t *testing.T) {
|
|||||||
sanitizedConfig := config.Sanitized()
|
sanitizedConfig := config.Sanitized()
|
||||||
|
|
||||||
expected := map[string]interface{}{
|
expected := map[string]interface{}{
|
||||||
"api_addr": "top_level_api_addr",
|
"api_addr": "top_level_api_addr",
|
||||||
"cache_size": 0,
|
"cache_size": 0,
|
||||||
"cluster_addr": "top_level_cluster_addr",
|
"cluster_addr": "top_level_cluster_addr",
|
||||||
"cluster_cipher_suites": "",
|
"cluster_cipher_suites": "",
|
||||||
"cluster_name": "testcluster",
|
"cluster_name": "testcluster",
|
||||||
"default_lease_ttl": 10 * time.Hour,
|
"default_lease_ttl": 10 * time.Hour,
|
||||||
"default_max_request_duration": 0 * time.Second,
|
"default_max_request_duration": 0 * time.Second,
|
||||||
"disable_cache": true,
|
"disable_cache": true,
|
||||||
"disable_clustering": false,
|
"disable_clustering": false,
|
||||||
"disable_indexing": false,
|
"disable_indexing": false,
|
||||||
"disable_mlock": true,
|
"disable_mlock": true,
|
||||||
"disable_performance_standby": false,
|
"disable_performance_standby": false,
|
||||||
"disable_printable_check": false,
|
"disable_printable_check": false,
|
||||||
"disable_sealwrap": true,
|
"disable_sealwrap": true,
|
||||||
"raw_storage_endpoint": true,
|
"raw_storage_endpoint": true,
|
||||||
"disable_sentinel_trace": true,
|
"disable_sentinel_trace": true,
|
||||||
"enable_ui": true,
|
"enable_ui": true,
|
||||||
|
"enable_response_header_hostname": false,
|
||||||
|
"enable_response_header_raft_node_id": false,
|
||||||
"ha_storage": map[string]interface{}{
|
"ha_storage": map[string]interface{}{
|
||||||
"cluster_addr": "top_level_cluster_addr",
|
"cluster_addr": "top_level_cluster_addr",
|
||||||
"disable_clustering": true,
|
"disable_clustering": true,
|
||||||
|
|||||||
@@ -45,3 +45,5 @@ pid_file = "./pidfile"
|
|||||||
raw_storage_endpoint = true
|
raw_storage_endpoint = true
|
||||||
disable_sealwrap = true
|
disable_sealwrap = true
|
||||||
disable_printable_check = 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"
|
"github.com/NYTimes/gziphandler"
|
||||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
sockaddr "github.com/hashicorp/go-sockaddr"
|
"github.com/hashicorp/go-sockaddr"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/internalshared/configutil"
|
"github.com/hashicorp/vault/internalshared/configutil"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"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 {
|
if maxRequestSize == 0 {
|
||||||
maxRequestSize = DefaultMaxRequestSize
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Set the Cache-Control header for all the responses returned
|
// Set the Cache-Control header for all the responses returned
|
||||||
// by Vault
|
// by Vault
|
||||||
@@ -316,6 +321,18 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr
|
|||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
r = r.WithContext(namespace.ContextWithNamespace(r.Context(), namespace.RootNamespace))
|
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 {
|
switch {
|
||||||
case strings.HasPrefix(r.URL.Path, "/v1/"):
|
case strings.HasPrefix(r.URL.Path, "/v1/"):
|
||||||
newR, status := adjustRequest(core, r)
|
newR, status := adjustRequest(core, r)
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"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) {
|
func TestHandler_CacheControlNoStore(t *testing.T) {
|
||||||
core, _, token := vault.TestCoreUnsealed(t)
|
core, _, token := vault.TestCoreUnsealed(t)
|
||||||
ln, addr := TestServer(t, core)
|
ln, addr := TestServer(t, core)
|
||||||
|
|||||||
@@ -24,28 +24,30 @@ func TestSysConfigState_Sanitized(t *testing.T) {
|
|||||||
var expected map[string]interface{}
|
var expected map[string]interface{}
|
||||||
|
|
||||||
configResp := map[string]interface{}{
|
configResp := map[string]interface{}{
|
||||||
"api_addr": "",
|
"api_addr": "",
|
||||||
"cache_size": json.Number("0"),
|
"cache_size": json.Number("0"),
|
||||||
"cluster_addr": "",
|
"cluster_addr": "",
|
||||||
"cluster_cipher_suites": "",
|
"cluster_cipher_suites": "",
|
||||||
"cluster_name": "",
|
"cluster_name": "",
|
||||||
"default_lease_ttl": json.Number("0"),
|
"default_lease_ttl": json.Number("0"),
|
||||||
"default_max_request_duration": json.Number("0"),
|
"default_max_request_duration": json.Number("0"),
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_clustering": false,
|
"disable_clustering": false,
|
||||||
"disable_indexing": false,
|
"disable_indexing": false,
|
||||||
"disable_mlock": false,
|
"disable_mlock": false,
|
||||||
"disable_performance_standby": false,
|
"disable_performance_standby": false,
|
||||||
"disable_printable_check": false,
|
"disable_printable_check": false,
|
||||||
"disable_sealwrap": false,
|
"disable_sealwrap": false,
|
||||||
"raw_storage_endpoint": false,
|
"raw_storage_endpoint": false,
|
||||||
"disable_sentinel_trace": false,
|
"disable_sentinel_trace": false,
|
||||||
"enable_ui": false,
|
"enable_ui": false,
|
||||||
"log_format": "",
|
"log_format": "",
|
||||||
"log_level": "",
|
"log_level": "",
|
||||||
"max_lease_ttl": json.Number("0"),
|
"max_lease_ttl": json.Number("0"),
|
||||||
"pid_file": "",
|
"pid_file": "",
|
||||||
"plugin_directory": "",
|
"plugin_directory": "",
|
||||||
|
"enable_response_header_hostname": false,
|
||||||
|
"enable_response_header_raft_node_id": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = map[string]interface{}{
|
expected = map[string]interface{}{
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/physical/raft"
|
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
log "github.com/hashicorp/go-hclog"
|
log "github.com/hashicorp/go-hclog"
|
||||||
@@ -41,6 +39,7 @@ import (
|
|||||||
"github.com/hashicorp/vault/helper/metricsutil"
|
"github.com/hashicorp/vault/helper/metricsutil"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/internalshared/reloadutil"
|
"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/certutil"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
"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 is used to disable the autopilot subsystem in raft storage
|
||||||
disableAutopilot bool
|
disableAutopilot bool
|
||||||
|
|
||||||
|
// enable/disable identifying response headers
|
||||||
|
enableResponseHeaderHostname bool
|
||||||
|
enableResponseHeaderRaftNodeID bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CoreConfig is used to parameterize a core
|
// 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 is used to disable autopilot subsystem in raft storage
|
||||||
DisableAutopilot bool
|
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
|
// GetServiceRegistration returns the config's ServiceRegistration, or nil if it does
|
||||||
@@ -813,15 +820,17 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||||||
requests: new(uint64),
|
requests: new(uint64),
|
||||||
syncInterval: syncInterval,
|
syncInterval: syncInterval,
|
||||||
},
|
},
|
||||||
recoveryMode: conf.RecoveryMode,
|
recoveryMode: conf.RecoveryMode,
|
||||||
postUnsealStarted: new(uint32),
|
postUnsealStarted: new(uint32),
|
||||||
raftJoinDoneCh: make(chan struct{}),
|
raftJoinDoneCh: make(chan struct{}),
|
||||||
clusterHeartbeatInterval: clusterHeartbeatInterval,
|
clusterHeartbeatInterval: clusterHeartbeatInterval,
|
||||||
activityLogConfig: conf.ActivityLogConfig,
|
activityLogConfig: conf.ActivityLogConfig,
|
||||||
keyRotateGracePeriod: new(int64),
|
keyRotateGracePeriod: new(int64),
|
||||||
numExpirationWorkers: conf.NumExpirationWorkers,
|
numExpirationWorkers: conf.NumExpirationWorkers,
|
||||||
raftFollowerStates: raft.NewFollowerStates(),
|
raftFollowerStates: raft.NewFollowerStates(),
|
||||||
disableAutopilot: conf.DisableAutopilot,
|
disableAutopilot: conf.DisableAutopilot,
|
||||||
|
enableResponseHeaderHostname: conf.EnableResponseHeaderHostname,
|
||||||
|
enableResponseHeaderRaftNodeID: conf.EnableResponseHeaderRaftNodeID,
|
||||||
}
|
}
|
||||||
c.standbyStopCh.Store(make(chan struct{}))
|
c.standbyStopCh.Store(make(chan struct{}))
|
||||||
atomic.StoreUint32(c.sealed, 1)
|
atomic.StoreUint32(c.sealed, 1)
|
||||||
@@ -1004,6 +1013,18 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||||||
return c, nil
|
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
|
// 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
|
// 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
|
// problem. It is only used to gracefully quit in the case of HA so that failover
|
||||||
|
|||||||
@@ -12,28 +12,28 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/api"
|
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
"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"
|
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/helper/testhelpers"
|
"github.com/hashicorp/vault/helper/testhelpers"
|
||||||
"github.com/hashicorp/vault/helper/testhelpers/teststorage"
|
"github.com/hashicorp/vault/helper/testhelpers/teststorage"
|
||||||
vaulthttp "github.com/hashicorp/vault/http"
|
vaulthttp "github.com/hashicorp/vault/http"
|
||||||
"github.com/hashicorp/vault/physical/raft"
|
"github.com/hashicorp/vault/physical/raft"
|
||||||
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RaftClusterOpts struct {
|
type RaftClusterOpts struct {
|
||||||
DisableFollowerJoins bool
|
DisableFollowerJoins bool
|
||||||
InmemCluster bool
|
InmemCluster bool
|
||||||
EnableAutopilot bool
|
EnableAutopilot bool
|
||||||
PhysicalFactoryConfig map[string]interface{}
|
PhysicalFactoryConfig map[string]interface{}
|
||||||
DisablePerfStandby bool
|
DisablePerfStandby bool
|
||||||
|
EnableResponseHeaderRaftNodeID bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func raftCluster(t testing.TB, ropts *RaftClusterOpts) *vault.TestCluster {
|
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{
|
CredentialBackends: map[string]logical.Factory{
|
||||||
"userpass": credUserpass.Factory,
|
"userpass": credUserpass.Factory,
|
||||||
},
|
},
|
||||||
DisableAutopilot: !ropts.EnableAutopilot,
|
DisableAutopilot: !ropts.EnableAutopilot,
|
||||||
|
EnableResponseHeaderRaftNodeID: ropts.EnableResponseHeaderRaftNodeID,
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := vault.TestClusterOptions{
|
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) {
|
func TestRaft_Configuration(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
cluster := raftCluster(t, nil)
|
cluster := raftCluster(t, nil)
|
||||||
|
|||||||
@@ -40,6 +40,17 @@ var (
|
|||||||
TestingUpdateClusterAddr uint32
|
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) {
|
func (c *Core) GetRaftIndexes() (committed uint64, applied uint64) {
|
||||||
c.stateLock.RLock()
|
c.stateLock.RLock()
|
||||||
defer c.stateLock.RUnlock()
|
defer c.stateLock.RUnlock()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
log "github.com/hashicorp/go-hclog"
|
log "github.com/hashicorp/go-hclog"
|
||||||
raftlib "github.com/hashicorp/raft"
|
raftlib "github.com/hashicorp/raft"
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
@@ -49,7 +49,7 @@ import (
|
|||||||
"github.com/hashicorp/vault/vault/cluster"
|
"github.com/hashicorp/vault/vault/cluster"
|
||||||
"github.com/hashicorp/vault/vault/seal"
|
"github.com/hashicorp/vault/vault/seal"
|
||||||
"github.com/mitchellh/copystructure"
|
"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/crypto/ed25519"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@@ -159,6 +159,7 @@ func TestCoreWithSealAndUI(t testing.T, opts *CoreConfig) *Core {
|
|||||||
conf.MetricSink = opts.MetricSink
|
conf.MetricSink = opts.MetricSink
|
||||||
conf.NumExpirationWorkers = numExpirationWorkersTest
|
conf.NumExpirationWorkers = numExpirationWorkersTest
|
||||||
conf.RawConfig = opts.RawConfig
|
conf.RawConfig = opts.RawConfig
|
||||||
|
conf.EnableResponseHeaderHostname = opts.EnableResponseHeaderHostname
|
||||||
|
|
||||||
if opts.Logger != nil {
|
if opts.Logger != nil {
|
||||||
conf.Logger = opts.Logger
|
conf.Logger = opts.Logger
|
||||||
@@ -1518,6 +1519,8 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
|
|||||||
coreConfig.RecoveryMode = base.RecoveryMode
|
coreConfig.RecoveryMode = base.RecoveryMode
|
||||||
|
|
||||||
coreConfig.ActivityLogConfig = base.ActivityLogConfig
|
coreConfig.ActivityLogConfig = base.ActivityLogConfig
|
||||||
|
coreConfig.EnableResponseHeaderHostname = base.EnableResponseHeaderHostname
|
||||||
|
coreConfig.EnableResponseHeaderRaftNodeID = base.EnableResponseHeaderRaftNodeID
|
||||||
|
|
||||||
testApplyEntBaseConfig(coreConfig, base)
|
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
|
- `pid_file` `(string: "")` - Path to the file in which the Vault server's
|
||||||
Process ID (PID) should be stored.
|
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
|
### High Availability Parameters
|
||||||
|
|
||||||
The following parameters are used on backends that support [high availability][high-availability].
|
The following parameters are used on backends that support [high availability][high-availability].
|
||||||
|
|||||||
Reference in New Issue
Block a user