diff --git a/driver/configuration/config_keys.go b/driver/configuration/config_keys.go index 6fac519d5c..88e725ba06 100644 --- a/driver/configuration/config_keys.go +++ b/driver/configuration/config_keys.go @@ -11,6 +11,7 @@ const ( ProxyIdleTimeout Key = "serve.proxy.timeout.idle" ProxyServeAddressHost Key = "serve.proxy.host" ProxyServeAddressPort Key = "serve.proxy.port" + ProxyTrustForwardedHeaders Key = "serve.proxy.trust_forwarded_headers" APIServeAddressHost Key = "serve.api.host" APIServeAddressPort Key = "serve.api.port" APIReadTimeout Key = "serve.api.timeout.read" diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 2b66517b97..4293d97092 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -43,6 +43,8 @@ type Provider interface { CORSOptions(iface string) cors.Options CORS(iface string) (cors.Options, bool) + ProxyTrustForwardedHeaders() bool + ProviderAuthenticators ProviderErrorHandlers ProviderAuthorizers diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index 2589b3a1ca..35795f03d6 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -384,6 +384,10 @@ func (v *KoanfProvider) AuthenticatorIsEnabled(id string) bool { return v.pipelineIsEnabled("authenticators", id) } +func (v *KoanfProvider) ProxyTrustForwardedHeaders() bool { + return v.source.Bool(ProxyTrustForwardedHeaders) +} + func (v *KoanfProvider) AuthenticatorConfig(id string, override json.RawMessage, dest interface{}) error { return v.PipelineConfig("authenticators", id, override, dest) } diff --git a/driver/registry_memory.go b/driver/registry_memory.go index b073833ec7..9bcf90ec4d 100644 --- a/driver/registry_memory.go +++ b/driver/registry_memory.go @@ -326,7 +326,7 @@ func (r *RegistryMemory) AvailablePipelineMutators() (available []string) { func (r *RegistryMemory) Proxy() *proxy.Proxy { if r.proxyProxy == nil { - r.proxyProxy = proxy.NewProxy(r) + r.proxyProxy = proxy.NewProxy(r, r.c) } return r.proxyProxy diff --git a/proxy/proxy.go b/proxy/proxy.go index 3f9bea36bf..4e9097f7eb 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -5,6 +5,7 @@ package proxy import ( "context" + "github.com/ory/oathkeeper/driver/configuration" "io" "net/http" "net/http/httputil" @@ -22,17 +23,17 @@ import ( type proxyRegistry interface { x.RegistryLogger x.RegistryWriter - ProxyRequestHandler() RequestHandler RuleMatcher() rule.Matcher } -func NewProxy(r proxyRegistry) *Proxy { - return &Proxy{r: r} +func NewProxy(r proxyRegistry, c configuration.Provider) *Proxy { + return &Proxy{r: r, c: c} } type Proxy struct { r proxyRegistry + c configuration.Provider } type key int @@ -107,6 +108,10 @@ func (d *Proxy) RoundTrip(r *http.Request) (*http.Response, error) { } func (d *Proxy) Rewrite(r *httputil.ProxyRequest) { + if d.c.ProxyTrustForwardedHeaders() { + r.SetXForwarded() + } + EnrichRequestedURL(r) rl, err := d.r.RuleMatcher().Match(r.Out.Context(), r.Out.Method, r.Out.URL, rule.ProtocolHTTP) if err != nil { diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index ce9a1e3403..816c2174ca 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -93,10 +93,12 @@ func TestProxy(t *testing.T) { url string code int messages []string + messagesNot []string rulesRegexp []rule.Rule rulesGlob []rule.Rule transform func(r *http.Request) d string + prep func(t *testing.T) }{ { d: "should fail because url does not exist in rule set", @@ -201,6 +203,62 @@ func TestProxy(t *testing.T) { "host=" + x.ParseURLOrPanic(backend.URL).Host, }, }, + { + d: "should pass and set x-forwarded headers", + prep: func(t *testing.T) { + conf.SetForTest(t, configuration.ProxyTrustForwardedHeaders, true) + }, + transform: func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "foobar.com") + }, + url: ts.URL + "/authn-anon/authz-allow/cred-noop/1234", + rulesRegexp: []rule.Rule{{ + Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"}, + Authenticators: []rule.Handler{{Handler: "anonymous"}}, + Authorizer: rule.Handler{Handler: "allow"}, + Mutators: []rule.Handler{{Handler: "noop"}}, + Upstream: rule.Upstream{URL: backend.URL}, + }}, + rulesGlob: []rule.Rule{{ + Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]*>"}, + Authenticators: []rule.Handler{{Handler: "anonymous"}}, + Authorizer: rule.Handler{Handler: "allow"}, + Mutators: []rule.Handler{{Handler: "noop"}}, + Upstream: rule.Upstream{URL: backend.URL}, + }}, + code: http.StatusOK, + messages: []string{ + "authorization=", + "url=/authn-anon/authz-allow/cred-noop/1234", + "host=" + x.ParseURLOrPanic(backend.URL).Host, + "header X-Forwarded-Proto=http", + }, + }, + { + d: "should pass and remove x-forwarded headers", + transform: func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "foobar.com") + }, + url: ts.URL + "/authn-anon/authz-allow/cred-noop/1234", + rulesRegexp: []rule.Rule{{ + Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]+>"}, + Authenticators: []rule.Handler{{Handler: "anonymous"}}, + Authorizer: rule.Handler{Handler: "allow"}, + Mutators: []rule.Handler{{Handler: "noop"}}, + Upstream: rule.Upstream{URL: backend.URL}, + }}, + rulesGlob: []rule.Rule{{ + Match: &rule.Match{Methods: []string{"GET"}, URL: ts.URL + "/authn-anon/authz-allow/cred-noop/<[0-9]*>"}, + Authenticators: []rule.Handler{{Handler: "anonymous"}}, + Authorizer: rule.Handler{Handler: "allow"}, + Mutators: []rule.Handler{{Handler: "noop"}}, + Upstream: rule.Upstream{URL: backend.URL}, + }}, + code: http.StatusOK, + messagesNot: []string{ + "header X-Forwarded-Proto=http", + }, + }, { d: "should fail when authorizer fails", url: ts.URL + "/authn-anon/authz-deny/cred-noop/1234", @@ -339,6 +397,10 @@ func TestProxy(t *testing.T) { } { t.Run(fmt.Sprintf("description=%s", tc.d), func(t *testing.T) { testFunc := func(t *testing.T, strategy configuration.MatchingStrategy, rules []rule.Rule) { + if tc.prep != nil { + tc.prep(t) + } + reg.RuleRepository().(*rule.RepositoryMemory).WithRules(rules) require.NoError(t, reg.RuleRepository().SetMatchingStrategy(context.Background(), strategy)) @@ -361,6 +423,13 @@ func TestProxy(t *testing.T) { %s proxy_url=%s backend_url=%s +`, m, greeting, ts.URL, backend.URL) + } + for _, m := range tc.messagesNot { + assert.False(t, strings.Contains(string(greeting), m), `Value "%s" found in message but not expected: +%s +proxy_url=%s +backend_url=%s `, m, greeting, ts.URL, backend.URL) } diff --git a/spec/config.schema.json b/spec/config.schema.json index 3a90a77a6b..77c3188b4b 100644 --- a/spec/config.schema.json +++ b/spec/config.schema.json @@ -16,7 +16,11 @@ "default": "5s", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "description": "The maximum duration for reading the entire request, including the body.", - "examples": ["5s", "5m", "5h"] + "examples": [ + "5s", + "5m", + "5h" + ] }, "write": { "title": "HTTP Write Timeout", @@ -24,7 +28,11 @@ "default": "120s", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "description": "The maximum duration before timing out writes of the response. Increase this parameter to prevent unexpected closing a client connection if an upstream request is responding slowly.", - "examples": ["5s", "5m", "5h"] + "examples": [ + "5s", + "5m", + "5h" + ] }, "idle": { "title": "HTTP Idle Timeout", @@ -32,7 +40,11 @@ "default": "120s", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "description": " The maximum amount of time to wait for any action of a request session, reading data or writing the response.", - "examples": ["5s", "5m", "5h"] + "examples": [ + "5s", + "5m", + "5h" + ] } } }, @@ -59,7 +71,9 @@ "path": { "title": "Path to PEM-encoded Fle", "type": "string", - "examples": ["path/to/file.pem"] + "examples": [ + "path/to/file.pem" + ] }, "base64": { "title": "Base64 Encoded Inline", @@ -114,7 +128,9 @@ "type": "string", "minLength": 1 }, - "default": ["*"], + "default": [ + "*" + ], "uniqueItems": true, "examples": [ [ @@ -142,7 +158,13 @@ ] }, "uniqueItems": true, - "default": ["GET", "POST", "PUT", "PATCH", "DELETE"] + "default": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE" + ] }, "allowed_headers": { "description": "A list of non simple headers the client is allowed to use with cross-domain requests.", @@ -153,7 +175,10 @@ }, "minLength": 1, "uniqueItems": true, - "default": ["Authorization", "Content-Type"] + "default": [ + "Authorization", + "Content-Type" + ] }, "exposed_headers": { "description": "Indicates which headers are safe to expose to the API of a CORS API specification", @@ -164,7 +189,9 @@ }, "minLength": 1, "uniqueItems": true, - "default": ["Content-Type"] + "default": [ + "Content-Type" + ] }, "allow_credentials": { "type": "boolean", @@ -191,13 +218,20 @@ "title": "Enabled", "type": "boolean", "default": false, - "examples": [true], + "examples": [ + true + ], "description": "En-/disables this component." }, "scopeStrategy": { "title": "Scope Strategy", "type": "string", - "enum": ["hierarchic", "exact", "wildcard", "none"], + "enum": [ + "hierarchic", + "exact", + "wildcard", + "none" + ], "default": "none", "description": "Sets the strategy validation algorithm." }, @@ -206,7 +240,9 @@ "title": "HTTP Redirect Error Handler", "description": "This section is optional when the error handler is disabled.", "additionalProperties": false, - "required": ["to"], + "required": [ + "to" + ], "properties": { "to": { "title": "Redirect to", @@ -223,7 +259,10 @@ "title": "HTTP Redirect Status Code", "description": "Defines the HTTP Redirect status code which can bei 301 (Moved Permanently) or 302 (Found).", "type": "integer", - "enum": [301, 302], + "enum": [ + 301, + 302 + ], "default": 302 }, "return_to_query_param": { @@ -347,7 +386,11 @@ "subject": { "type": "string", "title": "Anonymous Subject", - "examples": ["guest", "anon", "unknown"], + "examples": [ + "guest", + "anon", + "unknown" + ], "default": "anonymous", "description": "Sets the anonymous username." } @@ -364,7 +407,9 @@ "type": "string", "format": "uri", "description": "The origin to proxy requests to. If the response is a 200 with body `{ \"subject\": \"...\", \"extra\": {} }`. The request will pass the subject through successfully, otherwise it will be marked as unauthorized.\n\n>If this authenticator is enabled, this value is required.", - "examples": ["https://session-store-host"] + "examples": [ + "https://session-store-host" + ] }, "only": { "type": "array", @@ -395,7 +440,10 @@ "title": "Force HTTP Method", "type": "string", "description": "When set uses the given HTTP method instead of the request HTTP method.", - "examples": ["GET", "POST"] + "examples": [ + "GET", + "POST" + ] }, "forward_http_headers": { "title": "Set Forward HTTP Headers", @@ -426,7 +474,9 @@ "default": "subject" } }, - "required": ["check_session_url"], + "required": [ + "check_session_url" + ], "additionalProperties": false }, "configAuthenticatorsBearerToken": { @@ -439,7 +489,9 @@ "type": "string", "format": "uri", "description": "The origin to proxy requests to. If the response is a 200 with body `{ \"subject\": \"...\", \"extra\": {} }`. The request will pass the subject through successfully, otherwise it will be marked as unauthorized.\n\n>If this authenticator is enabled, this value is required.", - "examples": ["https://session-store-host"] + "examples": [ + "https://session-store-host" + ] }, "token_from": { "title": "Token From", @@ -503,7 +555,10 @@ "title": "Force HTTP Method", "type": "string", "description": "When set uses the given HTTP method instead of the request HTTP method.", - "examples": ["GET", "POST"] + "examples": [ + "GET", + "POST" + ] }, "forward_http_headers": { "title": "Set Forward HTTP Headers", @@ -534,14 +589,18 @@ "default": "sub" } }, - "required": ["check_session_url"], + "required": [ + "check_session_url" + ], "additionalProperties": false }, "configAuthenticatorsJwt": { "type": "object", "title": "JWT Authenticator Configuration", "description": "This section is optional when the authenticator is disabled.", - "required": ["jwks_urls"], + "required": [ + "jwks_urls" + ], "properties": { "required_scope": { "type": "array", @@ -592,14 +651,20 @@ "type": "string", "description": "The configuration which sets the max wait threshold when fetching new JWKs", "default": "1s", - "examples": ["100ms", "1s"] + "examples": [ + "100ms", + "1s" + ] }, "jwks_ttl": { "title": "JWK cache TTL configuration", "type": "string", "description": "The time interval for which fetched JWKs are cached", "default": "30s", - "examples": ["30m", "6h"] + "examples": [ + "30m", + "6h" + ] }, "scope_strategy": { "$ref": "#/definitions/scopeStrategy" @@ -658,7 +723,9 @@ "type": "string", "description": "The OAuth 2.0 Token Endpoint that will be used to validate the client credentials.\n\n>If this authenticator is enabled, this value is required.", "format": "uri", - "examples": ["https://my-website.com/oauth2/token"] + "examples": [ + "https://my-website.com/oauth2/token" + ] }, "required_scope": { "type": "array", @@ -683,7 +750,9 @@ "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "title": "Cache Time to Live", "description": "Can override the default behaviour of using the token exp time, and specify a set time to live for the token in the cache. If the token exp time is lower than the set value the token exp time will be used instead.", - "examples": ["5s"] + "examples": [ + "5s" + ] }, "max_tokens": { "type": "integer", @@ -694,7 +763,9 @@ } } }, - "required": ["token_url"], + "required": [ + "token_url" + ], "additionalProperties": false }, "configAuthenticatorsOauth2Introspection": { @@ -705,7 +776,9 @@ "introspection_url": { "type": "string", "format": "uri", - "examples": ["https://my-website.com/oauth2/introspection"], + "examples": [ + "https://my-website.com/oauth2/introspection" + ], "title": "OAuth 2.0 Introspection URL", "description": "The OAuth 2.0 Token Introspection endpoint URL.\n\n>If this authenticator is enabled, this value is required." }, @@ -743,7 +816,10 @@ "type": "string", "title": "OAuth 2.0 Audience", "description": "The OAuth 2.0 Audience to be requested during the OAuth 2.0 Client Credentials Grant.", - "examples": ["http://www.example.com", "services:my-app"] + "examples": [ + "http://www.example.com", + "services:my-app" + ] }, "scope": { "type": "array", @@ -752,7 +828,12 @@ }, "title": "OAuth 2.0 Scope", "description": "The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant.", - "examples": [["foo", "bar"]] + "examples": [ + [ + "foo", + "bar" + ] + ] } }, "oneOf": [ @@ -764,7 +845,11 @@ } }, { - "required": ["client_id", "client_secret", "token_url"], + "required": [ + "client_id", + "client_secret", + "token_url" + ], "properties": { "enabled": { "const": true @@ -792,7 +877,12 @@ }, "title": "OAuth 2.0 Scope", "description": "The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant.", - "examples": [["foo", "bar"]] + "examples": [ + [ + "foo", + "bar" + ] + ] } } } @@ -884,7 +974,9 @@ "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "title": "Cache Time to Live", "description": "Can override the default behaviour of using the token exp time, and specify a set time to live for the token in the cache.", - "examples": ["5s"] + "examples": [ + "5s" + ] }, "max_cost": { "type": "integer", @@ -895,7 +987,9 @@ } } }, - "required": ["introspection_url"], + "required": [ + "introspection_url" + ], "additionalProperties": false }, "configAuthorizersKetoEngineAcpOry": { @@ -908,7 +1002,9 @@ "type": "string", "format": "uri", "description": "The base URL of ORY Keto.\n\n>If this authorizer is enabled, this value is required.", - "examples": ["http://my-keto/"] + "examples": [ + "http://my-keto/" + ] }, "required_action": { "type": "string" @@ -923,7 +1019,11 @@ "type": "string" } }, - "required": ["base_url", "required_action", "required_resource"], + "required": [ + "base_url", + "required_action", + "required_resource" + ], "additionalProperties": false }, "configAuthorizersRemote": { @@ -936,7 +1036,9 @@ "type": "string", "format": "uri", "description": "The URL of the remote authorizer. The remote authorizer is expected to return either 200 OK or 403 Forbidden to allow/deny access.\n\n>If this authorizer is enabled, this value is required.", - "examples": ["https://host/path"] + "examples": [ + "https://host/path" + ] }, "headers": { "type": "object", @@ -959,7 +1061,9 @@ "$ref": "#/definitions/retry" } }, - "required": ["remote"], + "required": [ + "remote" + ], "additionalProperties": false }, "configAuthorizersRemoteJSON": { @@ -972,13 +1076,17 @@ "type": "string", "format": "uri", "description": "The URL of the remote authorizer. The remote authorizer is expected to return either 200 OK or 403 Forbidden to allow/deny access.\n\n>If this authorizer is enabled, this value is required.", - "examples": ["https://host/path"] + "examples": [ + "https://host/path" + ] }, "payload": { "title": "JSON Payload", "type": "string", "description": "The JSON payload of the request sent to the remote authorizer. The string will be parsed by the Go text/template package and applied to an AuthenticationSession object.\n\n>If this authorizer is enabled, this value is required.", - "examples": ["{\"subject\":\"{{ .Subject }}\"}"] + "examples": [ + "{\"subject\":\"{{ .Subject }}\"}" + ] }, "forward_response_headers_to_upstream": { "description": "A list of non simple headers the remote is allowed to return to mutate requests.", @@ -995,14 +1103,19 @@ "$ref": "#/definitions/retry" } }, - "required": ["remote", "payload"], + "required": [ + "remote", + "payload" + ], "additionalProperties": false }, "configMutatorsCookie": { "type": "object", "title": "Cookie Mutator Configuration", "description": "This section is optional when the mutator is disabled.", - "required": ["cookies"], + "required": [ + "cookies" + ], "properties": { "cookies": { "type": "object", @@ -1017,7 +1130,9 @@ "type": "object", "title": "Header Mutator Configuration", "description": "This section is optional when the mutator is disabled.", - "required": ["headers"], + "required": [ + "headers" + ], "properties": { "headers": { "type": "object", @@ -1035,7 +1150,9 @@ "properties": { "api": { "additionalProperties": false, - "required": ["url"], + "required": [ + "url" + ], "type": "object", "properties": { "url": { @@ -1047,7 +1164,10 @@ "additionalProperties": false, "properties": { "basic": { - "required": ["username", "password"], + "required": [ + "username", + "password" + ], "type": "object", "additionalProperties": false, "properties": { @@ -1083,14 +1203,19 @@ } } }, - "required": ["api"], + "required": [ + "api" + ], "additionalProperties": false }, "configMutatorsIdToken": { "type": "object", "title": "ID Token Mutator Configuration", "description": "This section is optional when the mutator is disabled.", - "required": ["jwks_url", "issuer_url"], + "required": [ + "jwks_url", + "issuer_url" + ], "properties": { "claims": { "type": "string" @@ -1117,7 +1242,11 @@ "description": "Sets the time-to-live of the JSON Web Token.", "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", "default": "15m", - "examples": ["1h", "1m", "30s"] + "examples": [ + "1h", + "1m", + "30s" + ] } }, "additionalProperties": false @@ -1143,7 +1272,10 @@ "host": { "type": "string", "default": "", - "examples": ["localhost", "127.0.0.1"], + "examples": [ + "localhost", + "127.0.0.1" + ], "title": "Host", "description": "The network interface to listen on." }, @@ -1172,10 +1304,19 @@ "host": { "type": "string", "default": "", - "examples": ["localhost", "127.0.0.1"], + "examples": [ + "localhost", + "127.0.0.1" + ], "title": "Host", "description": "The network interface to listen on. Leave empty to listen on all interfaces." }, + "trust_forwarded_headers": { + "type": "boolean", + "default": false, + "title": "Trust X-Forwarded Headers", + "description": "Trust the X-Forwarded-* headers from the reverse proxy. This is useful when running behind a load balancer or similar. Set this to false if you are not running behind a reverse proxy that prevents Hop-by-Hop attacks." + }, "timeout": { "$ref": "#/definitions/serverTimeout" }, @@ -1201,7 +1342,10 @@ "host": { "type": "string", "default": "", - "examples": ["localhost", "127.0.0.1"], + "examples": [ + "localhost", + "127.0.0.1" + ], "title": "Host", "description": "The network interface to listen on. Leave empty to listen on all interfaces." }, @@ -1265,8 +1409,13 @@ "description": "This an optional field describing matching strategy. Currently supported values are 'glob' and 'regexp'.", "type": "string", "default": "regexp", - "enum": ["glob", "regexp"], - "examples": ["glob"] + "enum": [ + "glob", + "regexp" + ], + "examples": [ + "glob" + ] } } }, @@ -1331,7 +1480,9 @@ "$ref": "#/definitions/configAuthenticatorsCookieSession" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1361,7 +1512,9 @@ "$ref": "#/definitions/configAuthenticatorsBearerToken" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1391,7 +1544,9 @@ "$ref": "#/definitions/configAuthenticatorsJwt" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1421,7 +1576,9 @@ "$ref": "#/definitions/configAuthenticatorsOauth2ClientCredentials" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1451,7 +1608,9 @@ "$ref": "#/definitions/configAuthenticatorsOauth2Introspection" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1476,8 +1635,14 @@ "items": { "type": "string" }, - "default": ["json"], - "examples": [["redirect"]] + "default": [ + "json" + ], + "examples": [ + [ + "redirect" + ] + ] }, "handlers": { "additionalProperties": false, @@ -1503,7 +1668,9 @@ "$ref": "#/definitions/configErrorsWWWAuthenticate" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1533,7 +1700,9 @@ "$ref": "#/definitions/configErrorsRedirect" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1564,7 +1733,9 @@ "$ref": "#/definitions/configErrorsJSON" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1630,7 +1801,9 @@ "$ref": "#/definitions/configAuthorizersKetoEngineAcpOry" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1660,7 +1833,9 @@ "$ref": "#/definitions/configAuthorizersRemote" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1690,7 +1865,9 @@ "$ref": "#/definitions/configAuthorizersRemoteJSON" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1739,7 +1916,9 @@ "$ref": "#/definitions/configMutatorsCookie" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1769,7 +1948,9 @@ "$ref": "#/definitions/configMutatorsHeader" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1799,7 +1980,9 @@ "$ref": "#/definitions/configMutatorsHydrator" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1829,7 +2012,9 @@ "$ref": "#/definitions/configMutatorsIdToken" } }, - "required": ["config"] + "required": [ + "config" + ] }, { "properties": { @@ -1852,7 +2037,11 @@ "title": "Profiling", "description": "Enables CPU or memory profiling if set. For more details on profiling Go programs read [Profiling Go Programs](https://blog.golang.org/profiling-go-programs).", "type": "string", - "enum": ["cpu", "mem", ""] + "enum": [ + "cpu", + "mem", + "" + ] }, "version": { "type": "string",