Skip to content

Commit

Permalink
Add HTTP response headers for hostname and raft node ID (if applicabl…
Browse files Browse the repository at this point in the history
…e) (#11289)
  • Loading branch information
raskchanky authored and Brian Kassouf committed Jun 22, 2021
1 parent 6873512 commit ece23bf
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 94 deletions.
3 changes: 3 additions & 0 deletions changelog/11289.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
http: Add optional HTTP response headers for hostname and raft node ID
```
64 changes: 33 additions & 31 deletions command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1195,37 +1195,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
Expand Down
32 changes: 32 additions & 0 deletions command/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
41 changes: 24 additions & 17 deletions command/server/config_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions command/server/test-fixtures/config.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 19 additions & 2 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,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"
Expand Down Expand Up @@ -280,6 +280,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
Expand All @@ -303,6 +308,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)
Expand Down
69 changes: 67 additions & 2 deletions http/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
46 changes: 24 additions & 22 deletions http/sys_config_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{
Expand Down
Loading

0 comments on commit ece23bf

Please sign in to comment.