diff --git a/agent/config.go b/agent/config.go index 504a860293..3bbc4b632f 100644 --- a/agent/config.go +++ b/agent/config.go @@ -129,6 +129,12 @@ type DNSConfig struct { RecursorTimeoutRaw string `mapstructure:"recursor_timeout" json:"-"` } +// HTTPConfig is used to fine tune the Http sub-system. +type HTTPConfig struct { + // ResponseHeaders are used to add HTTP header response fields to the HTTP API responses. + ResponseHeaders map[string]string `mapstructure:"response_headers"` +} + // RetryJoinEC2 is used to configure discovery of instances via Amazon's EC2 api type RetryJoinEC2 struct { // The AWS region to look for instances in @@ -365,6 +371,9 @@ type Config struct { // Domain is the DNS domain for the records. Defaults to "consul." Domain string `mapstructure:"domain"` + // HTTP configuration + HTTPConfig HTTPConfig `mapstructure:"http_config"` + // Encryption key to use for the Serf communication EncryptKey string `mapstructure:"encrypt" json:"-"` @@ -706,9 +715,6 @@ type Config struct { // send with the update check. This is used to deduplicate messages. DisableAnonymousSignature bool `mapstructure:"disable_anonymous_signature"` - // HTTPAPIResponseHeaders are used to add HTTP header response fields to the HTTP API responses. - HTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"` - // AEInterval controls the anti-entropy interval. This is how often // the agent attempts to reconcile its local state with the server's // representation of our state. Defaults to every 60s. @@ -759,11 +765,12 @@ type Config struct { // deprecated fields // keep them exported since otherwise the error messages don't show up - DeprecatedAtlasInfrastructure string `mapstructure:"atlas_infrastructure" json:"-"` - DeprecatedAtlasToken string `mapstructure:"atlas_token" json:"-"` - DeprecatedAtlasACLToken string `mapstructure:"atlas_acl_token" json:"-"` - DeprecatedAtlasJoin bool `mapstructure:"atlas_join" json:"-"` - DeprecatedAtlasEndpoint string `mapstructure:"atlas_endpoint" json:"-"` + DeprecatedAtlasInfrastructure string `mapstructure:"atlas_infrastructure" json:"-"` + DeprecatedAtlasToken string `mapstructure:"atlas_token" json:"-"` + DeprecatedAtlasACLToken string `mapstructure:"atlas_acl_token" json:"-"` + DeprecatedAtlasJoin bool `mapstructure:"atlas_join" json:"-"` + DeprecatedAtlasEndpoint string `mapstructure:"atlas_endpoint" json:"-"` + DeprecatedHTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"` } // IncomingHTTPSConfig returns the TLS configuration for HTTPS @@ -1368,6 +1375,19 @@ func DecodeConfig(r io.Reader) (*Config, error) { result.TLSCipherSuites = ciphers } + // This is for backwards compatibility. + // HTTPAPIResponseHeaders has been replaced with HTTPConfig.ResponseHeaders + if len(result.DeprecatedHTTPAPIResponseHeaders) > 0 { + fmt.Fprintln(os.Stderr, "==> DEPRECATION: http_api_response_headers is deprecated and "+ + "is no longer used. Please use http_config.response_headers instead.") + if result.HTTPConfig.ResponseHeaders == nil { + result.HTTPConfig.ResponseHeaders = make(map[string]string) + } + for field, value := range result.DeprecatedHTTPAPIResponseHeaders { + result.HTTPConfig.ResponseHeaders[field] = value + } + result.DeprecatedHTTPAPIResponseHeaders = nil + } return &result, nil } @@ -1974,12 +1994,12 @@ func MergeConfig(a, b *Config) *Config { result.SessionTTLMin = b.SessionTTLMin result.SessionTTLMinRaw = b.SessionTTLMinRaw } - if len(b.HTTPAPIResponseHeaders) != 0 { - if result.HTTPAPIResponseHeaders == nil { - result.HTTPAPIResponseHeaders = make(map[string]string) + if len(b.HTTPConfig.ResponseHeaders) > 0 { + if result.HTTPConfig.ResponseHeaders == nil { + result.HTTPConfig.ResponseHeaders = make(map[string]string) } - for field, value := range b.HTTPAPIResponseHeaders { - result.HTTPAPIResponseHeaders[field] = value + for field, value := range b.HTTPConfig.ResponseHeaders { + result.HTTPConfig.ResponseHeaders[field] = value } } if len(b.Meta) != 0 { diff --git a/agent/config_test.go b/agent/config_test.go index 67f2d0387b..a573d63987 100644 --- a/agent/config_test.go +++ b/agent/config_test.go @@ -332,7 +332,11 @@ func TestDecodeConfig(t *testing.T) { }, { in: `{"http_api_response_headers":{"a":"b","c":"d"}}`, - c: &Config{HTTPAPIResponseHeaders: map[string]string{"a": "b", "c": "d"}}, + c: &Config{HTTPConfig: HTTPConfig{ResponseHeaders: map[string]string{"a": "b", "c": "d"}}}, + }, + { + in: `{"http_config":{"response_headers":{"a":"b","c":"d"}}}`, + c: &Config{HTTPConfig: HTTPConfig{ResponseHeaders: map[string]string{"a": "b", "c": "d"}}}, }, { in: `{"key_file":"a"}`, @@ -1389,8 +1393,10 @@ func TestMergeConfig(t *testing.T) { }, DisableUpdateCheck: true, DisableAnonymousSignature: true, - HTTPAPIResponseHeaders: map[string]string{ - "Access-Control-Allow-Origin": "*", + HTTPConfig: HTTPConfig{ + ResponseHeaders: map[string]string{ + "Access-Control-Allow-Origin": "*", + }, }, UnixSockets: UnixSocketConfig{ UnixSocketPermissions{ diff --git a/agent/http.go b/agent/http.go index d622958c9c..5a041dafcd 100644 --- a/agent/http.go +++ b/agent/http.go @@ -162,7 +162,7 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { // wrap is used to wrap functions to make them more convenient func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) http.HandlerFunc { return func(resp http.ResponseWriter, req *http.Request) { - setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders) + setHeaders(resp, s.agent.config.HTTPConfig.ResponseHeaders) setTranslateAddr(resp, s.agent.config.TranslateWanAddrs) // Obfuscate any tokens from appearing in the logs diff --git a/agent/http_test.go b/agent/http_test.go index 3c02d75a58..01970a348a 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -241,7 +241,7 @@ func TestHTTPAPI_TranslateAddrHeader(t *testing.T) { func TestHTTPAPIResponseHeaders(t *testing.T) { t.Parallel() cfg := TestConfig() - cfg.HTTPAPIResponseHeaders = map[string]string{ + cfg.HTTPConfig.ResponseHeaders = map[string]string{ "Access-Control-Allow-Origin": "*", "X-XSS-Protection": "1; mode=block", } diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index 4bb839304a..2122b31fda 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -199,7 +199,7 @@ will exit with an error at startup. - Shared credentials file (`~/.aws/credentials` or the path specified by `AWS_SHARED_CREDENTIALS_FILE`) - ECS task role metadata (container-specific). - EC2 instance role metadata. - + The only required IAM permission is `ec2:DescribeInstances`, and it is recommended you make a dedicated key used only for auto-joining. @@ -437,7 +437,7 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass "allow" or "deny"; defaults to "allow". The default policy controls the behavior of a token when there is no matching rule. In "allow" mode, ACLs are a blacklist: any operation not specifically prohibited is allowed. In "deny" mode, ACLs are a whitelist: any operation not - specifically allowed is blocked. *Note*: this will not take effect until you've set `acl_datacenter` + specifically allowed is blocked. *Note*: this will not take effect until you've set `acl_datacenter` to enable ACL support. * `acl_down_policy` - Either @@ -745,6 +745,30 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass } ``` + This has been deprecated in Consul 0.9.0. Setting this value will set `http_config.response_headers` + instead for backwards compatibility. + +* `http_config` + This object allows setting options for the HTTP API. +

+ The following sub-keys are available: + + * `response_headers` + This object allows adding headers to the HTTP API responses. + For example, the following config can be used to enable + [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) on + the HTTP API endpoints: + + ```javascript + { + "http_config": { + "response_headers": { + "Access-Control-Allow-Origin": "*" + } + } + } + ``` + * `leave_on_terminate` If enabled, when the agent receives a TERM signal, it will send a `Leave` message to the rest of the cluster and gracefully leave. The default behavior for this feature varies based on