diff --git a/.schemas/authenticators.anonymous.schema.json b/.schemas/authenticators.anonymous.schema.json new file mode 100644 index 0000000000..b8fa24d84a --- /dev/null +++ b/.schemas/authenticators.anonymous.schema.json @@ -0,0 +1,22 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.anonymous.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Anonymous Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + "subject": { + "type": "string", + "title": "Anonymous Subject", + "examples": [ + "guest", + "anon", + "anonymous", + "unknown" + ], + "default": "anonymous", + "description": "Sets the anonymous username." + } + }, + "additionalProperties": false +} diff --git a/.schemas/authenticators.cookie_session.schema.json b/.schemas/authenticators.cookie_session.schema.json new file mode 100644 index 0000000000..b092d4f280 --- /dev/null +++ b/.schemas/authenticators.cookie_session.schema.json @@ -0,0 +1,31 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.cookie_session.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Cookie Session Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + "check_session_url": { + "title": "Session Check URL", + "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" + ] + }, + "only": { + "type": "array", + "items": { + "type": "string", + "additionalItems": false + }, + "title": "Only Cookies", + "description": "A list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if none of the passed cookies are set on the request." + } + }, + "required": [ + "check_session_url" + ], + "additionalProperties": false +} diff --git a/.schemas/authenticators.jwt.schema.json b/.schemas/authenticators.jwt.schema.json new file mode 100644 index 0000000000..e37c406abb --- /dev/null +++ b/.schemas/authenticators.jwt.schema.json @@ -0,0 +1,55 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.jwt.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "JWT Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + "required_scope": { + "type": "array", + "title": "Required Token Scope", + "description": "An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", + "items": { + "type": "string" + } + }, + "target_audience": { + "title": "Intended Audience", + "type": "array", + "description": "An array of audiences that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied.", + "items": { + "type": "string" + } + }, + "trusted_issuers": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_algorithms": { + "type": "array", + "items": { + "type": "string" + } + }, + "jwks_urls": { + "title": "JSON Web Key URLs", + "type": "array", + "items": { + "type": "string", + "format": "uri" + }, + "description": "URLs where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web Token. Usually something like \"https://my-keys.com/.well-known/jwks.json\". The response of that endpoint must return a JSON Web Key Set (JWKS).\n\n>If this authenticator is enabled, this value is required.", + "examples": [ + "https://my-website.com/.well-known/jwks.json", + "https://my-other-website.com/.well-known/jwks.json", + "file://path/to/local/jwks.json" + ] + }, + "scope_strategy": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/scope_strategy.schema.json#" + } + }, + "additionalProperties": false +} diff --git a/.schemas/authenticators.noop.schema.json b/.schemas/authenticators.noop.schema.json new file mode 100644 index 0000000000..860805a40d --- /dev/null +++ b/.schemas/authenticators.noop.schema.json @@ -0,0 +1,10 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.noop.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "NoOp Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + }, + "additionalProperties": false +} diff --git a/.schemas/authenticators.oauth2_client_credentials.schema.json b/.schemas/authenticators.oauth2_client_credentials.schema.json new file mode 100644 index 0000000000..a6072bba9f --- /dev/null +++ b/.schemas/authenticators.oauth2_client_credentials.schema.json @@ -0,0 +1,27 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.oauth2_client_credentials.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "OAuth 2.0 Client Credentials Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + "token_url": { + "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" + ] + }, + "required_scope": { + "type": "array", + "title": "Request Permissions (Token Scope)", + "description": "Scopes is an array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this rule.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", + "items": { + "type": "string" + } + } + }, + "required": ["token_url"], + "additionalProperties": false +} diff --git a/.schemas/authenticators.oauth2_introspection.schema.json b/.schemas/authenticators.oauth2_introspection.schema.json new file mode 100644 index 0000000000..97cf6d2b05 --- /dev/null +++ b/.schemas/authenticators.oauth2_introspection.schema.json @@ -0,0 +1,110 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.oauth2_introspection.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "OAuth 2.0 Introspection Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + "introspection_url": { + "type": "string", + "format": "uri", + "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." + }, + "scope_strategy": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/scope_strategy.schema.json#" + }, + "pre_authorization": { + "title": "Pre-Authorization", + "description": "Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant.", + "oneOf": [ + { + "type": "object", + "properties": { + "enabled": { + "title": "Enabled", + "const": false, + "default": false + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "client_id", + "client_secret", + "token_url" + ], + "properties": { + "enabled": { + "title": "Enabled", + "const": true, + "default": false + }, + "client_id": { + "type": "string", + "title": "OAuth 2.0 Client ID", + "description": "The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant.\n\n>If pre-authorization is enabled, this value is required." + }, + "client_secret": { + "type": "string", + "title": "OAuth 2.0 Client Secret", + "description": "The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant.\n\n>If pre-authorization is enabled, this value is required." + }, + "token_url": { + "type": "string", + "format": "uri", + "title": "OAuth 2.0 Token URL", + "description": "The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed.\n\n>If pre-authorization is enabled, this value is required." + }, + "scope": { + "type": "array", + "items": { + "type": "string" + }, + "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\"]" + ] + ] + } + } + } + ] + }, + "required_scope": { + "title": "Required Scope", + "description": "An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header did not request that specific scope, the request is denied.", + "type": "array", + "items": { + "type": "string" + } + }, + "target_audience": { + "title": "Target Audience", + "description": "An array of audiences that are required when accessing an endpoint protected by this handler.\n If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied.", + "type": "array", + "items": { + "type": "string" + } + }, + "trusted_issuers": { + "title": "Trusted Issuers", + "description": "The token must have been issued by one of the issuers listed in this array.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "introspection_url" + ], + "additionalProperties": false +} diff --git a/.schemas/authenticators.unauthorized.schema.json b/.schemas/authenticators.unauthorized.schema.json new file mode 100644 index 0000000000..613b2961d2 --- /dev/null +++ b/.schemas/authenticators.unauthorized.schema.json @@ -0,0 +1,10 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.unauthorized.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Unauthorized Authenticator Configuration", + "description": "This section is optional when the authenticator is disabled.", + "properties": { + }, + "additionalProperties": false +} diff --git a/.schemas/authorizers.allow.schema.json b/.schemas/authorizers.allow.schema.json new file mode 100644 index 0000000000..9471a8ddc5 --- /dev/null +++ b/.schemas/authorizers.allow.schema.json @@ -0,0 +1,10 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.allow.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Allow Authorizer Configuration", + "description": "This section is optional when the authorizer is disabled.", + "properties": { + }, + "additionalProperties": false +} diff --git a/.schemas/authorizers.deny.schema.json b/.schemas/authorizers.deny.schema.json new file mode 100644 index 0000000000..ba2690a8f7 --- /dev/null +++ b/.schemas/authorizers.deny.schema.json @@ -0,0 +1,10 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.deny.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Deny Authorizer Configuration", + "description": "This section is optional when the authorizer is disabled.", + "properties": { + }, + "additionalProperties": false +} diff --git a/.schemas/authorizers.keto_engine_acp_ory.schema.json b/.schemas/authorizers.keto_engine_acp_ory.schema.json new file mode 100644 index 0000000000..4f7c59d9f6 --- /dev/null +++ b/.schemas/authorizers.keto_engine_acp_ory.schema.json @@ -0,0 +1,36 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.keto_engine_acp_ory.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "ORY Keto Access Control Policy Authorizer Configuration", + "description": "This section is optional when the authorizer is disabled.", + "properties": { + "base_url": { + "title": "Base URL", + "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/" + ] + }, + "required_action": { + "type": "string" + }, + "required_resource": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "flavor": { + "type": "string" + } + }, + "required": [ + "base_url", + "required_action", + "required_resource" + ], + "additionalProperties": false +} diff --git a/.schemas/config.schema.json b/.schemas/config.schema.json index 3f61ce165d..9f8333af8a 100644 --- a/.schemas/config.schema.json +++ b/.schemas/config.schema.json @@ -1,9 +1,24 @@ { - "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/config.schema.json", + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/config.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "ORY Oathkeeper Configuration", "type": "object", "definitions": { + "handlerSwitch": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "title": "Enabled", + "enum": [ + false, + true + ], + "default": false, + "description": "En-/disables this component." + } + } + }, "scopeStrategy": { "title": "Scope Strategy", "type": "string", @@ -154,195 +169,150 @@ "additionalProperties": false, "properties": { "anonymous": { - "type": "object", "title": "Anonymous", "description": "The [`anonymous` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#anonymous).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "subject": { - "type": "string", - "title": "Anonymous Subject", - "examples": [ - "guest", - "anon", - "anonymous", - "unknown" - ], - "default": "anonymous", - "description": "Sets the anonymous username." - } + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.anonymous.schema.json#" } }, "noop": { - "type": "object", "title": "No Operation (noop)", "description": "The [`noop` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#noop).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - } + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.noop.schema.json#" } }, "unauthorized": { - "type": "object", "title": "Unauthorized", "description": "The [`unauthorized` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#unauthorized).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - } + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.unauthorized.schema.json#" } }, "cookie_session": { - "type": "object", "title": "Cookie Session", "description": "The [`cookie_session` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#cookie_session).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "check_session_url": { - "title": "Session Check URL", - "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" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.cookie_session.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" ] }, - "only": { - "type": "array", - "items": { - "type": "string" - }, - "title": "Only Cookies", - "description": "A list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if none of the passed cookies are set on the request." + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] }, "jwt": { - "type": "object", "title": "JSON Web Token (jwt)", "description": "The [`jwt` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#jwt).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "jwks_urls": { - "title": "JSON Web Key URLs", - "type": "array", - "items": { - "type": "string", - "format": "uri" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.jwt.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } }, - "description": "URLs where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web Token. Usually something like \"https://my-keys.com/.well-known/jwks.json\". The response of that endpoint must return a JSON Web Key Set (JWKS).\n\n>If this authenticator is enabled, this value is required.", - "examples": [ - "https://my-website.com/.well-known/jwks.json", - "https://my-other-website.com/.well-known/jwks.json", - "file://path/to/local/jwks.json" + "required": [ + "config" ] }, - "scope_strategy": { - "$ref": "#/definitions/scopeStrategy" + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] }, "oauth2_client_credentials": { - "type": "object", "title": "OAuth 2.0 Client Credentials", "description": "The [`oauth2_client_credentials` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#oauth2_client_credentials).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "token_url": { - "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" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.oauth2_client_credentials.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" ] + }, + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] }, "oauth2_introspection": { - "type": "object", "title": "OAuth 2.0 Token Introspection", "description": "The [`oauth2_introspection` authenticator](https://www.ory.sh/docs/oathkeeper/pipeline/authn#oauth2_introspection).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "introspection_url": { - "type": "string", - "format": "uri", - "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." - }, - "scope_strategy": { - "$ref": "#/definitions/scopeStrategy" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authenticators.oauth2_introspection.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" + ] }, - "pre_authorization": { - "title": "Pre-Authorization", - "description": "Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant.", - "type": "object", - "additionalProperties": false, + { "properties": { "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "client_id": { - "type": "string", - "title": "OAuth 2.0 Client ID", - "description": "The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant.\n\n>If pre-authorization is enabled, this value is required." - }, - "client_secret": { - "type": "string", - "title": "OAuth 2.0 Client Secret", - "description": "The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant.\n\n>If pre-authorization is enabled, this value is required." - }, - "token_url": { - "type": "string", - "format": "uri", - "title": "OAuth 2.0 Token URL", - "description": "The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed.\n\n>If pre-authorization is enabled, this value is required." - }, - "scope": { - "type": "array", - "items": { - "type": "string" - }, - "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\"]" - ] - ] + "const": false } } } - } + ] } } }, @@ -355,47 +325,51 @@ "allow": { "title": "Allow", "description": "The [`allow` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allow).", - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - } + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.allow.schema.json#" } }, "deny": { "title": "Deny", "description": "The [`deny` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#allow).", - "type": "object", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - } + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.deny.schema.json#" } }, "keto_engine_acp_ory": { "title": "ORY Keto Access Control Policies Engine", "description": "The [`keto_engine_acp_ory` authorizer](https://www.ory.sh/docs/oathkeeper/pipeline/authz#keto_engine_acp_ory).", - "additionalProperties": false, - "type": "object", - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "base_url": { - "title": "Base URL", - "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/" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/authorizers.keto_engine_acp_ory.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" ] + }, + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] } } }, @@ -406,93 +380,130 @@ "additionalProperties": false, "properties": { "noop": { - "type": "object", "title": "No Operation (noop)", "description": "The [`noop` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#noop).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - } + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.noop.schema.json#" } }, "cookie": { - "type": "object", "title": "HTTP Cookie", "description": "The [`cookie` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#cookie).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.cookie.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" + ] + }, + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] }, "header": { - "type": "object", "title": "HTTP Header", "description": "The [`header` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#header).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.header.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" + ] + }, + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] }, "hydrator": { - "type": "object", "title": "Hydrator", "description": "The [`hydrator` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#hydrator).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.hydrator.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" + ] + }, + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] }, "id_token": { - "type": "object", "title": "ID Token (JSON Web Token)", "description": "The [`id_token` mutator](https://www.ory.sh/docs/oathkeeper/pipeline/mutator#id_token).", - "additionalProperties": false, - "properties": { - "enabled": { - "title": "Enabled", - "type": "boolean" - }, - "issuer_url": { - "type": "string", - "format": "uri", - "title": "Issuer URL", - "description": "Sets the \"iss\" value of the ID Token.\n\n>If this mutator is enabled, this value is required." - }, - "jwks_url": { - "type": "string", - "format": "uri", - "title": "JSON Web Key URL", - "description": "Sets the URL where keys should be fetched from. Supports remote locations (http, https) as well as local filesystem paths.\n\n>If this mutator is enabled, this value is required.", - "examples": [ - "https://fetch-keys/from/this/location.json", - "file:///from/this/absolute/location.json", - "file://../from/this/relative/location.json" + "enabled": { + "$ref": "#/definitions/handlerSwitch" + }, + "config": { + "$ref": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators,id_token.schema.json#" + }, + "anyOf": [ + { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "config" ] }, - "ttl": { - "type": "string", - "title": "Expire After", - "description": "Sets the time-to-live of the JSON Web Token.", - "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", - "default": "1m", - "examples": [ - "1h", - "1m", - "30s" - ] + { + "properties": { + "enabled": { + "const": false + } + } } - } + ] } } }, diff --git a/.schemas/mutators.cookie.schema.json b/.schemas/mutators.cookie.schema.json new file mode 100644 index 0000000000..5b5b6259ed --- /dev/null +++ b/.schemas/mutators.cookie.schema.json @@ -0,0 +1,15 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.cookie.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Cookie Mutator Configuration", + "description": "This section is optional when the mutator is disabled.", + "required": ["cookies"], + "properties": { + "cookies": { + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false +} diff --git a/.schemas/mutators.header.schema.json b/.schemas/mutators.header.schema.json new file mode 100644 index 0000000000..5d882df861 --- /dev/null +++ b/.schemas/mutators.header.schema.json @@ -0,0 +1,15 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.header.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Header Mutator Configuration", + "description": "This section is optional when the mutator is disabled.", + "required": ["headers"], + "properties": { + "headers": { + "type": "object", + "additionalProperties": { "type": "string" } + } + }, + "additionalProperties": false +} diff --git a/.schemas/mutators.hydrator.schema.json b/.schemas/mutators.hydrator.schema.json new file mode 100644 index 0000000000..d0cb76a317 --- /dev/null +++ b/.schemas/mutators.hydrator.schema.json @@ -0,0 +1,64 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.hydrator.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Hydrator Mutator Configuration", + "description": "This section is optional when the mutator is disabled.", + "properties": { + "api": { + "additionalProperties": false, + "required": [ + "url" + ], + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + }, + "auth": { + "type": "object", + "additionalProperties": false, + "properties": { + "basic": { + "required": [ + "username", + "password" + ], + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + }, + "retry": { + "type": "object", + "additionalProperties": false, + "properties": { + "number_of_retries": { + "type": "number", + "minimum": 0, + "default": 100 + }, + "delay_in_milliseconds": { + "type": "number", + "minimum": 0, + "default": 3 + } + } + } + } + } + }, + "required": [ + "api" + ], + "additionalProperties": false +} diff --git a/.schemas/mutators.id_token.schema.json b/.schemas/mutators.id_token.schema.json new file mode 100644 index 0000000000..f4d113ded0 --- /dev/null +++ b/.schemas/mutators.id_token.schema.json @@ -0,0 +1,42 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.id_token.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "ID Token Mutator Configuration", + "description": "This section is optional when the mutator is disabled.", + "required": ["jwks_url","issuer_url"], + "properties": { + "claims": { + "type": "string" + }, + "issuer_url": { + "type": "string", + "title": "Issuer URL", + "description": "Sets the \"iss\" value of the ID Token.\n\n>If this mutator is enabled, this value is required." + }, + "jwks_url": { + "type": "string", + "format": "uri", + "title": "JSON Web Key URL", + "description": "Sets the URL where keys should be fetched from. Supports remote locations (http, https) as well as local filesystem paths.\n\n>If this mutator is enabled, this value is required.", + "examples": [ + "https://fetch-keys/from/this/location.json", + "file:///from/this/absolute/location.json", + "file://../from/this/relative/location.json" + ] + }, + "ttl": { + "type": "string", + "title": "Expire After", + "description": "Sets the time-to-live of the JSON Web Token.", + "pattern": "^[0-9]+(ns|us|ms|s|m|h)$", + "default": "1m", + "examples": [ + "1h", + "1m", + "30s" + ] + } + }, + "additionalProperties": false +} diff --git a/.schemas/mutators.noop.schema.json b/.schemas/mutators.noop.schema.json new file mode 100644 index 0000000000..ee7fb89ad1 --- /dev/null +++ b/.schemas/mutators.noop.schema.json @@ -0,0 +1,10 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/mutators.noop.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "NoOp Mutator Configuration", + "description": "This section is optional when the mutator is disabled.", + "properties": { + }, + "additionalProperties": false +} diff --git a/.schemas/scope_strategy.schema.json b/.schemas/scope_strategy.schema.json new file mode 100644 index 0000000000..060834f2f8 --- /dev/null +++ b/.schemas/scope_strategy.schema.json @@ -0,0 +1,14 @@ +{ + "$id": "https://raw.githubusercontent.com/ory/oathkeeper/master/.schemas/scope_strategy.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Scope Strategy", + "type": "string", + "enum": [ + "hierarchic", + "exact", + "wildcard", + "none" + ], + "default": "none", + "description": "Sets the strategy validation algorithm." +} diff --git a/CHANGELOG.md b/CHANGELOG.md index de4f66ccf9..3a3eb4f190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,16 +74,28 @@ # Change Log ## [v0.17.5-beta.1](https://github.com/ory/oathkeeper/tree/v0.17.5-beta.1) (2019-08-16) + [Full Changelog](https://github.com/ory/oathkeeper/compare/v0.17.4-beta.1...v0.17.5-beta.1) **Merged pull requests:** -- Add mutator for modifying authenticationSession with external API [\#240](https://github.com/ory/oathkeeper/pull/240) ([kubadz](https://github.com/kubadz)) -- docs: Updates issue and pull request templates [\#239](https://github.com/ory/oathkeeper/pull/239) ([aeneasr](https://github.com/aeneasr)) -- docs: Updates issue and pull request templates [\#238](https://github.com/ory/oathkeeper/pull/238) ([aeneasr](https://github.com/aeneasr)) -- docs: Updates issue and pull request templates [\#237](https://github.com/ory/oathkeeper/pull/237) ([aeneasr](https://github.com/aeneasr)) -- doc: Add adopters placeholder [\#236](https://github.com/ory/oathkeeper/pull/236) ([aeneasr](https://github.com/aeneasr)) -- support multiple mutators [\#233](https://github.com/ory/oathkeeper/pull/233) ([jakkab](https://github.com/jakkab)) +- Add mutator for modifying authenticationSession with external API + [\#240](https://github.com/ory/oathkeeper/pull/240) + ([kubadz](https://github.com/kubadz)) +- docs: Updates issue and pull request templates + [\#239](https://github.com/ory/oathkeeper/pull/239) + ([aeneasr](https://github.com/aeneasr)) +- docs: Updates issue and pull request templates + [\#238](https://github.com/ory/oathkeeper/pull/238) + ([aeneasr](https://github.com/aeneasr)) +- docs: Updates issue and pull request templates + [\#237](https://github.com/ory/oathkeeper/pull/237) + ([aeneasr](https://github.com/aeneasr)) +- doc: Add adopters placeholder + [\#236](https://github.com/ory/oathkeeper/pull/236) + ([aeneasr](https://github.com/aeneasr)) +- support multiple mutators [\#233](https://github.com/ory/oathkeeper/pull/233) + ([jakkab](https://github.com/jakkab)) ## [v0.17.4-beta.1](https://github.com/ory/oathkeeper/tree/v0.17.4-beta.1) (2019-08-09) @@ -691,375 +703,574 @@ **Closed issues:** - Make Oathkeeper work without Hydra \(Fix JWK Manager\) - [\#65](https://github.com/ory/oathkeeper/issues/65) -- Expected at least one private key - [\#61](https://github.com/ory/oathkeeper/issues/61) -- Disallow unknown JSON fields - [\#45](https://github.com/ory/oathkeeper/issues/45) -- Write AWS Lambda function for oathkeeper - [\#44](https://github.com/ory/oathkeeper/issues/44) + + Expected at least one private key [\#61](https:/ + +- Expected at least one private key Disallow unknown JSON fields + [\#45](https://gith +- Disallow unknown JSON fields Write AWS Lambda function for oathkeeper [\#44]( +- Write AWS Lambda function for oathkeeper **Merged pull requests:** + +- rsakey: Resolves iss **Merged pull requests:** - rsakey: Resolves issues with broken tests - [\#68](https://github.com/ory/oathkeeper/pull/68) - ([aeneasr](https://github.com/aeneasr)) -- cmd: Improves cors parsing [\#67](https://github.com/ory/oathkeeper/pull/67) ([aeneasr](https://github.com/aeneasr)) +- cmd: I (md: Improves cors parsing [\#67](http) +- cmd: Improves cors parsing om/aeneasr)) +- cmd: Doesn't fatal if no ORY Hydr (md: Doesn't fatal if no ORY Hydra is ) - cmd: Doesn't fatal if no ORY Hydra is unresponsive. - [\#66](https://github.com/ory/oathkeeper/pull/66) - ([aeneasr](https://github.com/aeneasr)) -- Keto [\#60](https://github.com/ory/oathkeeper/pull/60) ([aeneasr](https://github.com/aeneasr)) +- Keto [ (eto [\#60](https://github.com/ory/oat) +- Keto easr](https://github.com/aeneasr)) -## [v0.11.12](https://github.com/ory/oathkeeper/tree/v0.11.12) (2018-05-07) +## [v0.11.1 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.29...v0.11.12) +(# [v0.11.12](https://github.com/ory/o) + +## Full Changelog](https://github.com/ory/oathkeeper/compare/v (2018-05-07) + +**Closed issues:** + +- Unable to refresh RSA keys for JWK signing [\#53] **Closed issues:** -- Unable to refresh RSA keys for JWK signing - [\#53](https://github.com/ory/oathkeeper/issues/53) -- Add well known endpoint to swagger docs - [\#47](https://github.com/ory/oathkeeper/issues/47) +- Unable to refresh RSA keys for JWK signing Add well known endpoint to swagger + docs [\#47](h +- Add well known endpoint to swagger docs **Merged pull requests:** + +- Update README.md [\# **Merged pull requests:** -- Update README.md [\#58](https://github.com/ory/oathkeeper/pull/58) - ([aeneasr](https://github.com/aeneasr)) +- Update README.md //github.com/aeneasr)) +- docs: Moves documentati (ocs: Moves documentation to new repos) - docs: Moves documentation to new repository - [\#57](https://github.com/ory/oathkeeper/pull/57) - ([aeneasr](https://github.com/aeneasr)) -- Update 2-EXECUTION.md [\#56](https://github.com/ory/oathkeeper/pull/56) - ([maryoush](https://github.com/maryoush)) -- Update 2-EXECUTION.md [\#55](https://github.com/ory/oathkeeper/pull/55) - ([taland](https://github.com/taland)) -- Improve tests [\#54](https://github.com/ory/oathkeeper/pull/54) ([aeneasr](https://github.com/aeneasr)) -- cmd: correct logging typo [\#52](https://github.com/ory/oathkeeper/pull/52) - ([euank](https://github.com/euank)) +- Update (pdate 2-EXECUTION.md [\#56](https://g) +- Update 2-EXECUTION.md thub.com/maryoush)) +- Update 2-EXECUTION.md [\#5 (pdate 2-EXECUTION.md [\#55](https://git) +- Update 2-EXECUTION.md ub.com/taland)) +- Improve tests [\#54](https://g (mprove tests [\#54](https://github.) +- Improve tests ps://github.com/aeneasr)) +- cmd: correct logging (md: correct logging typo [\#52](https) +- cmd: correct logging typo m/euank)) +- docs: Adds license note to all sourc (ocs: Adds license note to all sou) - docs: Adds license note to all source files - [\#51](https://github.com/ory/oathkeeper/pull/51) - ([aeneasr](https://github.com/aeneasr)) -- ci: Resolves issue with pushing docs - [\#50](https://github.com/ory/oathkeeper/pull/50) ([aeneasr](https://github.com/aeneasr)) +- ci: Re (i: Resolves issue with pushing docs ) +- ci: Resolves issue with pushing docs ([aeneasr](https://github.com/aeneasr)) +- docs: (ocs: Adds automatic summary generatio) - docs: Adds automatic summary generation - [\#49](https://github.com/ory/oathkeeper/pull/49) ([aeneasr](https://github.com/aeneasr)) -## [v0.0.29](https://github.com/ory/oathkeeper/tree/v0.0.29) (2017-12-19) +## [v0 + +(# [v0.0.29](https://github.com/ory/oa) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.28...v0.0.29) +## Full Changelog](https://github.com/ory/oathkeeper/compare (2017-12-19) **Merged pull requests:** -- Adds use field to well known [\#48](https://github.com/ory/oathkeeper/pull/48) - ([aeneasr](https://github.com/aeneasr)) +- Adds use field to well known [\#48](https:// + +**Merged pull requests:** + +- Adds use field to well known /aeneasr)) -## [v0.0.28](https://github.com/ory/oathkeeper/tree/v0.0.28) (2017-12-19) +## [v0.0.28](https://github.com/ory -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.27...v0.0.28) +(# [v0.0.28](https://github.com/ory/oa) + +## Full Changelog](https://github.com/ory/oathkeeper/compare (2017-12-19) **Closed issues:** - Make key discovery easier with well-known feature - [\#43](https://github.com/ory/oathkeeper/issues/43) + +**Closed issues:** + +- Make key discovery easier with well-known feature **Merged pull requests:** + +- Replaces key discove **Merged pull requests:** - Replaces key discovery with well-known feature - [\#46](https://github.com/ory/oathkeeper/pull/46) ([aeneasr](https://github.com/aeneasr)) -## [v0.0.27](https://github.com/ory/oathkeeper/tree/v0.0.27) (2017-12-12) +## [v0 + +(# [v0.0.27](https://github.com/ory/oa) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.26...v0.0.27) +## Full Changelog](https://github.com/ory/oathkeeper/compare (2017-12-12) **Merged pull requests:** - Adds cors capabilities to management server - [\#40](https://github.com/ory/oathkeeper/pull/40) - ([aeneasr](https://github.com/aeneasr)) -## [v0.0.26](https://github.com/ory/oathkeeper/tree/v0.0.26) (2017-12-11) +**Merged pull requests:** + +- Adds cors capabilities to management server (# [v0 (# + [v0.0.26](https://github.) + +## [v0 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.25...v0.0.26) +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- Fixes broken image link i (2017-12-11) **Merged pull requests:** -- Fixes broken image link in docs - [\#39](https://github.com/ory/oathkeeper/pull/39) - ([aeneasr](https://github.com/aeneasr)) +- Fixes broken image link in docs [*Mer](ht -## [v0.0.25](https://github.com/ory/oathkeeper/tree/v0.0.25) (2017-11-28) +**Merged pull requests:** + +- Fixes broken image link in docs (thub.com/ory/oa) + +## Full Changelog) + +## [v0 + +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.24...v0.0.25) +- Add extra data from token (2017-11-28) **Merged pull requests:** -- Add extra data from token introspection to session - [\#37](https://github.com/ory/oathkeeper/pull/37) - ([aeneasr](https://github.com/aeneasr)) +- Add extra data from token introspection to s + +**Merged pull requests:** + +- Add extra data from token introspection to session (# [v0 (# + [v0.0.24](https://github.) + +## [v0 + +(# Full Changelog](https://github.com/o -## [v0.0.24](https://github.com/ory/oathkeeper/tree/v0.0.24) (2017-11-26) +## Full Changelog](:\*\* -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.23...v0.0.24) +- Document HYDRA_JWK_SET_ID [\#34](2017-11-26) **Closed issues:** -- Document HYDRA_JWK_SET_ID [\#34](https://github.com/ory/oathkeeper/issues/34) -- Investigate if the issuer should be oathkeeper or hydra - [\#27](https://github.com/ory/oathkeeper/issues/27) +- Document HYDRA_JWK_SET_ID [umen](HYDRA_JWK_SET_ID s + +**Closed issues:** + +- Document HYDRA_JWK_SET_ID should be oathkeeper or hydra [nves](gate if the +- Investigate if the issuer should be oathkeeper or hydra **Merged pull + requests:** + +- Telemetry [ll r](htt **Merged pull requests:** -- Telemetry [\#36](https://github.com/ory/oathkeeper/pull/36) - ([aeneasr](https://github.com/aeneasr)) +- Telemetry (](htt (# [v0.0.23](http)) + +## [ [v0.0.](htt + +(# Full Changelog](https://github.com/o -## [v0.0.23](https://github.com/ory/oathkeeper/tree/v0.0.23) (2017-11-24) +## Full Changelog](:\*\* -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.22...v0.0.23) +- Rename basicAuthorizationModeEna (2017-11-24) + +**Closed issues:** + +- Rename basicAuthorizationModeEnabled to something t **Closed issues:** - Rename basicAuthorizationModeEnabled to something that does not clash with - HTTP basic authorization [\#29](https://github.com/ory/oathkeeper/issues/29) -- Rename bypass values for better clarity - [\#13](https://github.com/ory/oathkeeper/issues/13) + HTTP basic authorization better clarity [enam](bypass values for better c +- Rename bypass values for better clarity **Merged pull requests:** + +- Print formatted outp **Merged pull requests:** -- Print formatted output string in rule management CLI - [\#35](https://github.com/ory/oathkeeper/pull/35) - ([aeneasr](https://github.com/aeneasr)) -- docs: Add JWK set docs [\#33](https://github.com/ory/oathkeeper/pull/33) - ([aeneasr](https://github.com/aeneasr)) -- Update docs and add tests [\#32](https://github.com/ory/oathkeeper/pull/32) - ([aeneasr](https://github.com/aeneasr)) +- Print formatted output string in rule management CLI (ocs: (ocs: Add JWK set + docs [\#33]) +- docs: (ocs: Add JWK set docs ub.com/aeneasr)) +- docs: Add JWK set docs ub.com/aeneasr)) +- Update docs and add tests [\# (pdate docs and add tests com/aeneasr)) +- Update docs and add tests com/aeneasr)) + +## [ [v0.0.](](https://github.co -## [v0.0.22](https://github.com/ory/oathkeeper/tree/v0.0.22) (2017-11-20) +(# Full Changelog](https://github.com/o -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.21...v0.0.22) +## Full Changelog](equests:\*\* + +- Renames bypass values for (2017-11-20) **Merged pull requests:** - Renames bypass values for better clarity - [\#31](https://github.com/ory/oathkeeper/pull/31) - ([aeneasr](https://github.com/aeneasr)) -## [v0.0.21](https://github.com/ory/oathkeeper/tree/v0.0.21) (2017-11-19) +**Merged pull requests:** + +- Renames bypass values for better clarity (# [v0 (# [v0.0.21](https://github.) + +## [v0 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.20...v0.0.21) +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- Request hydra.keys scope (2017-11-19) **Merged pull requests:** -- Request hydra.keys scope and fix panic - [\#30](https://github.com/ory/oathkeeper/pull/30) - ([aeneasr](https://github.com/aeneasr)) +- Request hydra.keys scope and fix panic [\ + +**Merged pull requests:** + +- Request hydra.keys scope and fix panic (# [v0 (# [v0.0.20](https://github.) -## [v0.0.20](https://github.com/ory/oathkeeper/tree/v0.0.20) (2017-11-18) +## [v0 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.19...v0.0.20) +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- docs: Improve swagger doc (2017-11-18) **Merged pull requests:** -- docs: Improve swagger documentation - [\#28](https://github.com/ory/oathkeeper/pull/28) - ([aeneasr](https://github.com/aeneasr)) -- cmd: Add rules management capabilities to the cli - [\#26](https://github.com/ory/oathkeeper/pull/26) - ([aeneasr](https://github.com/aeneasr)) -- unstaged [\#25](https://github.com/ory/oathkeeper/pull/25) - ([aeneasr](https://github.com/aeneasr)) +- docs: Improve swagger documentation [\*M28 + +**Merged pull requests:** -## [v0.0.19](https://github.com/ory/oathkeeper/tree/v0.0.19) (2017-11-13) +- docs: Improve swagger documentation (md: A (md: Add rules management ca) +- cmd: A (md: Add rules management capabilities) +- cmd: Add rules management capabilities to the cli (nstag (nstaged + [\#25](https://gith) +- unstag (nstaged ](https://github.com/aeneasr)) +- unstaged ](](ht (# [v0.0.19](https)) + +## [ [v0.0.](ht + +(# Full Changelog](https://github.com/o + +## Full Changelog](:\*\* + +- evaluator: token\[:5\] will caus (2017-11-13) + +**Closed issues:** -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.18...v0.0.19) +- evaluator: tokenos:5 i will cause panic [*Clo](h **Closed issues:** -- evaluator: token\[:5\] will cause panic - [\#22](https://github.com/ory/oathkeeper/issues/22) +- evaluator: tokenit:5b. will cause panic **Merged pull requests:** + +- evaluator: Use full **Merged pull requests:** -- evaluator: Use full request URL - [\#24](https://github.com/ory/oathkeeper/pull/24) - ([aeneasr](https://github.com/aeneasr)) +- evaluator: Use full request URL (thub.com/ory/oa) + +## Full Changelog) -## [v0.0.18](https://github.com/ory/oathkeeper/tree/v0.0.18) (2017-11-13) +## [v0 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.17...v0.0.18) +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- evaluator: Resolve potent (2017-11-13) **Merged pull requests:** -- evaluator: Resolve potential panic in token id generation - [\#23](https://github.com/ory/oathkeeper/pull/23) - ([aeneasr](https://github.com/aeneasr)) +- evaluator: Resolve potential panic in token -## [v0.0.17](https://github.com/ory/oathkeeper/tree/v0.0.17) (2017-11-12) +**Merged pull requests:** + +- evaluator: Resolve potential panic in token id generation (# [v0 (# + [v0.0.17](https://github.) + +## [v0 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.16...v0.0.17) +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- Introduces surrogate_id t (2017-11-12) **Merged pull requests:** -- Introduces surrogate_id to SQLManager - [\#21](https://github.com/ory/oathkeeper/pull/21) - ([aeneasr](https://github.com/aeneasr)) +- Introduces surrogate_id to SQLManager [\*M -## [v0.0.16](https://github.com/ory/oathkeeper/tree/v0.0.16) (2017-11-12) +**Merged pull requests:** + +- Introduces surrogate_id to SQLManager (# [v0 (# [v0.0.16](https://github.) + +## [v0 + +(# Full Changelog](https://github.com/o -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.15...v0.0.16) +## Full Changelog](equests:\*\* + +- Replace MatchesPath with (2017-11-12) **Merged pull requests:** -- Replace MatchesPath with MatchesURL - [\#20](https://github.com/ory/oathkeeper/pull/20) - ([aeneasr](https://github.com/aeneasr)) +- Replace MatchesPath with MatchesURL [\*M20 -## [v0.0.15](https://github.com/ory/oathkeeper/tree/v0.0.15) (2017-11-09) +**Merged pull requests:** -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.14...v0.0.15) +- Replace MatchesPath with MatchesURL (# [v0 (# [v0.0.15](https://github.) + +## [v0 + +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- Add HTTPS capabilities an (2017-11-09) **Merged pull requests:** -- Add HTTPS capabilities and document proxy/management commands - [\#19](https://github.com/ory/oathkeeper/pull/19) - ([aeneasr](https://github.com/aeneasr)) +- Add HTTPS capabilities and document proxy/ma + +**Merged pull requests:** + +- Add HTTPS capabilities and document proxy/management commands (# [v0 (# + [v0.0.14](https://github.) + +## [v0 + +(# Full Changelog](https://github.com/o -## [v0.0.14](https://github.com/ory/oathkeeper/tree/v0.0.14) (2017-11-07) +## Full Changelog](equests:\*\* -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.13...v0.0.14) +- Make refresh_delay config (2017-11-07) **Merged pull requests:** -- Make refresh_delay configurable and skip it on boot - [\#18](https://github.com/ory/oathkeeper/pull/18) - ([aeneasr](https://github.com/aeneasr)) +- Make refresh_delay configurable and skip it + +**Merged pull requests:** -## [v0.0.13](https://github.com/ory/oathkeeper/tree/v0.0.13) (2017-11-07) +- Make refresh_delay configurable and skip it on boot (# [v0 (# + [v0.0.13](https://github.) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.12...v0.0.13) +## [v0 + +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- Store rules path match in (2017-11-07) **Merged pull requests:** -- Store rules path match in plaintext - [\#17](https://github.com/ory/oathkeeper/pull/17) - ([aeneasr](https://github.com/aeneasr)) +- Store rules path match in plaintext [\*M17 + +**Merged pull requests:** -## [v0.0.12](https://github.com/ory/oathkeeper/tree/v0.0.12) (2017-11-07) +- Store rules path match in plaintext (# [v0 (# [v0.0.12](https://github.) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.11...v0.0.12) +## [v0 + +(# Full Changelog](https://github.com/o + +## Full Changelog](equests:\*\* + +- Use ladon regex compiler (2017-11-07) **Merged pull requests:** -- Use ladon regex compiler for matches - [\#16](https://github.com/ory/oathkeeper/pull/16) - ([aeneasr](https://github.com/aeneasr)) +- Use ladon regex compiler for matches [\*M1 + +**Merged pull requests:** + +- Use ladon regex compiler for matches (# [v0 (# [v0.0.11](https://github.) -## [v0.0.11](https://github.com/ory/oathkeeper/tree/v0.0.11) (2017-11-06) +## [v0 -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.10...v0.0.11) +(# Full Changelog](https://github.com/o -## [v0.0.10](https://github.com/ory/oathkeeper/tree/v0.0.10) (2017-11-06) +## Full Changelog](tps://github.com/ory/oathkeeper/tree/v0.0 (2017-11-06) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.9...v0.0.10) +## -## [v0.0.9](https://github.com/ory/oathkeeper/tree/v0.0.9) (2017-11-06) +## Full Changelog](https://github.com/ory/oathkeeper/com (2017-11-06) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.8...v0.0.9) +## Full Changelog](ps://github.com/ory/oathkeeper/tree/v0.0. (2017-11-06) -## [v0.0.8](https://github.com/ory/oathkeeper/tree/v0.0.8) (2017-11-06) +## -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.7...v0.0.8) +## Full Changelog](https://github.com/ory/oathkeeper/ (2017-11-06) + +## Full Changelog](://github.com/ory/oathkeeper/tree/v0.0. (2017-11-06) + +## # Full Changelog](https://github.com/ory/oathkeeper/com (2017-11-06) + +## Full Changelog](uests:\*\* + +- Make oathkeeper binary ex (2017-11-06) **Merged pull requests:** -- Make oathkeeper binary executable\# - [\#15](https://github.com/ory/oathkeeper/pull/15) - ([aeneasr](https://github.com/aeneasr)) +- Make oathkeeper binary executableke -## [v0.0.7](https://github.com/ory/oathkeeper/tree/v0.0.7) (2017-11-06) + [\ -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.6...v0.0.7) +**Merged pull requests:** + +- Make oathkeeper binary executablehk + + ## [Change](g](https://github.com/ory/oathkeeper/ + + (\*Merged pull requests:\*\* + +- Build o) + +## ll Changelog](eper docker image statically + +\*\*Merge (2017-11-06) **Merged pull requests:** - Build oathekeeper docker image statically - [\#14](https://github.com/ory/oathkeeper/pull/14) - ([aeneasr](https://github.com/aeneasr)) -## [v0.0.6](https://github.com/ory/oathkeeper/tree/v0.0.6) (2017-11-03) +**Merged pull requests:** + +- Build oathekeeper docker image statically + + ## [Change](g](https://github.com/ory/oathkeeper/ + + (\*Merged pull requests:\*\* -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.5...v0.0.6) +- Added s) + +## ll Changelog](ll command [\#12](https://githu + +\*\*Merg (2017-11-03) **Merged pull requests:** -- Added serve all command [\#12](https://github.com/ory/oathkeeper/pull/12) - ([aeneasr](https://github.com/aeneasr)) +- Added serve all command [ ub.](m/ory/oathkee + +**Merged pull requests:** + +- Added serve all command ub.com/ory/oathkeeper/tree/v0.0.5) (2017-11-01) + + (\*Merged pull requests:\*\* -## [v0.0.5](https://github.com/ory/oathkeeper/tree/v0.0.5) (2017-11-01) +- Add cor) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.4...v0.0.5) +## ll Changelog](ling to proxy [\#11](https://gi + +\*\*Merg (2017-11-01) **Merged pull requests:** -- Add cors handling to proxy [\#11](https://github.com/ory/oathkeeper/pull/11) - ([aeneasr](https://github.com/aeneasr)) -- Remove goveralls from circle build - [\#10](https://github.com/ory/oathkeeper/pull/10) - ([aeneasr](https://github.com/aeneasr)) -- Use circle ci build status badge - [\#9](https://github.com/ory/oathkeeper/pull/9) - ([aeneasr](https://github.com/aeneasr)) -- Switch from glide to golang/dep for vendoring - [\#8](https://github.com/ory/oathkeeper/pull/8) - ([aeneasr](https://github.com/aeneasr)) +- Add cors handling to proxy [ e b](ld [\#1 + +**Merged pull requests:** + +- Add cors handling to proxy e build [se c](cle ci build status badge [\ + + (em10](ttps:/ (s 9](m circle build ) + +- Remove goveralls from circle build Use circle ci build status badge Use circle + ci build status badge Switch from glide to +- Use circle ci build status badge Switch from glide to golang/dep for vendoring + + ( S8](h (fr7](glide to golang/dep f) + +- Switch from glide to golang/dep for vendoring Resolve tests by replacing nil + slice e tests by replacing nil slice + + ## [Change) + - Resolve tests by replacing nil slice - [\#7](https://github.com/ory/oathkeeper/pull/7) - ([aeneasr](https://github.com/aeneasr)) -## [v0.0.4](https://github.com/ory/oathkeeper/tree/v0.0.4) (2017-10-21) + ## [*Merge](g](l requests:\*\* + +- Return ) + +(\*Merged pull requests:\*\* + +- Return ) + +## ll Changelog]( instead of null on rule creati -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.3...v0.0.4) +\*\*Merg (2017-10-21) **Merged pull requests:** -- Return arrays instead of null on rule creation - [\#6](https://github.com/ory/oathkeeper/pull/6) - ([aeneasr](https://github.com/aeneasr)) +- Return arrays instead of null on rule creati + +**Merged pull requests:** + +- Return arrays instead of null on rule creation Add circleci configuration file + Add circleci configuration file + + ## circleci co + - Add circleci configuration file - [\#5](https://github.com/ory/oathkeeper/pull/5) - ([aeneasr](https://github.com/aeneasr)) -## [v0.0.3](https://github.com/ory/oathkeeper/tree/v0.0.3) (2017-10-18) + ## [x una)](g](l requests:\*\* -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.2...v0.0.3) +- Fix una) + +(\*Merged pull requests:\*\* + +- Fix una) + +## ll Changelog](zed [) + +](\*\*Merged pull request + +\*\*Merg (2017-10-18) **Merged pull requests:** -- Fix unauthorized [\#4](https://github.com/ory/oathkeeper/pull/4) - ([aeneasr](https://github.com/aeneasr)) +- Fix unauthorized [ :/](ithub.com/ory/oathkee -## [v0.0.2](https://github.com/ory/oathkeeper/tree/v0.0.2) (2017-10-12) +**Merged pull requests:** + +- Fix unauthorized ://github.com/ory/oathkeeper/tree/v0.0.2) (2017 (\*Merged + pull requests:\*\* + +- Skip ac) -[Full Changelog](https://github.com/ory/oathkeeper/compare/v0.0.1...v0.0.2) +## ll Changelog](ks [2) + +]( \*\*Merged pull request + +\*\*Merg (2017-10-12) **Merged pull requests:** -- Skip acp checks [\#3](https://github.com/ory/oathkeeper/pull/3) - ([aeneasr](https://github.com/aeneasr)) +- Skip acp checks [://](github.com/ory/oathkee -## [v0.0.1](https://github.com/ory/oathkeeper/tree/v0.0.1) (2017-10-10) +**Merged pull requests:** + +- Skip acp checks s://github.com/ory/oathkeeper/tree/v0.0.1) (201 ( travis: add + goveralls repor (2017-10) + +## erged pull requests:\*\* + +- travis: add goveralls repor (2017-10-10) **Merged pull requests:** -- travis: add goveralls report submission - [\#2](https://github.com/ory/oathkeeper/pull/2) - ([aeneasr](https://github.com/aeneasr)) -- Prototype [\#1](https://github.com/ory/oathkeeper/pull/1) - ([aeneasr](https://github.com/aeneasr)) +- travis: add goveralls report submission Prototype his Change Log was + automatically ge) +- Prototype hange Log was automatically generated by [gith (\_This Change Log + was automatically ge) -\* _This Change Log was automatically generated by -[github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)_ +\*_This Change Log was automatically generated by _ diff --git a/README.md b/README.md index a262c777c3..96f47a40c5 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,13 @@ changes in future releases. Any breaking change will have extensive documentation and upgrade instructions. [![CircleCI](https://circleci.com/gh/ory/oathkeeper.svg?style=shield&circle-token=eb458bf636326d41674141b6bbfa475a39c9db1e)](https://circleci.com/gh/ory/oathkeeper) -[![Coverage Status](https://coveralls.io/repos/github/ory/oathkeeper/badge.svg?branch=master)](https://coveralls.io/github/ory/oathkeeper?branch=master) + +![Go Report Card](https://goreportcard.com/badge/github.com/ory/oathkeeper) + +--- + + - - [Installation](#installation) -- [Who's using it?](#whos-using-it) -- [Ecosystem](#ecosystem) +- - [ORY Security Console: Administ +- - [ORY Hydra: OAuth2 & - [ORY Security Console: Administrative User Interface](#ory-security-console-administrative-user-interface) - [ORY Hydra: OAuth2 & OpenID Connect Server](#ory-hydra-oauth2--openid-connect-server) - - [ORY Keto: Access Control Policies as a Server](#ory-keto-access-control-policies-as-a-server) + - ecurity](#security) + - [Disclosing vulnerabilities](#disclosing-vulnerabilities) +- [Telemet - [Examples](#examples) - [Security](#security) - - [Disclosing vulnerabilities](#disclosing-vulnerabilities) -- [Telemetry](#telemetry) -- [Documentation](#documentation) + - ocumentation](#documentation) + - [Guide](#guide) + - [ +- - [Guide](#guide) + - +- - [HTTP API documentation](#htt - [Guide](#guide) - [HTTP API documentation](#http-api-documentation) - [Upgrading and Changelog](#upgrading-and-changelog) - - [Command line documentation](#command-line-documentation) - - [Develop](#develop) -- [Backers](#backers) + - ackers](#backers) - [Sponsors](#sponsors) + ## Installation -Head over to the -[ORY Developer Documentation](https://www.ory.sh/docs/oathkeeper/install) to -learn how to install ORY Oathkeeper on Linux, macOS, Windows, and Docker and how -to build ORY Oathkeeper from source. +Head over to the to build ORY Oathkeeper from source. + +## Who's using it? + + + - [master](#master) +- [v0.19.0-beta.1+oryOS.13](#v0190-beta1oryos13) + - [Config changes](#config-changes) + - [Hydrator Mutator](#hydrator-mutator) +- [v0.18.0-beta.1+oryOS.12](#v0180-beta1oryos12) + - [Access Rule Mutators](#access-rule-mutators) + - [`id_token` mutator now renders go templates](#id_token-mutator-now-renders-go-templates) - [v0.17.0-beta.1+oryOS.12](#v0170-beta1oryos12) - [v0.16.0-beta.1+oryOS.12](#v0160-beta1oryos12) - [Access Rule Changes](#access-rule-changes) @@ -38,6 +45,40 @@ before finalizing the upgrade process. ## master +## v0.19.0-beta.1+oryOS.13 + +### Config changes + +This release homogenizes all configuration settings. Previously all handlers (mutators, authenticators, and authorizers) +had two different types of config: global and per access rule. + +With this release, all handlers have the same configuration for global and per access rule. For example, the `id_token` +handler requires the `issuer_url`. Previously, this value was only configurable in the global config. Now, it +can be set on a per rule basis as well as globally. The global config will always be used as a fallback when no +access rule specific configuration is set. + +For this to work, the ORY Oathkeeper configuration file has changed when it comes to mutators, authenticaotrs, and +authorizers. Instead of defining the config at the same level as the `enabled` flag, it is now nested in a subkey +"config": + +``` +authorizers: + jwt: + enabled: true +- jwks_urls: +- - foo +- - bar ++ config ++ jwks_urls: ++ - foo ++ - bar +``` + +### Hydrator Mutator + +The Hydrator mutator has two configuration keys `api.retry.number` and `api.retry.delayInMilliseconds`. These have +been renamed for consistency reasons to: `api.retry.number_of_retries` and `api.retry.delay_in_milliseconds`. + ## v0.18.0-beta.1+oryOS.12 ### Access Rule Mutators @@ -47,7 +88,7 @@ before finalizing the upgrade process. forwarded. 2. The `mutator` property was renamed to `mutators` to reflect its true nature (see previous item). - + If you have existing rules, please update them as follows: ```patch @@ -89,8 +130,8 @@ The `id_token` mutator is now capable of rendering custom claims using Go [text/template](https://golang.org/pkg/text/template/) receiving the `AuthenticationSession` struct as its parameters. -To enable this change, the `aud` config was removed and the `claims` config was introduced. -The `claims` field is a raw string representing a Go template. +To enable this change, the `aud` config was removed and the `claims` config was +introduced. The `claims` field is a raw string representing a Go template. To upgrade existing rules, apply patches similar to this one: diff --git a/api/credential.go b/api/credential.go index b603fe7230..a8ca2cbc36 100644 --- a/api/credential.go +++ b/api/credential.go @@ -1,11 +1,16 @@ package api import ( + "context" "net/http" "net/url" + "github.com/tidwall/gjson" + "github.com/ory/oathkeeper/credentials" "github.com/ory/oathkeeper/driver/configuration" + "github.com/ory/oathkeeper/pipeline/mutate" + "github.com/ory/oathkeeper/rule" "github.com/ory/oathkeeper/x" "github.com/julienschmidt/httprouter" @@ -19,6 +24,7 @@ const ( type credentialHandlerRegistry interface { x.RegistryWriter credentials.FetcherRegistry + rule.Registry } type CredentialsHandler struct { @@ -49,9 +55,12 @@ func (h *CredentialsHandler) SetRoutes(r *x.RouterAPI) { // 200: jsonWebKeySet // 500: genericError func (h *CredentialsHandler) wellKnown(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - sets, err := h.r.CredentialsFetcher().ResolveSets(r.Context(), []url.URL{ - *h.c.MutatorIDTokenJWKSURL(), - }) + urls, err := h.jwksURLs() + if err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + sets, err := h.r.CredentialsFetcher().ResolveSets(r.Context(), urls) if err != nil { h.r.Writer().WriteError(w, r, err) return @@ -68,3 +77,39 @@ func (h *CredentialsHandler) wellKnown(w http.ResponseWriter, r *http.Request, _ h.r.Writer().Write(w, r, &jose.JSONWebKeySet{Keys: keys}) } + +func (h *CredentialsHandler) jwksURLs() ([]url.URL, error) { + t := map[url.URL]bool{} + for _, u := range h.c.JSONWebKeyURLs() { + t[u] = true + } + + rules, err := h.r.RuleRepository().List(context.Background(), 2147483647, 0) + if err != nil { + return nil, err + } + for _, r := range rules { + for _, m := range r.Mutators { + if m.Handler == new(mutate.MutatorIDToken).GetID() { + u := gjson.GetBytes(m.Config, "jwks_url").String() + if len(u) == 0 { + continue + } + uu, err := url.Parse(u) + if err != nil { + return nil, err + } + t[*uu] = true + } + } + } + + result := make([]url.URL, len(t)) + i := 0 + for u := range t { + result[i] = u + i++ + } + + return result, nil +} diff --git a/api/credential_test.go b/api/credential_test.go index bb6ef3644e..7ff5dfa672 100644 --- a/api/credential_test.go +++ b/api/credential_test.go @@ -1,6 +1,7 @@ package api_test import ( + "context" "crypto/rsa" "encoding/json" "net/http" @@ -15,6 +16,7 @@ import ( "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/internal" + "github.com/ory/oathkeeper/rule" "github.com/ory/oathkeeper/x" ) @@ -23,6 +25,11 @@ func TestCredentialsHandler(t *testing.T) { viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, "file://../test/stub/jwks-rsa-multiple.json") r := internal.NewRegistry(conf) + require.NoError(t, r.RuleRepository().Set( + context.Background(), + []rule.Rule{{Mutators: []rule.RuleHandler{{Handler: "id_token", Config: json.RawMessage(`{"jwks_url":"file://../test/stub/jwks-rsa-single.json"}`)}}}}), + ) + router := x.NewAPIRouter() r.CredentialHandler().SetRoutes(router) server := httptest.NewServer(router) @@ -36,8 +43,10 @@ func TestCredentialsHandler(t *testing.T) { var j jose.JSONWebKeySet require.NoError(t, json.NewDecoder(res.Body).Decode(&j)) assert.Len(t, j.Key("3e0edde4-12ad-425d-a783-135f46eac57e"), 1, "The public key should be broadcasted") + assert.Len(t, j.Key("81be3441-5303-4c52-b00d-bbdfadc75633"), 1, "The public key should be broadcasted") assert.Len(t, j.Key("f4190122-ae96-4c29-8b79-56024e459d80"), 1, "The public key generated from the private key should be broadcasted") assert.IsType(t, new(rsa.PublicKey), j.Key("3e0edde4-12ad-425d-a783-135f46eac57e")[0].Key, "Ensure a public key") assert.IsType(t, new(rsa.PublicKey), j.Key("f4190122-ae96-4c29-8b79-56024e459d80")[0].Key, "Ensure a public key") - assert.Len(t, j.Keys, 2, "There should not be any unexpected keys") + assert.IsType(t, new(rsa.PublicKey), j.Key("81be3441-5303-4c52-b00d-bbdfadc75633")[0].Key, "Ensure a public key") + assert.Len(t, j.Keys, 3, "There should not be any unexpected keys") } diff --git a/api/decision_test.go b/api/decision_test.go index 852f2fd064..59d4fe1770 100644 --- a/api/decision_test.go +++ b/api/decision_test.go @@ -28,10 +28,9 @@ import ( "github.com/ory/viper" - "github.com/ory/oathkeeper/driver/configuration" - "github.com/urfave/negroni" + "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/internal" "github.com/julienschmidt/httprouter" diff --git a/cmd/server/server.go b/cmd/server/server.go index 14e0313549..a39da4cb1d 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -129,14 +129,14 @@ func cert(daemon string, logger logrus.FieldLogger) []tls.Certificate { func clusterID(c configuration.Provider) string { var id bytes.Buffer if err := json.NewEncoder(&id).Encode(viper.AllSettings()); err != nil { - for _, repo := range append( - append(c.AccessRuleRepositories(), *c.MutatorIDTokenJWKSURL(), *c.MutatorIDTokenIssuerURL()), - c.AuthenticatorJWTJWKSURIs()..., - ) { + for _, repo := range c.AccessRuleRepositories() { _, _ = id.WriteString(repo.String()) } _, _ = id.WriteString(c.ProxyServeAddress()) _, _ = id.WriteString(c.APIServeAddress()) + _, _ = id.WriteString(viper.GetString("mutators.id_token.config.jwks_url")) + _, _ = id.WriteString(viper.GetString("mutators.id_token.config.issuer_url")) + _, _ = id.WriteString(viper.GetString("authenticators.jwt.config.jwks_urls")) } return id.String() diff --git a/cmd/tools.go b/cmd/tools.go index e9e32fad28..b187806a81 100644 --- a/cmd/tools.go +++ b/cmd/tools.go @@ -13,5 +13,5 @@ import ( _ "github.com/ory/go-acc" _ "github.com/ory/x/tools/listx" - _ "github.com/gobuffalo/packr/v2/..." + _ "github.com/gobuffalo/packr/v2" ) diff --git a/docs/.oathkeeper.yaml b/docs/.oathkeeper.yaml index 84306613a2..c3a7501141 100644 --- a/docs/.oathkeeper.yaml +++ b/docs/.oathkeeper.yaml @@ -95,39 +95,45 @@ authenticators: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true - # Sets the anonymous username. Defaults to "anonymous". Common names include "guest", "anon", "anonymous", "unknown". - subject: guest + config: + + # Sets the anonymous username. Defaults to "anonymous". Common names include "guest", "anon", "anonymous", "unknown". + subject: guest # Configures the cookie session authenticator cookie_session: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true - # Sets 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 - check_session_url: https://session-store-host + config: + + # Sets 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 + check_session_url: https://session-store-host - # Sets a list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if - # none of the passed cookies are set on the request - only: - - sessionid + # Sets a list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if + # none of the passed cookies are set on the request + only: + - sessionid # Configures the jwt authenticator jwt: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true - # REQUIRED IF ENABLED - The URL where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web - # Token. Usually something like "https://my-keys.com/.well-known/jwks.json". The response of that endpoint must - # return a JSON Web Key Set (JWKS). - jwks_urls: - - https://my-website.com/.well-known/jwks.json - - https://my-other-website.com/.well-known/jwks.json - - file://path/to/local/jwks.json + config: - # Sets the strategy to be used to validate/match the scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults - # to "none". - scope_strategy: wildcard + # REQUIRED IF ENABLED - The URL where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web + # Token. Usually something like "https://my-keys.com/.well-known/jwks.json". The response of that endpoint must + # return a JSON Web Key Set (JWKS). + jwks_urls: + - https://my-website.com/.well-known/jwks.json + - https://my-other-website.com/.well-known/jwks.json + - file://path/to/local/jwks.json + + # Sets the strategy to be used to validate/match the scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults + # to "none". + scope_strategy: wildcard # Configures the noop authenticator noop: @@ -139,40 +145,44 @@ authenticators: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true - # REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint that will be used to validate the client credentials. - token_url: https://my-website.com/oauth2/token + config: + + # REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint that will be used to validate the client credentials. + token_url: https://my-website.com/oauth2/token # Configures the oauth2_introspection authenticator oauth2_introspection: # Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false. enabled: true - # REQUIRED IF ENABLED - The OAuth 2.0 Token Introspection endpoint. - introspection_url: https://my-website.com/oauth2/introspection + config: - # Sets the strategy to be used to validate/match the token scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults - # to "none". - scope_strategy: exact + # REQUIRED IF ENABLED - The OAuth 2.0 Token Introspection endpoint. + introspection_url: https://my-website.com/oauth2/introspection - # Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer - # Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant. - pre_authorization: - # Enable pre-authorization. Defaults to false. - enabled: true + # Sets the strategy to be used to validate/match the token scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults + # to "none". + scope_strategy: exact - # REQUIRED IF ENABLED - The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant. - client_id: some_id + # Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer + # Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant. + pre_authorization: + # Enable pre-authorization. Defaults to false. + enabled: true - # REQUIRED IF ENABLED - The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant. - client_secret: some_secret + # REQUIRED IF ENABLED - The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant. + client_id: some_id - # REQUIRED IF ENABLED - The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant. - scope: - - foo - - bar + # REQUIRED IF ENABLED - The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant. + client_secret: some_secret - # REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed. - token_url: https://my-website.com/oauth2/token + # REQUIRED IF ENABLED - The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant. + scope: + - foo + - bar + + # REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed. + token_url: https://my-website.com/oauth2/token # Configures the unauthorized authenticator unauthorized: @@ -195,41 +205,53 @@ authorizers: keto_engine_acp_ory: # Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false. enabled: true - # REQUIRED IF ENABLED - The base URL of ORY Keto, typically something like http(s)://[:]/ - base_url: http://my-keto/ + config: + # REQUIRED IF ENABLED - The base URL of ORY Keto, typically something like http(s)://[:]/ + base_url: http://my-keto/ + required_action: unknown + required_resource: unknown # All mutators can be configured under this configuration key mutators: - # Configures the cookie mutator - cookie: - # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. + header: enabled: true + config: + headers: + foo: bar - # Configures the header mutator - header: + # Configures the cookie mutator + cookie: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true + config: + cookies: + foo: bar # Configures the hydrator mutator hydrator: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true + config: + api: + url: https://some-url/ + # Configures the id_token mutator id_token: # Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false. enabled: true - # REQUIRED IF ENABLED - Sets the "iss" value of the ID Token. - issuer_url: https://my-oathkeeper/ - # REQUIRED IF ENABLED - Sets the URL where keys should be fetched from. Supports remote locations (http, https) as - # well as local filesystem paths. - jwks_url: https://fetch-keys/from/this/location.json - # jwks_url: file:///from/this/absolute/location.json - # jwks_url: file://../from/this/relative/location.json - - # Sets the time-to-live of the ID token. Defaults to one minute. Valid time units are: s (second), m (minute), h (hour). - ttl: 1h + config: + # REQUIRED IF ENABLED - Sets the "iss" value of the ID Token. + issuer_url: https://my-oathkeeper/ + # REQUIRED IF ENABLED - Sets the URL where keys should be fetched from. Supports remote locations (http, https) as + # well as local filesystem paths. + jwks_url: https://fetch-keys/from/this/location.json + # jwks_url: file:///from/this/absolute/location.json + # jwks_url: file://../from/this/relative/location.json + + # Sets the time-to-live of the ID token. Defaults to one minute. Valid time units are: s (second), m (minute), h (hour). + ttl: 1h # Configures the noop mutator noop: diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 28ac7d3f1b..f65eebcef3 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -1,17 +1,20 @@ package configuration import ( + "encoding/json" "net/url" "time" + "github.com/gobuffalo/packr/v2" "github.com/sirupsen/logrus" - "github.com/rs/cors" - "golang.org/x/oauth2/clientcredentials" - "github.com/ory/fosite" + + "github.com/rs/cors" ) +var schemas = packr.New("schemas", "../../.schemas") + type Provider interface { CORSEnabled(iface string) bool CORSOptions(iface string) cors.Options @@ -28,55 +31,25 @@ type Provider interface { ProxyServeAddress() string APIServeAddress() string + + ToScopeStrategy(value string, key string) fosite.ScopeStrategy + ParseURLs(sources []string) ([]url.URL, error) + JSONWebKeyURLs() []url.URL } type ProviderAuthenticators interface { - AuthenticatorAnonymousIsEnabled() bool - AuthenticatorAnonymousIdentifier() string - - AuthenticatorNoopIsEnabled() bool - - AuthenticatorCookieSessionIsEnabled() bool - AuthenticatorCookieSessionCheckSessionURL() *url.URL - AuthenticatorCookieSessionOnly() []string - - AuthenticatorJWTIsEnabled() bool - AuthenticatorJWTJWKSURIs() []url.URL - AuthenticatorJWTScopeStrategy() fosite.ScopeStrategy - - AuthenticatorOAuth2ClientCredentialsIsEnabled() bool - AuthenticatorOAuth2ClientCredentialsTokenURL() *url.URL - - AuthenticatorOAuth2TokenIntrospectionIsEnabled() bool - AuthenticatorOAuth2TokenIntrospectionScopeStrategy() fosite.ScopeStrategy - AuthenticatorOAuth2TokenIntrospectionIntrospectionURL() *url.URL - AuthenticatorOAuth2TokenIntrospectionPreAuthorization() *clientcredentials.Config - - AuthenticatorUnauthorizedIsEnabled() bool + AuthenticatorConfig(id string, overrides json.RawMessage, destination interface{}) error + AuthenticatorIsEnabled(id string) bool } type ProviderAuthorizers interface { - AuthorizerAllowIsEnabled() bool - - AuthorizerDenyIsEnabled() bool - - AuthorizerKetoEngineACPORYIsEnabled() bool - AuthorizerKetoEngineACPORYBaseURL() *url.URL + AuthorizerConfig(id string, overrides json.RawMessage, destination interface{}) error + AuthorizerIsEnabled(id string) bool } type ProviderMutators interface { - MutatorCookieIsEnabled() bool - - MutatorHeaderIsEnabled() bool - - MutatorIDTokenIsEnabled() bool - MutatorIDTokenIssuerURL() *url.URL - MutatorIDTokenJWKSURL() *url.URL - MutatorIDTokenTTL() time.Duration - - MutatorNoopIsEnabled() bool - - MutatorHydratorIsEnabled() bool + MutatorConfig(id string, overrides json.RawMessage, destination interface{}) error + MutatorIsEnabled(id string) bool } func MustValidate(l logrus.FieldLogger, p Provider) { diff --git a/driver/configuration/provider_viper.go b/driver/configuration/provider_viper.go index db994accfb..fb3482e0d2 100644 --- a/driver/configuration/provider_viper.go +++ b/driver/configuration/provider_viper.go @@ -1,20 +1,29 @@ package configuration import ( + "bytes" + "encoding/json" "fmt" "net/url" "strings" "time" + "github.com/imdario/mergo" + "github.com/pkg/errors" "github.com/rs/cors" "github.com/sirupsen/logrus" + "github.com/ory/gojsonschema" + "github.com/ory/x/jsonx" + "github.com/ory/viper" "github.com/ory/fosite" "github.com/ory/x/corsx" "github.com/ory/x/urlx" "github.com/ory/x/viperx" + + "github.com/ory/oathkeeper/x" ) var _ Provider = new(ViperProvider) @@ -30,6 +39,53 @@ const ( ViperKeyAccessRuleRepositories = "access_rules.repositories" ) +// Authorizers +const ( + ViperKeyAuthorizerAllowIsEnabled = "authorizers.allow.enabled" + + ViperKeyAuthorizerDenyIsEnabled = "authorizers.deny.enabled" + + ViperKeyAuthorizerKetoEngineACPORYIsEnabled = "authorizers.keto_engine_acp_ory.enabled" +) + +// Mutators +const ( + ViperKeyMutatorCookieIsEnabled = "mutators.cookie.enabled" + + ViperKeyMutatorHeaderIsEnabled = "mutators.header.enabled" + + ViperKeyMutatorNoopIsEnabled = "mutators.noop.enabled" + + ViperKeyMutatorHydratorIsEnabled = "mutators.hydrator.enabled" + + ViperKeyMutatorIDTokenIsEnabled = "mutators.id_token.enabled" + ViperKeyMutatorIDTokenJWKSURL = "mutators.id_token.jwks_url" +) + +// Authenticators +const ( + // anonymous + ViperKeyAuthenticatorAnonymousIsEnabled = "authenticators.anonymous.enabled" + + // noop + ViperKeyAuthenticatorNoopIsEnabled = "authenticators.noop.enabled" + + // cookie session + ViperKeyAuthenticatorCookieSessionIsEnabled = "authenticators.cookie_session.enabled" + + // jwt + ViperKeyAuthenticatorJWTIsEnabled = "authenticators.jwt.enabled" + + // oauth2_client_credentials + ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled = "authenticators.oauth2_client_credentials.enabled" + + // oauth2_token_introspection + ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled = "authenticators.oauth2_introspection.enabled" + + // unauthorized + ViperKeyAuthenticatorUnauthorizedIsEnabled = "authenticators.unauthorized.enabled" +) + func BindEnvs() { if err := viper.BindEnv( ViperKeyProxyReadTimeout, @@ -40,38 +96,22 @@ func BindEnvs() { ViperKeyAPIServeAddressHost, ViperKeyAPIServeAddressPort, ViperKeyAccessRuleRepositories, - ViperKeyAuthenticatorAnonymousIsEnabled, - ViperKeyAuthenticatorAnonymousIdentifier, - ViperKeyAuthenticatorNoopIsEnabled, - ViperKeyAuthenticatorCookieSessionIsEnabled, - ViperKeyAuthenticatorCookieSessionCheckSessionURL, - ViperKeyAuthenticatorCookieSessionOnly, - ViperKeyAuthenticatorJWTIsEnabled, - ViperKeyAuthenticatorJWTJWKSURIs, - ViperKeyAuthenticatorJWTScopeStrategy, - ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, - ViperKeyAuthenticatorClientCredentialsTokenURL, - ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, - ViperKeyAuthenticatorOAuth2TokenIntrospectionScopeStrategy, - ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationEnabled, - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientID, - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientSecret, - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationScope, - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationTokenURL, - ViperKeyAuthenticatorUnauthorizedIsEnabled, ViperKeyAuthorizerAllowIsEnabled, ViperKeyAuthorizerDenyIsEnabled, ViperKeyAuthorizerKetoEngineACPORYIsEnabled, - ViperKeyAuthorizerKetoEngineACPORYBaseURL, ViperKeyMutatorCookieIsEnabled, ViperKeyMutatorHeaderIsEnabled, ViperKeyMutatorNoopIsEnabled, ViperKeyMutatorHydratorIsEnabled, ViperKeyMutatorIDTokenIsEnabled, - ViperKeyMutatorIDTokenIssuerURL, ViperKeyMutatorIDTokenJWKSURL, - ViperKeyMutatorIDTokenTTL, + ViperKeyAuthenticatorAnonymousIsEnabled, + ViperKeyAuthenticatorNoopIsEnabled, + ViperKeyAuthenticatorCookieSessionIsEnabled, + ViperKeyAuthenticatorJWTIsEnabled, + ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, + ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, + ViperKeyAuthenticatorUnauthorizedIsEnabled, ); err != nil { panic(err.Error()) } @@ -131,6 +171,19 @@ func (v *ViperProvider) APIServeAddress() string { ) } +func (v *ViperProvider) ParseURLs(sources []string) ([]url.URL, error) { + r := make([]url.URL, len(sources)) + for k, u := range sources { + p, err := url.Parse(u) + if err != nil { + return nil, err + } + r[k] = *p + } + + return r, nil +} + func (v *ViperProvider) getURL(value string, key string) *url.URL { u, err := url.ParseRequestURI(value) if err != nil { @@ -141,7 +194,7 @@ func (v *ViperProvider) getURL(value string, key string) *url.URL { return u } -func (v *ViperProvider) toScopeStrategy(value string, key string) fosite.ScopeStrategy { +func (v *ViperProvider) ToScopeStrategy(value string, key string) fosite.ScopeStrategy { switch strings.ToLower(value) { case "hierarchic": return fosite.HierarchicScopeStrategy @@ -156,3 +209,81 @@ func (v *ViperProvider) toScopeStrategy(value string, key string) fosite.ScopeSt return nil } } + +func (v *ViperProvider) pipelineIsEnabled(prefix, id string) bool { + return viperx.GetBool(v.l, fmt.Sprintf("%s.%s.enabled", prefix, id), false) +} + +func (v *ViperProvider) PipelineConfig(prefix, id string, override json.RawMessage, dest interface{}) error { + // we need to create a copy for config otherwise we will accidentally override values + config, err := x.Deepcopy(viper.GetStringMap(fmt.Sprintf("%s.%s.config", prefix, id))) + if err != nil { + return errors.WithStack(err) + } + + if len(override) != 0 { + var overrideMap map[string]interface{} + if err := json.Unmarshal(override, &overrideMap); err != nil { + return errors.WithStack(err) + } + + if err := mergo.Merge(&config, &overrideMap, mergo.WithOverride); err != nil { + return errors.WithStack(err) + } + } + + marshalled, err := json.Marshal(config) + if err != nil { + return errors.WithStack(err) + } + + if dest != nil { + if err := jsonx.NewStrictDecoder(bytes.NewBuffer(marshalled)).Decode(dest); err != nil { + return errors.WithStack(err) + } + } + + schema, err := schemas.Find(fmt.Sprintf("%s.%s.schema.json", prefix, id)) + if err != nil { + return errors.WithStack(err) + } + + if result, err := gojsonschema.Validate( + gojsonschema.NewBytesLoader(schema), + gojsonschema.NewBytesLoader(marshalled), + ); err != nil { + return errors.WithStack(err) + } else if !result.Valid() { + return errors.WithStack(result.Errors()) + } + + return nil +} + +func (v *ViperProvider) AuthenticatorIsEnabled(id string) bool { + return v.pipelineIsEnabled("authenticators", id) +} + +func (v *ViperProvider) AuthenticatorConfig(id string, override json.RawMessage, dest interface{}) error { + return v.PipelineConfig("authenticators", id, override, dest) +} + +func (v *ViperProvider) AuthorizerIsEnabled(id string) bool { + return v.pipelineIsEnabled("authorizers", id) +} + +func (v *ViperProvider) AuthorizerConfig(id string, override json.RawMessage, dest interface{}) error { + return v.PipelineConfig("authorizers", id, override, dest) +} + +func (v *ViperProvider) MutatorIsEnabled(id string) bool { + return v.pipelineIsEnabled("mutators", id) +} + +func (v *ViperProvider) MutatorConfig(id string, override json.RawMessage, dest interface{}) error { + return v.PipelineConfig("mutators", id, override, dest) +} + +func (v *ViperProvider) JSONWebKeyURLs() []url.URL { + return []url.URL{*v.getURL(viperx.GetString(v.l, ViperKeyMutatorIDTokenJWKSURL, ""), ViperKeyMutatorIDTokenJWKSURL)} +} diff --git a/driver/configuration/provider_viper_authn.go b/driver/configuration/provider_viper_authn.go deleted file mode 100644 index a0fed4fb9d..0000000000 --- a/driver/configuration/provider_viper_authn.go +++ /dev/null @@ -1,162 +0,0 @@ -package configuration - -import ( - "net/url" - - "golang.org/x/oauth2/clientcredentials" - - "github.com/ory/fosite" - "github.com/ory/x/viperx" -) - -// Authenticators -const ( - // anonymous - ViperKeyAuthenticatorAnonymousIsEnabled = "authenticators.anonymous.enabled" - ViperKeyAuthenticatorAnonymousIdentifier = "authenticators.anonymous.subject" - - // noop - ViperKeyAuthenticatorNoopIsEnabled = "authenticators.noop.enabled" - - // cookie session - ViperKeyAuthenticatorCookieSessionIsEnabled = "authenticators.cookie_session.enabled" - ViperKeyAuthenticatorCookieSessionCheckSessionURL = "authenticators.cookie_session.check_session_url" - ViperKeyAuthenticatorCookieSessionOnly = "authenticators.cookie_session.only" - - // jwt - ViperKeyAuthenticatorJWTIsEnabled = "authenticators.jwt.enabled" - ViperKeyAuthenticatorJWTJWKSURIs = "authenticators.jwt.jwks_urls" - ViperKeyAuthenticatorJWTScopeStrategy = "authenticators.jwt.scope_strategy" - - // oauth2_client_credentials - ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled = "authenticators.oauth2_client_credentials.enabled" - ViperKeyAuthenticatorClientCredentialsTokenURL = "authenticators.oauth2_client_credentials.token_url" - - // oauth2_token_introspection - ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled = "authenticators.oauth2_introspection.enabled" - ViperKeyAuthenticatorOAuth2TokenIntrospectionScopeStrategy = "authenticators.oauth2_introspection.scope_strategy" - ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL = "authenticators.oauth2_introspection.introspection_url" - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationEnabled = "authenticators.oauth2_introspection.pre_authorization.enabled" - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientID = "authenticators.oauth2_introspection.pre_authorization.client_id" - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientSecret = "authenticators.oauth2_introspection.pre_authorization.client_secret" - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationScope = "authenticators.oauth2_introspection.pre_authorization.scope" - ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationTokenURL = "authenticators.oauth2_introspection.pre_authorization.token_url" - - // unauthorized - ViperKeyAuthenticatorUnauthorizedIsEnabled = "authenticators.unauthorized.enabled" -) - -func (v *ViperProvider) AuthenticatorAnonymousIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorAnonymousIsEnabled, false) -} -func (v *ViperProvider) AuthenticatorAnonymousIdentifier() string { - return viperx.GetString(v.l, ViperKeyAuthenticatorAnonymousIdentifier, "anonymous", "AUTHENTICATOR_ANONYMOUS_USERNAME") -} - -func (v *ViperProvider) AuthenticatorNoopIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorNoopIsEnabled, false) - -} - -func (v *ViperProvider) AuthenticatorCookieSessionIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorCookieSessionIsEnabled, false) -} - -func (v *ViperProvider) AuthenticatorCookieSessionCheckSessionURL() *url.URL { - return v.getURL( - viperx.GetString(v.l, ViperKeyAuthenticatorCookieSessionCheckSessionURL, ""), - ViperKeyAuthenticatorCookieSessionCheckSessionURL, - ) - -} - -func (v *ViperProvider) AuthenticatorCookieSessionOnly() []string { - return viperx.GetStringSlice(v.l, ViperKeyAuthenticatorCookieSessionOnly, []string{}) -} - -func (v *ViperProvider) AuthenticatorJWTIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorJWTIsEnabled, false) - -} -func (v *ViperProvider) AuthenticatorJWTJWKSURIs() []url.URL { - res := make([]url.URL, 0) - for _, u := range viperx.GetStringSlice(v.l, ViperKeyAuthenticatorJWTJWKSURIs, []string{}, "AUTHENTICATOR_JWT_JWKS_URL") { - if p := v.getURL(u, ViperKeyAuthenticatorJWTJWKSURIs); p != nil { - res = append(res, *p) - } - } - return res -} - -func (v *ViperProvider) AuthenticatorJWTScopeStrategy() fosite.ScopeStrategy { - return v.toScopeStrategy( - viperx.GetString(v.l, ViperKeyAuthenticatorJWTScopeStrategy, "none", "AUTHENTICATOR_JWT_SCOPE_STRATEGY"), - ViperKeyAuthenticatorJWTScopeStrategy, - ) -} - -func (v *ViperProvider) AuthenticatorOAuth2ClientCredentialsIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, false) - -} -func (v *ViperProvider) AuthenticatorOAuth2ClientCredentialsTokenURL() *url.URL { - return v.getURL( - viperx.GetString(v.l, ViperKeyAuthenticatorClientCredentialsTokenURL, "", "AUTHENTICATOR_OAUTH2_CLIENT_CREDENTIALS_TOKEN_URL"), - ViperKeyAuthenticatorClientCredentialsTokenURL, - ) -} - -func (v *ViperProvider) AuthenticatorOAuth2TokenIntrospectionIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, false) -} -func (v *ViperProvider) AuthenticatorOAuth2TokenIntrospectionScopeStrategy() fosite.ScopeStrategy { - return v.toScopeStrategy( - viperx.GetString(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionScopeStrategy, "none", "AUTHENTICATOR_OAUTH2_INTROSPECTION_SCOPE_STRATEGY"), - ViperKeyAuthenticatorOAuth2TokenIntrospectionScopeStrategy, - ) -} -func (v *ViperProvider) AuthenticatorOAuth2TokenIntrospectionIntrospectionURL() *url.URL { - return v.getURL( - viperx.GetString(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, "", "AUTHENTICATOR_OAUTH2_INTROSPECTION_URL"), - ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, - ) -} -func (v *ViperProvider) AuthenticatorOAuth2TokenIntrospectionPreAuthorization() *clientcredentials.Config { - if !viperx.GetBool(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationEnabled, false) { - v.l.Infof("Authenticator oauth2_token_introspection did not specify pre-authorization which is thus disabled") - return nil - } - - var ( - id = viperx.GetString(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientID, "", "AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_CLIENT_ID") - secret = viperx.GetString(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientSecret, "", "AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_CLIENT_SECRET") - tu = viperx.GetString(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationTokenURL, "", "AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_TOKEN_URL") - scope = viperx.GetStringSlice(v.l, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationScope, []string{}, "AUTHENTICATOR_OAUTH2_INTROSPECTION_AUTHORIZATION_SCOPE") - ) - - if len(id) == 0 { - v.l.Errorf(`Authenticator oauth2_token_introspection has pre-authorization enabled but configuration value "%s" is missing or empty. Thus, pre-authorization is disabled.`, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientID) - return nil - } - - if len(secret) == 0 { - v.l.Errorf(`Authenticator oauth2_token_introspection has pre-authorization enabled but configuration value "%s" is missing or empty. Thus, pre-authorization is disabled.`, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientSecret) - return nil - } - - if len(tu) == 0 { - v.l.Errorf(`Authenticator oauth2_token_introspection has pre-authorization enabled but configuration value "%s" is missing or empty. Thus, pre-authorization is disabled.`, ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationTokenURL) - return nil - } - - return &clientcredentials.Config{ - ClientID: id, - ClientSecret: secret, - Scopes: scope, - TokenURL: tu, - } -} - -func (v *ViperProvider) AuthenticatorUnauthorizedIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthenticatorUnauthorizedIsEnabled, false) -} diff --git a/driver/configuration/provider_viper_authz.go b/driver/configuration/provider_viper_authz.go deleted file mode 100644 index d88064cc91..0000000000 --- a/driver/configuration/provider_viper_authz.go +++ /dev/null @@ -1,35 +0,0 @@ -package configuration - -import ( - "net/url" - - "github.com/ory/x/viperx" -) - -const ( - ViperKeyAuthorizerAllowIsEnabled = "authorizers.allow.enabled" - - ViperKeyAuthorizerDenyIsEnabled = "authorizers.deny.enabled" - - ViperKeyAuthorizerKetoEngineACPORYIsEnabled = "authorizers.keto_engine_acp_ory.enabled" - ViperKeyAuthorizerKetoEngineACPORYBaseURL = "authorizers.keto_engine_acp_ory.base_url" -) - -func (v *ViperProvider) AuthorizerAllowIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthorizerAllowIsEnabled, false) -} - -func (v *ViperProvider) AuthorizerDenyIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthorizerDenyIsEnabled, false) -} - -func (v *ViperProvider) AuthorizerKetoEngineACPORYIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyAuthorizerKetoEngineACPORYIsEnabled, false) -} - -func (v *ViperProvider) AuthorizerKetoEngineACPORYBaseURL() *url.URL { - return v.getURL( - viperx.GetString(v.l, ViperKeyAuthorizerKetoEngineACPORYBaseURL, "", "AUTHORIZER_KETO_URL"), - ViperKeyAuthorizerKetoEngineACPORYBaseURL, - ) -} diff --git a/driver/configuration/provider_viper_mutator.go b/driver/configuration/provider_viper_mutator.go deleted file mode 100644 index 08248aaa4c..0000000000 --- a/driver/configuration/provider_viper_mutator.go +++ /dev/null @@ -1,59 +0,0 @@ -package configuration - -import ( - "net/url" - "time" - - "github.com/ory/x/viperx" -) - -const ( - ViperKeyMutatorCookieIsEnabled = "mutators.cookie.enabled" - - ViperKeyMutatorHeaderIsEnabled = "mutators.header.enabled" - - ViperKeyMutatorNoopIsEnabled = "mutators.noop.enabled" - - ViperKeyMutatorHydratorIsEnabled = "mutators.hydrator.enabled" - - ViperKeyMutatorIDTokenIsEnabled = "mutators.id_token.enabled" - ViperKeyMutatorIDTokenIssuerURL = "mutators.id_token.issuer_url" - ViperKeyMutatorIDTokenJWKSURL = "mutators.id_token.jwks_url" - ViperKeyMutatorIDTokenTTL = "mutators.id_token.ttl" -) - -func (v *ViperProvider) MutatorCookieIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyMutatorCookieIsEnabled, false) -} - -func (v *ViperProvider) MutatorHeaderIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyMutatorHeaderIsEnabled, false) - -} - -func (v *ViperProvider) MutatorIDTokenIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyMutatorIDTokenIsEnabled, false) -} -func (v *ViperProvider) MutatorIDTokenIssuerURL() *url.URL { - return v.getURL( - viperx.GetString(v.l, ViperKeyMutatorIDTokenIssuerURL, "", "CREDENTIALS_ISSUER_ID_TOKEN_ISSUER"), - ViperKeyMutatorIDTokenIssuerURL, - ) -} -func (v *ViperProvider) MutatorIDTokenJWKSURL() *url.URL { - return v.getURL( - viperx.GetString(v.l, ViperKeyMutatorIDTokenJWKSURL, ""), - ViperKeyMutatorIDTokenJWKSURL, - ) -} -func (v *ViperProvider) MutatorIDTokenTTL() time.Duration { - return viperx.GetDuration(v.l, ViperKeyMutatorIDTokenTTL, time.Minute, "CREDENTIALS_ISSUER_ID_TOKEN_LIFESPAN") -} - -func (v *ViperProvider) MutatorNoopIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyMutatorNoopIsEnabled, false) -} - -func (v *ViperProvider) MutatorHydratorIsEnabled() bool { - return viperx.GetBool(v.l, ViperKeyMutatorHydratorIsEnabled, false) -} diff --git a/driver/configuration/provider_viper_private_test.go b/driver/configuration/provider_viper_private_test.go new file mode 100644 index 0000000000..7d04f9d46a --- /dev/null +++ b/driver/configuration/provider_viper_private_test.go @@ -0,0 +1,34 @@ +/* + * Copyright © 2017-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2017-2018 Aeneas Rekkas + * @license Apache-2.0 + */ + +package configuration + +import ( + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestGetURL(t *testing.T) { + v := NewViperProvider(logrus.New()) + assert.Nil(t, v.getURL("", "key")) + assert.Nil(t, v.getURL("a", "key")) +} diff --git a/driver/configuration/provider_viper_public_test.go b/driver/configuration/provider_viper_public_test.go new file mode 100644 index 0000000000..5776b21596 --- /dev/null +++ b/driver/configuration/provider_viper_public_test.go @@ -0,0 +1,329 @@ +package configuration_test + +import ( + "encoding/json" + "fmt" + "net/url" + "testing" + + "github.com/rs/cors" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ory/gojsonschema" + "github.com/ory/x/urlx" + "github.com/ory/x/viperx" + + "github.com/ory/viper" + + . "github.com/ory/oathkeeper/driver/configuration" + "github.com/ory/oathkeeper/pipeline/authn" + "github.com/ory/oathkeeper/pipeline/authz" + "github.com/ory/oathkeeper/pipeline/mutate" +) + +func TestPipelineConfig(t *testing.T) { + viper.Reset() + BindEnvs() + viperx.InitializeConfig( + "oathkeeper", + "./../../docs/", + logrus.New(), + ) + + err := viperx.Validate(gojsonschema.NewReferenceLoader("file://../../.schemas/config.schema.json")) + if err != nil { + viperx.LoggerWithValidationErrorFields(logrus.New(), err).Error("unable to validate") + } + require.NoError(t, err) + + p := NewViperProvider(logrus.New()) + + t.Run("case=should fail when invalid value is used in override", func(t *testing.T) { + res := json.RawMessage{} + require.Error(t, p.PipelineConfig("mutators", "hydrator", json.RawMessage(`{"not-api":"invalid"}`), &res)) + assert.JSONEq(t, `{"api":{"url":"https://some-url/"},"not-api":"invalid"}`, string(res)) + + require.Error(t, p.PipelineConfig("mutators", "hydrator", json.RawMessage(`{"api":{"this-key-does-not-exist":true}}`), &res)) + assert.JSONEq(t, `{"api":{"url":"https://some-url/","this-key-does-not-exist":true}}`, string(res)) + + require.Error(t, p.PipelineConfig("mutators", "hydrator", json.RawMessage(`{"api":{"url":"not-a-url"}}`), &res)) + assert.JSONEq(t, `{"api":{"url":"not-a-url"}}`, string(res)) + }) + + t.Run("case=should pass and override values", func(t *testing.T) { + var dec mutate.MutatorHydratorConfig + require.NoError(t, p.PipelineConfig("mutators", "hydrator", json.RawMessage(``), &dec)) + assert.Equal(t, "https://some-url/", dec.Api.URL) + + require.NoError(t, p.PipelineConfig("mutators", "hydrator", json.RawMessage(`{"api":{"url":"http://override-url/foo","retry":{"number_of_retries":15}}}`), &dec)) + assert.Equal(t, "http://override-url/foo", dec.Api.URL) + assert.Equal(t, 15, dec.Api.Retry.NumberOfRetries) + }) +} + +func TestViperProvider(t *testing.T) { + viper.Reset() + BindEnvs() + viperx.InitializeConfig( + "oathkeeper", + "./../../docs/", + logrus.New(), + ) + + err := viperx.Validate(gojsonschema.NewReferenceLoader("file://../../.schemas/config.schema.json")) + if err != nil { + viperx.LoggerWithValidationErrorFields(logrus.New(), err).Error("unable to validate") + } + p := NewViperProvider(logrus.New()) + + t.Run("group=serve", func(t *testing.T) { + assert.Equal(t, "127.0.0.1:1234", p.ProxyServeAddress()) + assert.Equal(t, "127.0.0.2:1235", p.APIServeAddress()) + + t.Run("group=cors", func(t *testing.T) { + assert.True(t, p.CORSEnabled("proxy")) + assert.True(t, p.CORSEnabled("api")) + + assert.Equal(t, cors.Options{ + AllowedOrigins: []string{"https://example.com", "https://*.example.com"}, + AllowedMethods: []string{"POST", "GET", "PUT", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Authorization", "Content-Type"}, + ExposedHeaders: []string{"Content-Type"}, + MaxAge: 10, + AllowCredentials: true, + OptionsPassthrough: false, + Debug: true, + }, p.CORSOptions("proxy")) + + assert.Equal(t, cors.Options{ + AllowedOrigins: []string{"https://example.org", "https://*.example.org"}, + AllowedMethods: []string{"GET", "PUT", "PATCH", "DELETE"}, + AllowedHeaders: []string{"Authorization", "Content-Type"}, + ExposedHeaders: []string{"Content-Type"}, + MaxAge: 10, + AllowCredentials: true, + OptionsPassthrough: false, + Debug: true, + }, p.CORSOptions("api")) + }) + + t.Run("group=tls", func(t *testing.T) { + for _, daemon := range []string{"proxy", "api"} { + t.Run(fmt.Sprintf("daemon="+daemon), func(t *testing.T) { + assert.Equal(t, "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3...", viper.GetString("serve."+daemon+".tls.key.base64")) + assert.Equal(t, "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr...", viper.GetString("serve."+daemon+".tls.cert.base64")) + assert.Equal(t, "/path/to/key.pem", viper.GetString("serve."+daemon+".tls.key.path")) + assert.Equal(t, "/path/to/cert.pem", viper.GetString("serve."+daemon+".tls.cert.path")) + }) + } + }) + }) + + t.Run("group=access_rules", func(t *testing.T) { + assert.Equal(t, []url.URL{ + *urlx.ParseOrPanic("file://path/to/rules.json"), + *urlx.ParseOrPanic("inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d"), + *urlx.ParseOrPanic("https://path-to-my-rules/rules.json"), + }, p.AccessRuleRepositories()) + + }) + + t.Run("group=authenticators", func(t *testing.T) { + t.Run("authenticator=anonymous", func(t *testing.T) { + a := authn.NewAuthenticatorAnonymous(p) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + assert.Equal(t, "guest", config.Subject) + }) + + t.Run("authenticator=noop", func(t *testing.T) { + a := authn.NewAuthenticatorNoOp(p) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("authenticator=cookie_session", func(t *testing.T) { + a := authn.NewAuthenticatorCookieSession(p) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + + assert.Equal(t, []string{"sessionid"}, config.Only) + assert.Equal(t, "https://session-store-host", config.CheckSessionURL) + }) + + t.Run("authenticator=jwt", func(t *testing.T) { + a := authn.NewAuthenticatorJWT(p, nil) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + + assert.Equal(t, "wildcard", config.ScopeStrategy) + assert.Equal(t, []string{ + "https://my-website.com/.well-known/jwks.json", + "https://my-other-website.com/.well-known/jwks.json", + "file://path/to/local/jwks.json", + }, config.JWKSURLs) + }) + + t.Run("authenticator=oauth2_client_credentials", func(t *testing.T) { + a := authn.NewAuthenticatorOAuth2ClientCredentials(p) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + assert.Equal(t, "https://my-website.com/oauth2/token", config.TokenURL) + }) + + t.Run("authenticator=oauth2_introspection", func(t *testing.T) { + a := authn.NewAuthenticatorOAuth2Introspection(p) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + assert.Equal(t, "https://my-website.com/oauth2/introspection", config.IntrospectionURL) + assert.Equal(t, "exact", config.ScopeStrategy) + assert.Equal(t, &authn.AuthenticatorOAuth2IntrospectionPreAuthConfiguration{ + ClientID: "some_id", + ClientSecret: "some_secret", + TokenURL: "https://my-website.com/oauth2/token", + Scope: []string{"foo", "bar"}, + Enabled: true, + }, config.PreAuth) + }) + + t.Run("authenticator=unauthorized", func(t *testing.T) { + a := authn.NewAuthenticatorUnauthorized(p) + assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) + }) + }) + + t.Run("group=authorizers", func(t *testing.T) { + t.Run("authorizer=allow", func(t *testing.T) { + a := authz.NewAuthorizerAllow(p) + assert.True(t, p.AuthorizerIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("authorizer=deny", func(t *testing.T) { + a := authz.NewAuthorizerDeny(p) + assert.True(t, p.AuthorizerIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("authorizer=keto_engine_acp_ory", func(t *testing.T) { + a := authz.NewAuthorizerKetoEngineACPORY(p) + assert.True(t, p.AuthorizerIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + + assert.EqualValues(t, "http://my-keto/", config.BaseURL) + }) + }) + + t.Run("group=mutators", func(t *testing.T) { + t.Run("mutator=noop", func(t *testing.T) { + a := mutate.NewMutatorNoop(p) + assert.True(t, p.MutatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("mutator=cookie", func(t *testing.T) { + a := mutate.NewMutatorCookie(p) + assert.True(t, p.MutatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("mutator=header", func(t *testing.T) { + a := mutate.NewMutatorHeader(p) + assert.True(t, p.MutatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("mutator=hydrator", func(t *testing.T) { + a := mutate.NewMutatorHydrator(p) + assert.True(t, p.MutatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + }) + + t.Run("mutator=id_token", func(t *testing.T) { + a := mutate.NewMutatorIDToken(p, nil) + assert.True(t, p.MutatorIsEnabled(a.GetID())) + require.NoError(t, a.Validate(nil)) + + config, err := a.Config(nil) + require.NoError(t, err) + + assert.EqualValues(t, "https://my-oathkeeper/", config.IssuerURL) + assert.EqualValues(t, "https://fetch-keys/from/this/location.json", config.JWKSURL) + assert.EqualValues(t, "1h", config.TTL) + }) + }) +} + +func TestToScopeStrategy(t *testing.T) { + v := NewViperProvider(logrus.New()) + + assert.True(t, v.ToScopeStrategy("exact", "foo")([]string{"foo"}, "foo")) + assert.True(t, v.ToScopeStrategy("hierarchic", "foo")([]string{"foo"}, "foo.bar")) + assert.True(t, v.ToScopeStrategy("wildcard", "foo")([]string{"foo.*"}, "foo.bar")) + assert.Nil(t, v.ToScopeStrategy("none", "foo")) + assert.Nil(t, v.ToScopeStrategy("whatever", "foo")) +} + +func TestAuthenticatorOAuth2TokenIntrospectionPreAuthorization(t *testing.T) { + viper.Reset() + v := NewViperProvider(logrus.New()) + viper.Set("authenticators.oauth2_introspection.enabled", true) + viper.Set("authenticators.oauth2_introspection.config.introspection_url", "http://some-url/") + + for k, tc := range []struct { + enabled bool + id string + secret string + turl string + err bool + }{ + {enabled: true, id: "", secret: "", turl: "", err: true}, + {enabled: true, id: "a", secret: "", turl: "", err: true}, + {enabled: true, id: "", secret: "b", turl: "", err: true}, + {enabled: true, id: "", secret: "", turl: "c", err: true}, + {enabled: true, id: "a", secret: "b", turl: "", err: true}, + {enabled: true, id: "", secret: "b", turl: "c", err: true}, + {enabled: true, id: "a", secret: "", turl: "c", err: true}, + {enabled: false, id: "a", secret: "b", turl: "c", err: false}, + {enabled: true, id: "a", secret: "b", turl: "https://some-url", err: false}, + } { + t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { + a := authn.NewAuthenticatorOAuth2Introspection(v) + + config, err := a.Config(json.RawMessage(fmt.Sprintf(`{ + "pre_authorization": { + "enabled": %v, + "client_id": "%v", + "client_secret": "%v", + "token_url": "%v" + } +}`, tc.enabled, tc.id, tc.secret, tc.turl))) + + if tc.err { + assert.Error(t, err, "%+v", config) + } else { + assert.NoError(t, err, "%+v", config) + } + }) + } +} diff --git a/driver/configuration/provider_viper_test.go b/driver/configuration/provider_viper_test.go deleted file mode 100644 index 2478057d28..0000000000 --- a/driver/configuration/provider_viper_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package configuration - -import ( - "fmt" - "net/url" - "reflect" - "testing" - "time" - - "github.com/rs/cors" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/oauth2/clientcredentials" - - "github.com/ory/fosite" - "github.com/ory/gojsonschema" - "github.com/ory/x/urlx" - "github.com/ory/x/viperx" - - "github.com/ory/viper" -) - -func TestViperProvider(t *testing.T) { - viper.Reset() - BindEnvs() - viperx.InitializeConfig( - "oathkeeper", - "./../../docs/", - logrus.New(), - ) - - require.NoError(t, viperx.Validate(gojsonschema.NewReferenceLoader("file://../../.schemas/config.schema.json"))) - p := NewViperProvider(logrus.New()) - - t.Run("group=serve", func(t *testing.T) { - assert.Equal(t, "127.0.0.1:1234", p.ProxyServeAddress()) - assert.Equal(t, "127.0.0.2:1235", p.APIServeAddress()) - - t.Run("group=cors", func(t *testing.T) { - assert.True(t, p.CORSEnabled("proxy")) - assert.True(t, p.CORSEnabled("api")) - - assert.Equal(t, cors.Options{ - AllowedOrigins: []string{"https://example.com", "https://*.example.com"}, - AllowedMethods: []string{"POST", "GET", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Authorization", "Content-Type"}, - ExposedHeaders: []string{"Content-Type"}, - MaxAge: 10, - AllowCredentials: true, - OptionsPassthrough: false, - Debug: true, - }, p.CORSOptions("proxy")) - - assert.Equal(t, cors.Options{ - AllowedOrigins: []string{"https://example.org", "https://*.example.org"}, - AllowedMethods: []string{"GET", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Authorization", "Content-Type"}, - ExposedHeaders: []string{"Content-Type"}, - MaxAge: 10, - AllowCredentials: true, - OptionsPassthrough: false, - Debug: true, - }, p.CORSOptions("api")) - }) - - t.Run("group=tls", func(t *testing.T) { - for _, daemon := range []string{"proxy", "api"} { - t.Run(fmt.Sprintf("daemon="+daemon), func(t *testing.T) { - assert.Equal(t, "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3...", viper.GetString("serve."+daemon+".tls.key.base64")) - assert.Equal(t, "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr...", viper.GetString("serve."+daemon+".tls.cert.base64")) - assert.Equal(t, "/path/to/key.pem", viper.GetString("serve."+daemon+".tls.key.path")) - assert.Equal(t, "/path/to/cert.pem", viper.GetString("serve."+daemon+".tls.cert.path")) - }) - } - }) - }) - - t.Run("group=access_rules", func(t *testing.T) { - assert.Equal(t, []url.URL{ - *urlx.ParseOrPanic("file://path/to/rules.json"), - *urlx.ParseOrPanic("inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d"), - *urlx.ParseOrPanic("https://path-to-my-rules/rules.json"), - }, p.AccessRuleRepositories()) - - }) - - t.Run("group=authenticators", func(t *testing.T) { - t.Run("authenticator=anonymous", func(t *testing.T) { - assert.True(t, p.AuthenticatorAnonymousIsEnabled()) - assert.Equal(t, "guest", p.AuthenticatorAnonymousIdentifier()) - }) - - t.Run("authenticator=noop", func(t *testing.T) { - assert.True(t, p.AuthenticatorNoopIsEnabled()) - }) - - t.Run("authenticator=cookie_session", func(t *testing.T) { - assert.True(t, p.AuthenticatorCookieSessionIsEnabled()) - assert.Equal(t, []string{"sessionid"}, p.AuthenticatorCookieSessionOnly()) - assert.Equal(t, urlx.ParseOrPanic("https://session-store-host"), p.AuthenticatorCookieSessionCheckSessionURL()) - }) - - t.Run("authenticator=jwt", func(t *testing.T) { - assert.True(t, p.AuthenticatorJWTIsEnabled()) - assert.True(t, reflect.ValueOf(fosite.WildcardScopeStrategy).Pointer() == reflect.ValueOf(p.AuthenticatorJWTScopeStrategy()).Pointer()) - assert.Equal(t, []url.URL{ - *urlx.ParseOrPanic("https://my-website.com/.well-known/jwks.json"), - *urlx.ParseOrPanic("https://my-other-website.com/.well-known/jwks.json"), - *urlx.ParseOrPanic("file://path/to/local/jwks.json"), - }, p.AuthenticatorJWTJWKSURIs()) - }) - - t.Run("authenticator=oauth2_client_credentials", func(t *testing.T) { - assert.True(t, p.AuthenticatorOAuth2ClientCredentialsIsEnabled()) - assert.Equal(t, urlx.ParseOrPanic("https://my-website.com/oauth2/token"), p.AuthenticatorOAuth2ClientCredentialsTokenURL()) - }) - - t.Run("authenticator=oauth2_introspection", func(t *testing.T) { - assert.True(t, p.AuthenticatorOAuth2TokenIntrospectionIsEnabled()) - assert.Equal(t, urlx.ParseOrPanic("https://my-website.com/oauth2/introspection"), p.AuthenticatorOAuth2TokenIntrospectionIntrospectionURL()) - assert.True(t, reflect.ValueOf(fosite.ExactScopeStrategy).Pointer() == reflect.ValueOf(p.AuthenticatorOAuth2TokenIntrospectionScopeStrategy()).Pointer()) - assert.Equal(t, &clientcredentials.Config{ - ClientID: "some_id", - ClientSecret: "some_secret", - TokenURL: "https://my-website.com/oauth2/token", - Scopes: []string{"foo", "bar"}, - AuthStyle: 0, - }, p.AuthenticatorOAuth2TokenIntrospectionPreAuthorization()) - }) - t.Run("authenticator=unauthorized", func(t *testing.T) { - assert.True(t, p.AuthenticatorUnauthorizedIsEnabled()) - }) - }) - - t.Run("group=authorizers", func(t *testing.T) { - t.Run("authorizer=allow", func(t *testing.T) { - assert.True(t, p.AuthorizerAllowIsEnabled()) - - }) - - t.Run("authorizer=deny", func(t *testing.T) { - assert.True(t, p.AuthorizerDenyIsEnabled()) - }) - - t.Run("authorizer=keto_engine_acp_ory", func(t *testing.T) { - assert.True(t, p.AuthorizerKetoEngineACPORYIsEnabled()) - assert.EqualValues(t, urlx.ParseOrPanic("http://my-keto/"), p.AuthorizerKetoEngineACPORYBaseURL()) - }) - }) - - t.Run("group=mutators", func(t *testing.T) { - t.Run("mutator=noop", func(t *testing.T) { - assert.True(t, p.MutatorNoopIsEnabled()) - }) - - t.Run("mutator=cookie", func(t *testing.T) { - assert.True(t, p.MutatorCookieIsEnabled()) - }) - - t.Run("mutator=header", func(t *testing.T) { - assert.True(t, p.MutatorHeaderIsEnabled()) - }) - - t.Run("mutator=hydrator", func(t *testing.T) { - assert.True(t, p.MutatorHydratorIsEnabled()) - }) - - t.Run("mutator=id_token", func(t *testing.T) { - assert.True(t, p.MutatorIDTokenIsEnabled()) - assert.EqualValues(t, urlx.ParseOrPanic("https://my-oathkeeper/"), p.MutatorIDTokenIssuerURL()) - assert.EqualValues(t, urlx.ParseOrPanic("https://fetch-keys/from/this/location.json"), p.MutatorIDTokenJWKSURL()) - assert.EqualValues(t, time.Hour, p.MutatorIDTokenTTL()) - }) - }) -} - -func TestToScopeStrategy(t *testing.T) { - v := NewViperProvider(logrus.New()) - - assert.True(t, v.toScopeStrategy("exact", "foo")([]string{"foo"}, "foo")) - assert.True(t, v.toScopeStrategy("hierarchic", "foo")([]string{"foo"}, "foo.bar")) - assert.True(t, v.toScopeStrategy("wildcard", "foo")([]string{"foo.*"}, "foo.bar")) - assert.Nil(t, v.toScopeStrategy("none", "foo")) - assert.Nil(t, v.toScopeStrategy("whatever", "foo")) -} - -func TestAuthenticatorOAuth2TokenIntrospectionPreAuthorization(t *testing.T) { - viper.Reset() - v := NewViperProvider(logrus.New()) - - for k, tc := range []struct { - enabled bool - id string - secret string - turl string - ok bool - }{ - {enabled: true, id: "", secret: "", turl: "", ok: false}, - {enabled: true, id: "a", secret: "", turl: "", ok: false}, - {enabled: true, id: "", secret: "b", turl: "", ok: false}, - {enabled: true, id: "", secret: "", turl: "c", ok: false}, - {enabled: true, id: "a", secret: "b", turl: "", ok: false}, - {enabled: true, id: "", secret: "b", turl: "c", ok: false}, - {enabled: true, id: "a", secret: "", turl: "c", ok: false}, - {enabled: false, id: "a", secret: "b", turl: "c", ok: false}, - {enabled: true, id: "a", secret: "b", turl: "c", ok: true}, - } { - t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { - viper.Set(ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationEnabled, tc.enabled) - viper.Set(ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientID, tc.id) - viper.Set(ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationClientSecret, tc.secret) - viper.Set(ViperKeyAuthenticatorOAuth2TokenIntrospectionPreAuthorizationTokenURL, tc.turl) - - c := v.AuthenticatorOAuth2TokenIntrospectionPreAuthorization() - if tc.ok { - assert.NotNil(t, c) - } else { - assert.Nil(t, c) - } - }) - } - v.AuthenticatorOAuth2TokenIntrospectionPreAuthorization() -} - -func TestGetURL(t *testing.T) { - v := NewViperProvider(logrus.New()) - assert.Nil(t, v.getURL("", "key")) - assert.Nil(t, v.getURL("a", "key")) -} diff --git a/go.mod b/go.mod index 8012c4b28d..98db5b7c6c 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,9 @@ require ( github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 github.com/bxcodec/faker v2.0.1+incompatible github.com/cenkalti/backoff v2.1.1+incompatible - github.com/codegangsta/negroni v1.0.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.7 github.com/ghodss/yaml v1.0.0 - github.com/go-errors/errors v1.0.1 github.com/go-openapi/analysis v0.19.0 // indirect github.com/go-openapi/errors v0.19.0 github.com/go-openapi/inflect v0.19.0 // indirect @@ -32,7 +30,7 @@ require ( github.com/gorilla/handlers v1.4.0 // indirect github.com/gorilla/mux v1.7.1 // indirect github.com/huandu/xstrings v1.2.0 // indirect - github.com/imdario/mergo v0.3.7 // indirect + github.com/imdario/mergo v0.3.7 github.com/jessevdk/go-flags v1.4.0 // indirect github.com/julienschmidt/httprouter v1.2.0 github.com/lib/pq v1.0.0 @@ -44,12 +42,12 @@ require ( github.com/ory/fosite v0.29.2 github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90 github.com/ory/go-convenience v0.1.0 - github.com/ory/gojsonschema v0.0.0-20190717132251-f184856edacf + github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9 github.com/ory/graceful v0.1.1 github.com/ory/herodot v0.6.2 github.com/ory/ladon v1.0.1 github.com/ory/viper v1.5.6 - github.com/ory/x v0.0.66 + github.com/ory/x v0.0.76 github.com/pborman/uuid v1.2.0 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.8.1 @@ -59,6 +57,8 @@ require ( github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518 github.com/square/go-jose v2.3.1+incompatible github.com/stretchr/testify v1.3.0 + github.com/tidwall/gjson v1.3.2 + github.com/tidwall/sjson v1.0.4 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce github.com/toqueteos/webbrowser v1.1.0 // indirect github.com/urfave/negroni v1.0.0 @@ -70,3 +70,5 @@ require ( // Fix for https://github.com/golang/lint/issues/436 replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1 + +go 1.13 diff --git a/go.sum b/go.sum index 392b16c661..2e2a6841bd 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,6 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7a github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-bindata/go-bindata v3.1.1+incompatible h1:tR4f0e4VTO7LK6B2YWyAoVEzG9ByG1wrXB4TL9+jiYg= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -265,7 +263,6 @@ github.com/gobuffalo/packr v1.20.0/go.mod h1:JDytk1t2gP+my1ig7iI4NcVaXr886+N0ecU github.com/gobuffalo/packr v1.21.0/go.mod h1:H00jGfj1qFKxscFJSw8wcL4hpQtPe1PfU2wa6sg/SR0= github.com/gobuffalo/packr v1.22.0 h1:/YVd/GRGsu0QuoCJtlcWSVllobs4q3Xvx3nqxTvPyN0= github.com/gobuffalo/packr v1.22.0/go.mod h1:Qr3Wtxr3+HuQEwWqlLnNW4t1oTvK+7Gc/Rnoi/lDFvA= -github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr/v2 v2.0.0-rc.8/go.mod h1:y60QCdzwuMwO2R49fdQhsjCPv7tLQFR0ayzxxla9zes= github.com/gobuffalo/packr/v2 v2.0.0-rc.9/go.mod h1:fQqADRfZpEsgkc7c/K7aMew3n4aF1Kji7+lIZeR98Fc= github.com/gobuffalo/packr/v2 v2.0.0-rc.10/go.mod h1:4CWWn4I5T3v4c1OsJ55HbHlUEKNWMITG5iIkdr4Px4w= @@ -275,7 +272,6 @@ github.com/gobuffalo/packr/v2 v2.0.0-rc.13/go.mod h1:2Mp7GhBFMdJlOK8vGfl7SYtfMP3 github.com/gobuffalo/packr/v2 v2.0.0-rc.14/go.mod h1:06otbrNvDKO1eNQ3b8hst+1010UooI2MFg+B2Ze4MV8= github.com/gobuffalo/packr/v2 v2.0.0-rc.15 h1:vSmYcMO6CtuNQvMSbEJeIJlaeZzz2zoxGLTy8HrDh80= github.com/gobuffalo/packr/v2 v2.0.0-rc.15/go.mod h1:IMe7H2nJvcKXSF90y4X1rjYIRlNMJYCxEhssBXNZwWs= -github.com/gobuffalo/packr/v2 v2.5.2 h1:4EvjeIpQLZuRIljwnidYgbRXbr1yIzVRrESiLjqKj6s= github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= github.com/gobuffalo/plush v3.7.21+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= @@ -496,8 +492,8 @@ github.com/opencontainers/runc v1.0.0-rc5/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rm github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8= -github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ory/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= github.com/ory/fosite v0.29.2 h1:SV6tI1JBl64EPaHZ8ZTsfWGB6z4IlID+0xKoAVK+Fzo= github.com/ory/fosite v0.29.2/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= @@ -505,8 +501,10 @@ github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90 h1:Bpk3eqc3rbJT2mE+uS9E github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4= github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= -github.com/ory/gojsonschema v0.0.0-20190717132251-f184856edacf h1:4u2kqhTpMYabxKmTYyJasLXFozkGVvbb6+AdMnWadwg= -github.com/ory/gojsonschema v0.0.0-20190717132251-f184856edacf/go.mod h1:ypRRv2Fg8r46Xc3bGggOPzwzJFN2CiW7roiJN5k/p4w= +github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8 h1:e2S2FmxqSbhFyVNP24HncpRY+X1qAZmtE3nZ0gJKR4Q= +github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8/go.mod h1:wsH1C4nIeeQClDtD5AH7kF1uTS6zWyqfjVDTmB0Em7A= +github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9 h1:LDIG2Mnha10nFZuVXv3GIBqhQ1+JLwRXPcP4Ykx5VOY= +github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9/go.mod h1:BNZpdJgB74KOLSsWFvzw6roXg1I6O51WO8roMmW+T7Y= github.com/ory/graceful v0.1.1 h1:zx+8tDObLPrG+7Tc8jKYlXsqWnLtOQA1IZ/FAAKHMXU= github.com/ory/graceful v0.1.1/go.mod h1:zqu70l95WrKHF4AZ6tXHvAqAvpY6M7g6ttaAVcMm7KU= github.com/ory/herodot v0.5.1/go.mod h1:3BOneqcyBsVybCPAJoi92KN2BpJHcmDqAMcAAaJiJow= @@ -517,8 +515,8 @@ github.com/ory/ladon v1.0.1/go.mod h1:1VhCA2mBtaMhRUS6VS0d9qrNVDQnFXqSRb5D0NvQUP github.com/ory/pagination v0.0.1/go.mod h1:d1ToRROAUleriPhmb2dYbhANhhLwZ8s395m2yJCDFh8= github.com/ory/viper v1.5.6 h1:w4ceGgWwWLzAFYQ7bHaDZmwNsAto2JPVdyQjQnn7VWI= github.com/ory/viper v1.5.6/go.mod h1:TYmpFpKLxjQwvT4f0QPpkOn4sDXU1kDgAwJpgLYiQ28= -github.com/ory/x v0.0.66 h1:k+g4OpQ4CGdmx/3JoyWuCwxW9FlNCPG6gZASvFNKQ2Q= -github.com/ory/x v0.0.66/go.mod h1:nbNQF9P15jCwS5fd/BVS+H+feJMZUd1RLpH1DY4AY50= +github.com/ory/x v0.0.76 h1:pM9oK8szqYr/tAa0I/oGiCRVGppt4pJSm56oQUnjsvM= +github.com/ory/x v0.0.76/go.mod h1:TH1ImNLBepjywXHy3fgEXDgOIxH+ZF95jkZuo4/lPEU= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -614,6 +612,14 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/subosito/gotenv v1.1.1 h1:TWxckSF6WVKWbo2M3tMqCtWa9NFUgqM1SSynxmYONOI= github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI= +github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.0.4 h1:UcdIRXff12Lpnu3OLtZvnc03g4vH2suXDXhBwBqmzYg= +github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= @@ -630,8 +636,6 @@ github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= diff --git a/internal/driver.go b/internal/driver.go index 6f360e03dc..e43c2eba8a 100644 --- a/internal/driver.go +++ b/internal/driver.go @@ -9,8 +9,7 @@ import ( ) func ResetViper() { - viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, nil) - + viper.Reset() viper.Set("log.level", "debug") } diff --git a/pipeline/authn/authenticator.go b/pipeline/authn/authenticator.go index f7e418d88f..5a13ac3e49 100644 --- a/pipeline/authn/authenticator.go +++ b/pipeline/authn/authenticator.go @@ -4,10 +4,11 @@ import ( "encoding/json" "net/http" + "github.com/pkg/errors" + "github.com/ory/herodot" - "github.com/ory/oathkeeper/pipeline" - "github.com/go-errors/errors" + "github.com/ory/oathkeeper/pipeline" ) var ErrAuthenticatorNotResponsible = errors.New("Authenticator not responsible") @@ -20,7 +21,19 @@ var ErrAuthenticatorNotEnabled = herodot.DefaultError{ type Authenticator interface { Authenticate(r *http.Request, config json.RawMessage, rule pipeline.Rule) (*AuthenticationSession, error) GetID() string - Validate() error + Validate(config json.RawMessage) error +} + +func NewErrAuthenticatorNotEnabled(a Authenticator) *herodot.DefaultError { + return ErrAuthenticatorNotEnabled.WithTrace(errors.New("")).WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID()) +} + +func NewErrAuthenticatorMisconfigured(a Authenticator, err error) *herodot.DefaultError { + return ErrAuthenticatorNotEnabled.WithTrace(err).WithReasonf( + `Configuration for authenticator "%s" could not be validated: %s`, + a.GetID(), + err, + ) } type AuthenticationSession struct { diff --git a/pipeline/authn/authenticator_anonymous.go b/pipeline/authn/authenticator_anonymous.go index cbe0f01311..3d0add2edb 100644 --- a/pipeline/authn/authenticator_anonymous.go +++ b/pipeline/authn/authenticator_anonymous.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" + "github.com/ory/x/stringsx" + "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/pipeline" @@ -14,28 +16,49 @@ type AuthenticatorAnonymous struct { c configuration.Provider } +type AuthenticatorAnonymousConfiguration struct { + Subject string `json:"subject"` +} + func NewAuthenticatorAnonymous(c configuration.Provider) *AuthenticatorAnonymous { return &AuthenticatorAnonymous{ c: c, } } -func (a *AuthenticatorAnonymous) Validate() error { - if !a.c.AuthenticatorAnonymousIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorAnonymous) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) } - return nil + _, err := a.Config(config) + return err } func (a *AuthenticatorAnonymous) GetID() string { return "anonymous" } +func (a *AuthenticatorAnonymous) Config(config json.RawMessage) (*AuthenticatorAnonymousConfiguration, error) { + var c AuthenticatorAnonymousConfiguration + if err := a.c.AuthenticatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrAuthenticatorMisconfigured(a, err) + } + + return &c, nil +} + func (a *AuthenticatorAnonymous) Authenticate(r *http.Request, config json.RawMessage, _ pipeline.Rule) (*AuthenticationSession, error) { if len(r.Header.Get("Authorization")) != 0 { return nil, errors.WithStack(ErrAuthenticatorNotResponsible) } - return &AuthenticationSession{Subject: a.c.AuthenticatorAnonymousIdentifier()}, nil + cf, err := a.Config(config) + if err != nil { + return nil, err + } + + return &AuthenticationSession{ + Subject: stringsx.Coalesce(cf.Subject, "anonymous"), + }, nil } diff --git a/pipeline/authn/authenticator_anonymous_test.go b/pipeline/authn/authenticator_anonymous_test.go index 082b10aa68..d4a24b35c2 100644 --- a/pipeline/authn/authenticator_anonymous_test.go +++ b/pipeline/authn/authenticator_anonymous_test.go @@ -21,6 +21,7 @@ package authn_test import ( + "encoding/json" "net/http" "testing" @@ -35,7 +36,7 @@ import ( func TestAuthenticatorAnonymous(t *testing.T) { conf := internal.NewConfigurationWithDefaults() - viper.Set(configuration.ViperKeyAuthenticatorAnonymousIdentifier, "anon") + // viper.Set(configuration.ViperKeyAuthenticatorAnonymousIdentifier, "anon") reg := internal.NewRegistry(conf) a, err := reg.PipelineAuthenticator("anonymous") @@ -43,21 +44,21 @@ func TestAuthenticatorAnonymous(t *testing.T) { assert.Equal(t, "anonymous", a.GetID()) t.Run("method=authenticate/case=is anonymous user", func(t *testing.T) { - session, err := a.Authenticate(&http.Request{Header: http.Header{}}, nil, nil) + session, err := a.Authenticate(&http.Request{Header: http.Header{}}, json.RawMessage(`{"subject":"anon"}`), nil) require.NoError(t, err) assert.Equal(t, "anon", session.Subject) }) t.Run("method=authenticate/case=has credentials", func(t *testing.T) { - _, err := a.Authenticate(&http.Request{Header: http.Header{"Authorization": {"foo"}}}, nil, nil) + _, err := a.Authenticate(&http.Request{Header: http.Header{"Authorization": {"foo"}}}, json.RawMessage(`{"subject":"anon"}`), nil) require.Error(t, err) }) t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthenticatorAnonymousIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(json.RawMessage(`{"subject":"foo"}`))) viper.Set(configuration.ViperKeyAuthenticatorAnonymousIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"subject":"foo"}`))) }) } diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index d9546cb978..36aecb1645 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -1,7 +1,6 @@ package authn import ( - "bytes" "encoding/json" "io/ioutil" "net/http" @@ -36,46 +35,35 @@ func (a *AuthenticatorCookieSession) GetID() string { return "cookie_session" } -func (a *AuthenticatorCookieSession) Validate() error { - if !a.c.AuthenticatorCookieSessionIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorCookieSession) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) } - if a.c.AuthenticatorCookieSessionCheckSessionURL().String() == "" { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf( - `Configuration for authenticator "%s" did not specify any values for configuration key "%s" and is thus disabled.`, - a.GetID(), - configuration.ViperKeyAuthenticatorCookieSessionCheckSessionURL, - )) + _, err := a.Config(config) + return err +} + +func (a *AuthenticatorCookieSession) Config(config json.RawMessage) (*AuthenticatorCookieSessionConfiguration, error) { + var c AuthenticatorCookieSessionConfiguration + if err := a.c.AuthenticatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrAuthenticatorMisconfigured(a, err) } - return nil + return &c, nil } func (a *AuthenticatorCookieSession) Authenticate(r *http.Request, config json.RawMessage, _ pipeline.Rule) (*AuthenticationSession, error) { - var cf AuthenticatorCookieSessionConfiguration - if len(config) == 0 { - config = []byte("{}") - } - d := json.NewDecoder(bytes.NewBuffer(config)) - d.DisallowUnknownFields() - if err := d.Decode(&cf); err != nil { - return nil, errors.WithStack(err) + cf, err := a.Config(config) + if err != nil { + return nil, err } - only := cf.Only - if len(only) == 0 { - only = a.c.AuthenticatorCookieSessionOnly() - } - if !cookieSessionResponsible(r, only) { + if !cookieSessionResponsible(r, cf.Only) { return nil, errors.WithStack(ErrAuthenticatorNotResponsible) } origin := cf.CheckSessionURL - if origin == "" { - origin = a.c.AuthenticatorCookieSessionCheckSessionURL().String() - } - body, err := forwardRequestToSessionStore(r, origin) if err != nil { return nil, helper.ErrForbidden.WithReason(err.Error()).WithTrace(err) diff --git a/pipeline/authn/authenticator_jwt.go b/pipeline/authn/authenticator_jwt.go index 01051ee3ed..7c32403d1c 100644 --- a/pipeline/authn/authenticator_jwt.go +++ b/pipeline/authn/authenticator_jwt.go @@ -10,6 +10,7 @@ import ( "github.com/ory/go-convenience/jwtx" "github.com/ory/herodot" + "github.com/ory/oathkeeper/credentials" "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/helper" @@ -21,18 +22,12 @@ type AuthenticatorJWTRegistry interface { } type AuthenticatorOAuth2JWTConfiguration struct { - // An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler. - // If the token used in the Authorization header did not request that specific scope, the request is denied. - Scope []string `json:"required_scope"` - - // An array of audiences that are required when accessing an endpoint protected by this handler. - // If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied. - Audience []string `json:"target_audience"` - - // The token must have been issued by one of the issuers listed in this array. - Issuers []string `json:"trusted_issuers"` - + Scope []string `json:"required_scope"` + Audience []string `json:"target_audience"` + Issuers []string `json:"trusted_issuers"` AllowedAlgorithms []string `json:"allowed_algorithms"` + JWKSURLs []string `json:"jwks_urls"` + ScopeStrategy string `json:"scope_strategy"` } type AuthenticatorJWT struct { @@ -54,16 +49,22 @@ func (a *AuthenticatorJWT) GetID() string { return "jwt" } -func (a *AuthenticatorJWT) Validate() error { - if !a.c.AuthenticatorJWTIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorJWT) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) } - if len(a.c.AuthenticatorJWTJWKSURIs()) == 0 { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Configuration for authenticator "%s" did not specify any values for configuration key "%s" and is thus disabled.`, a.GetID(), configuration.ViperKeyAuthenticatorJWTJWKSURIs)) + _, err := a.Config(config) + return err +} + +func (a *AuthenticatorJWT) Config(config json.RawMessage) (*AuthenticatorOAuth2JWTConfiguration, error) { + var c AuthenticatorOAuth2JWTConfiguration + if err := a.c.AuthenticatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrAuthenticatorMisconfigured(a, err) } - return nil + return &c, nil } func (a *AuthenticatorJWT) Authenticate(r *http.Request, config json.RawMessage, _ pipeline.Rule) (*AuthenticationSession, error) { @@ -87,13 +88,18 @@ func (a *AuthenticatorJWT) Authenticate(r *http.Request, config json.RawMessage, cf.AllowedAlgorithms = []string{"RS256"} } + jwksu, err := a.c.ParseURLs(cf.JWKSURLs) + if err != nil { + return nil, err + } + pt, err := a.r.CredentialsVerifier().Verify(r.Context(), token, &credentials.ValidationContext{ Algorithms: cf.AllowedAlgorithms, - KeyURLs: a.c.AuthenticatorJWTJWKSURIs(), + KeyURLs: jwksu, Scope: cf.Scope, Issuers: cf.Issuers, Audiences: cf.Audience, - ScopeStrategy: a.c.AuthenticatorJWTScopeStrategy(), + ScopeStrategy: a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.jwt.Config.scope_strategy"), }) if err != nil { return nil, helper.ErrForbidden.WithReason(err.Error()).WithTrace(err) diff --git a/pipeline/authn/authenticator_jwt_test.go b/pipeline/authn/authenticator_jwt_test.go index be710d288d..37a6a868f9 100644 --- a/pipeline/authn/authenticator_jwt_test.go +++ b/pipeline/authn/authenticator_jwt_test.go @@ -29,13 +29,12 @@ import ( "time" "github.com/dgrijalva/jwt-go" + "github.com/tidwall/sjson" - "github.com/ory/viper" + "github.com/ory/x/urlx" - "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/internal" . "github.com/ory/oathkeeper/pipeline/authn" - "github.com/ory/x/urlx" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -50,7 +49,7 @@ func TestAuthenticatorJWT(t *testing.T) { "file://../../test/stub/jwks-ecdsa.json", } conf := internal.NewConfigurationWithDefaults() - viper.Set(configuration.ViperKeyAuthenticatorJWTJWKSURIs, keys) + // viper.Set(configuration.ViperKeyAuthenticatorJWTJWKSURIs, keys) reg := internal.NewRegistry(conf) a, err := reg.PipelineAuthenticator("jwt") @@ -86,9 +85,6 @@ func TestAuthenticatorJWT(t *testing.T) { }, { d: "should pass because JWT is valid", - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorJWTScopeStrategy, "exact") - }, r: &http.Request{Header: http.Header{"Authorization": []string{"bearer " + gen(keys[1], jwt.MapClaims{ "sub": "sub", "exp": now.Add(time.Hour).Unix(), @@ -96,7 +92,7 @@ func TestAuthenticatorJWT(t *testing.T) { "iss": "iss-2", "scope": []string{"scope-3", "scope-2", "scope-1"}, })}}}, - config: `{"target_audience": ["aud-1", "aud-2"], "trusted_issuers": ["iss-1", "iss-2"], "required_scope": ["scope-1", "scope-2"]}`, + config: `{"target_audience": ["aud-1", "aud-2"], "trusted_issuers": ["iss-1", "iss-2"], "required_scope": ["scope-1", "scope-2"], "scope_strategy":"exact"}`, expectErr: false, expectSess: &AuthenticationSession{ Subject: "sub", @@ -111,9 +107,6 @@ func TestAuthenticatorJWT(t *testing.T) { }, { d: "should pass because JWT scope can be a string", - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorJWTScopeStrategy, "exact") - }, r: &http.Request{Header: http.Header{"Authorization": []string{"bearer " + gen(keys[2], jwt.MapClaims{ "sub": "sub", "exp": now.Add(time.Hour).Unix(), @@ -121,7 +114,7 @@ func TestAuthenticatorJWT(t *testing.T) { "iss": "iss-2", "scope": "scope-3 scope-2 scope-1", })}}}, - config: `{"target_audience": ["aud-1", "aud-2"], "trusted_issuers": ["iss-1", "iss-2"], "required_scope": ["scope-1", "scope-2"]}`, + config: `{"target_audience": ["aud-1", "aud-2"], "trusted_issuers": ["iss-1", "iss-2"], "required_scope": ["scope-1", "scope-2"], "scope_strategy":"exact"}`, expectErr: false, expectSess: &AuthenticationSession{ Subject: "sub", @@ -136,9 +129,6 @@ func TestAuthenticatorJWT(t *testing.T) { }, { d: "should pass because JWT is valid and HS256 is allowed", - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorJWTScopeStrategy, "none") - }, r: &http.Request{Header: http.Header{"Authorization": []string{"bearer " + gen(keys[0], jwt.MapClaims{ "sub": "sub", "exp": now.Add(time.Hour).Unix(), @@ -152,9 +142,6 @@ func TestAuthenticatorJWT(t *testing.T) { }, { d: "should pass because JWT is valid and ES256 is allowed", - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorJWTScopeStrategy, "none") - }, r: &http.Request{Header: http.Header{"Authorization": []string{"bearer " + gen(keys[3], jwt.MapClaims{ "sub": "sub", "exp": now.Add(time.Hour).Unix(), @@ -240,6 +227,7 @@ func TestAuthenticatorJWT(t *testing.T) { tc.setup() } + tc.config, _ = sjson.Set(tc.config, "jwks_urls", keys) session, err := a.Authenticate(tc.r, json.RawMessage([]byte(tc.config)), nil) if tc.expectErr { require.Error(t, err) @@ -253,8 +241,4 @@ func TestAuthenticatorJWT(t *testing.T) { }) } }) - - t.Run("method=validate", func(t *testing.T) { - - }) } diff --git a/pipeline/authn/authenticator_noop.go b/pipeline/authn/authenticator_noop.go index 64e7afbb47..5ee0960911 100644 --- a/pipeline/authn/authenticator_noop.go +++ b/pipeline/authn/authenticator_noop.go @@ -4,8 +4,6 @@ import ( "encoding/json" "net/http" - "github.com/pkg/errors" - "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/pipeline" ) @@ -22,11 +20,14 @@ func (a *AuthenticatorNoOp) GetID() string { return "noop" } -func (a *AuthenticatorNoOp) Validate() error { - if !a.c.AuthenticatorNoopIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorNoOp) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) } + if err := a.c.AuthenticatorConfig(a.GetID(), config, nil); err != nil { + return NewErrAuthenticatorMisconfigured(a, err) + } return nil } diff --git a/pipeline/authn/authenticator_noop_test.go b/pipeline/authn/authenticator_noop_test.go index 95eff50e36..940f6bca18 100644 --- a/pipeline/authn/authenticator_noop_test.go +++ b/pipeline/authn/authenticator_noop_test.go @@ -47,9 +47,9 @@ func TestAuthenticatorNoop(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthenticatorNoopIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(nil)) viper.Set(configuration.ViperKeyAuthenticatorNoopIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(nil)) }) } diff --git a/pipeline/authn/authenticator_oauth2_client_credentials.go b/pipeline/authn/authenticator_oauth2_client_credentials.go index aa37925a55..7aaadb7048 100644 --- a/pipeline/authn/authenticator_oauth2_client_credentials.go +++ b/pipeline/authn/authenticator_oauth2_client_credentials.go @@ -20,9 +20,8 @@ import ( ) type AuthenticatorOAuth2Configuration struct { - // Scopes is an array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this rule. - // If the token used in the Authorization header did not request that specific scope, the request is denied. - Scopes []string `json:"required_scope"` + Scopes []string `json:"required_scope"` + TokenURL string `json:"token_url"` } type AuthenticatorOAuth2ClientCredentials struct { @@ -37,16 +36,22 @@ func (a *AuthenticatorOAuth2ClientCredentials) GetID() string { return "oauth2_client_credentials" } -func (a *AuthenticatorOAuth2ClientCredentials) Validate() error { - if !a.c.AuthenticatorOAuth2ClientCredentialsIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorOAuth2ClientCredentials) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) } - if a.c.AuthenticatorOAuth2ClientCredentialsTokenURL() == nil { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Configuration for authenticator "%s" did not specify any values for configuration key "%s" and is thus disabled.`, a.GetID(), configuration.ViperKeyAuthenticatorClientCredentialsTokenURL)) + _, err := a.Config(config) + return err +} + +func (a *AuthenticatorOAuth2ClientCredentials) Config(config json.RawMessage) (*AuthenticatorOAuth2Configuration, error) { + var c AuthenticatorOAuth2Configuration + if err := a.c.AuthenticatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrAuthenticatorMisconfigured(a, err) } - return nil + return &c, nil } func (a *AuthenticatorOAuth2ClientCredentials) Authenticate(r *http.Request, config json.RawMessage, _ pipeline.Rule) (*AuthenticationSession, error) { @@ -81,7 +86,7 @@ func (a *AuthenticatorOAuth2ClientCredentials) Authenticate(r *http.Request, con ClientID: user, ClientSecret: password, Scopes: cf.Scopes, - TokenURL: a.c.AuthenticatorOAuth2ClientCredentialsTokenURL().String(), + TokenURL: cf.TokenURL, } token, err := c.Token(context.WithValue( diff --git a/pipeline/authn/authenticator_oauth2_client_credentials_test.go b/pipeline/authn/authenticator_oauth2_client_credentials_test.go index d893f88845..5edfbbc278 100644 --- a/pipeline/authn/authenticator_oauth2_client_credentials_test.go +++ b/pipeline/authn/authenticator_oauth2_client_credentials_test.go @@ -70,38 +70,29 @@ func TestAuthenticatorOAuth2ClientCredentials(t *testing.T) { authInvalid.SetBasicAuth("foo", "bar") for k, tc := range []struct { - setup func() r *http.Request config json.RawMessage expectErr error expectSession *authn.AuthenticationSession }{ { - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, "") - }, r: &http.Request{Header: http.Header{}}, expectErr: authn.ErrAuthenticatorNotResponsible, + config: json.RawMessage(`{"token_url":""}`), }, { - r: authInvalid, - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, ts.URL+"/oauth2/token") - }, + r: authInvalid, expectErr: helper.ErrUnauthorized, + config: json.RawMessage(`{"token_url":"` + ts.URL + "/oauth2/token" + `"}`), }, { - setup: func() { - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, ts.URL+"/oauth2/token") - }, r: authOk, expectErr: nil, expectSession: &authn.AuthenticationSession{Subject: "client"}, + config: json.RawMessage(`{"token_url":"` + ts.URL + "/oauth2/token" + `"}`), }, } { t.Run(fmt.Sprintf("method=authenticate/case=%d", k), func(t *testing.T) { - tc.setup() - session, err := a.Authenticate(tc.r, tc.config, nil) if tc.expectErr != nil { @@ -118,19 +109,15 @@ func TestAuthenticatorOAuth2ClientCredentials(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, false) - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, "") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"token_url":""}`))) viper.Set(configuration.ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, false) - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, ts.URL+"/oauth2/token") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"token_url":"`+ts.URL+"/oauth2/token"+`"}`))) viper.Set(configuration.ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, true) - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, "") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"token_url":""}`))) viper.Set(configuration.ViperKeyAuthenticatorOAuth2ClientCredentialsIsEnabled, true) - viper.Set(configuration.ViperKeyAuthenticatorClientCredentialsTokenURL, ts.URL+"/oauth2/token") - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(json.RawMessage(`{"token_url":"`+ts.URL+"/oauth2/token"+`"}`))) }) } diff --git a/pipeline/authn/authenticator_oauth2_introspection.go b/pipeline/authn/authenticator_oauth2_introspection.go index 0e34667cba..6bcfd1aa1a 100644 --- a/pipeline/authn/authenticator_oauth2_introspection.go +++ b/pipeline/authn/authenticator_oauth2_introspection.go @@ -10,25 +10,31 @@ import ( "strings" "github.com/pkg/errors" + "golang.org/x/oauth2/clientcredentials" "github.com/ory/go-convenience/stringslice" + "github.com/ory/x/httpx" + "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/pipeline" - "github.com/ory/x/httpx" ) type AuthenticatorOAuth2IntrospectionConfiguration struct { - // An array of OAuth 2.0 scopes that are required when accessing an endpoint protected by this handler. - // If the token used in the Authorization header did not request that specific scope, the request is denied. - Scopes []string `json:"required_scope"` - - // An array of audiences that are required when accessing an endpoint protected by this handler. - // If the token used in the Authorization header is not intended for any of the requested audiences, the request is denied. - Audience []string `json:"target_audience"` + Scopes []string `json:"required_scope"` + Audience []string `json:"target_audience"` + Issuers []string `json:"trusted_issuers"` + PreAuth *AuthenticatorOAuth2IntrospectionPreAuthConfiguration `json:"pre_authorization"` + ScopeStrategy string `json:"scope_strategy"` + IntrospectionURL string `json:"introspection_url"` +} - // The token must have been issued by one of the issuers listed in this array. - Issuers []string `json:"trusted_issuers"` +type AuthenticatorOAuth2IntrospectionPreAuthConfiguration struct { + Enabled bool `json:"enabled"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Scope []string `json:"scope"` + TokenURL string `json:"token_url"` } type AuthenticatorOAuth2Introspection struct { @@ -39,10 +45,6 @@ type AuthenticatorOAuth2Introspection struct { func NewAuthenticatorOAuth2Introspection(c configuration.Provider) *AuthenticatorOAuth2Introspection { var rt http.RoundTripper - if conf := c.AuthenticatorOAuth2TokenIntrospectionPreAuthorization(); conf != nil { - rt = conf.Client(context.Background()).Transport - } - return &AuthenticatorOAuth2Introspection{c: c, client: httpx.NewResilientClientLatencyToleranceSmall(rt)} } @@ -82,7 +84,7 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, config } body := url.Values{"token": {token}, "scope": {strings.Join(cf.Scopes, " ")}} - resp, err := a.client.Post(a.c.AuthenticatorOAuth2TokenIntrospectionIntrospectionURL().String(), "application/x-www-form-urlencoded", strings.NewReader(body.Encode())) + resp, err := a.client.Post(cf.IntrospectionURL, "application/x-www-form-urlencoded", strings.NewReader(body.Encode())) if err != nil { return nil, errors.WithStack(err) } @@ -116,9 +118,9 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, config } } - if a.c.AuthenticatorOAuth2TokenIntrospectionScopeStrategy() != nil { + if ss := a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.oauth2_introspection.scope_strategy"); ss != nil { for _, scope := range cf.Scopes { - if !a.c.AuthenticatorOAuth2TokenIntrospectionScopeStrategy()(strings.Split(i.Scope, " "), scope) { + if !ss(strings.Split(i.Scope, " "), scope) { return nil, errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Scope %s was not granted", scope))) } } @@ -138,14 +140,33 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, config }, nil } -func (a *AuthenticatorOAuth2Introspection) Validate() error { - if !a.c.AuthenticatorOAuth2TokenIntrospectionIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorOAuth2Introspection) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) + } + + _, err := a.Config(config) + return err +} + +func (a *AuthenticatorOAuth2Introspection) Config(config json.RawMessage) (*AuthenticatorOAuth2IntrospectionConfiguration, error) { + var c AuthenticatorOAuth2IntrospectionConfiguration + if err := a.c.AuthenticatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrAuthenticatorMisconfigured(a, err) } - if a.c.AuthenticatorOAuth2TokenIntrospectionIntrospectionURL() == nil { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Configuration for authenticator "%s" did not specify any values for configuration key "%s" and is thus disabled.`, a.GetID(), configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL)) + if c.PreAuth != nil && c.PreAuth.Enabled { + a.client = httpx.NewResilientClientLatencyToleranceSmall( + (&clientcredentials.Config{ + ClientID: c.PreAuth.ClientID, + ClientSecret: c.PreAuth.ClientSecret, + Scopes: c.PreAuth.Scope, + TokenURL: c.PreAuth.TokenURL, + }). + Client(context.Background()). + Transport, + ) } - return nil + return &c, nil } diff --git a/pipeline/authn/authenticator_oauth2_introspection_test.go b/pipeline/authn/authenticator_oauth2_introspection_test.go index 64ff0a7005..9db2fd3bb5 100644 --- a/pipeline/authn/authenticator_oauth2_introspection_test.go +++ b/pipeline/authn/authenticator_oauth2_introspection_test.go @@ -27,6 +27,8 @@ import ( "net/http/httptest" "testing" + "github.com/tidwall/sjson" + "github.com/ory/viper" "github.com/ory/oathkeeper/driver/configuration" @@ -248,8 +250,8 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { ts := httptest.NewServer(router) defer ts.Close() - viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, ts.URL+"/oauth2/introspect") - viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionScopeStrategy, "exact") + tc.config, _ = sjson.SetBytes(tc.config, "introspection_url", ts.URL+"/oauth2/introspect") + tc.config, _ = sjson.SetBytes(tc.config, "scope_strategy", "exact") sess, err := a.Authenticate(tc.r, tc.config, nil) if tc.expectErr { require.Error(t, err) @@ -266,19 +268,15 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, false) - viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, "") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"introspection_url":""}`))) viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, true) - viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, "") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"introspection_url":""}`))) viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, false) - viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, "/oauth2/token") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"introspection_url":"/oauth2/token"}`))) viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIsEnabled, true) - viper.Set(configuration.ViperKeyAuthenticatorOAuth2TokenIntrospectionIntrospectionURL, "/oauth2/token") - require.NoError(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"introspection_url":"/oauth2/token"}`))) }) } diff --git a/pipeline/authn/authenticator_unauthorized.go b/pipeline/authn/authenticator_unauthorized.go index 4310802259..6318c0332f 100644 --- a/pipeline/authn/authenticator_unauthorized.go +++ b/pipeline/authn/authenticator_unauthorized.go @@ -40,11 +40,14 @@ func NewAuthenticatorUnauthorized(c configuration.Provider) *AuthenticatorUnauth return &AuthenticatorUnauthorized{c: c} } -func (a *AuthenticatorUnauthorized) Validate() error { - if !a.c.AuthenticatorUnauthorizedIsEnabled() { - return errors.WithStack(ErrAuthenticatorNotEnabled.WithReasonf(`Authenticator "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthenticatorUnauthorized) Validate(config json.RawMessage) error { + if !a.c.AuthenticatorIsEnabled(a.GetID()) { + return NewErrAuthenticatorNotEnabled(a) } + if err := a.c.AuthenticatorConfig(a.GetID(), config, nil); err != nil { + return NewErrAuthenticatorMisconfigured(a, err) + } return nil } diff --git a/pipeline/authn/authenticator_unauthorized_test.go b/pipeline/authn/authenticator_unauthorized_test.go index b4c1c42e43..04c8ab553d 100644 --- a/pipeline/authn/authenticator_unauthorized_test.go +++ b/pipeline/authn/authenticator_unauthorized_test.go @@ -48,9 +48,9 @@ func TestAuthenticatorBroken(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthenticatorUnauthorizedIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(nil)) viper.Set(configuration.ViperKeyAuthenticatorUnauthorizedIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(nil)) }) } diff --git a/pipeline/authz/authorizer.go b/pipeline/authz/authorizer.go index a5eaf51ae5..be3c0098dc 100644 --- a/pipeline/authz/authorizer.go +++ b/pipeline/authz/authorizer.go @@ -4,7 +4,10 @@ import ( "encoding/json" "net/http" + "github.com/pkg/errors" + "github.com/ory/herodot" + "github.com/ory/oathkeeper/pipeline" "github.com/ory/oathkeeper/pipeline/authn" ) @@ -15,8 +18,20 @@ var ErrAuthorizerNotEnabled = herodot.DefaultError{ StatusField: http.StatusText(http.StatusInternalServerError), } +func NewErrAuthorizerNotEnabled(a Authorizer) *herodot.DefaultError { + return ErrAuthorizerNotEnabled.WithTrace(errors.New("")).WithReasonf(`Authorizer "%s" is disabled per configuration.`, a.GetID()) +} + +func NewErrAuthorizerMisconfigured(a Authorizer, err error) *herodot.DefaultError { + return ErrAuthorizerNotEnabled.WithTrace(err).WithReasonf( + `Configuration for authorizer "%s" could not be validated: %s`, + a.GetID(), + err, + ) +} + type Authorizer interface { Authorize(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, rule pipeline.Rule) error GetID() string - Validate() error + Validate(config json.RawMessage) error } diff --git a/pipeline/authz/authorizer_allow.go b/pipeline/authz/authorizer_allow.go index ae054f93df..117f9e3933 100644 --- a/pipeline/authz/authorizer_allow.go +++ b/pipeline/authz/authorizer_allow.go @@ -24,8 +24,6 @@ import ( "encoding/json" "net/http" - "github.com/pkg/errors" - "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/pipeline" "github.com/ory/oathkeeper/pipeline/authn" @@ -47,10 +45,13 @@ func (a *AuthorizerAllow) Authorize(r *http.Request, session *authn.Authenticati return nil } -func (a *AuthorizerAllow) Validate() error { - if !a.c.AuthorizerAllowIsEnabled() { - return errors.WithStack(ErrAuthorizerNotEnabled.WithReasonf(`Authorizer "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthorizerAllow) Validate(config json.RawMessage) error { + if !a.c.AuthorizerIsEnabled(a.GetID()) { + return NewErrAuthorizerNotEnabled(a) } + if err := a.c.AuthorizerConfig(a.GetID(), config, nil); err != nil { + return NewErrAuthorizerMisconfigured(a, err) + } return nil } diff --git a/pipeline/authz/authorizer_allow_test.go b/pipeline/authz/authorizer_allow_test.go index b0f7f0a274..e60f8f2c5f 100644 --- a/pipeline/authz/authorizer_allow_test.go +++ b/pipeline/authz/authorizer_allow_test.go @@ -46,9 +46,9 @@ func TestAuthorizerAllow(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthorizerAllowIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(nil)) viper.Set(configuration.ViperKeyAuthorizerAllowIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(nil)) }) } diff --git a/pipeline/authz/authorizer_deny.go b/pipeline/authz/authorizer_deny.go index b7d2410eb4..58429d303c 100644 --- a/pipeline/authz/authorizer_deny.go +++ b/pipeline/authz/authorizer_deny.go @@ -49,10 +49,13 @@ func (a *AuthorizerDeny) Authorize(r *http.Request, session *authn.Authenticatio return errors.WithStack(helper.ErrForbidden) } -func (a *AuthorizerDeny) Validate() error { - if !a.c.AuthorizerDenyIsEnabled() { - return errors.WithStack(ErrAuthorizerNotEnabled.WithReasonf(`Authorizer "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthorizerDeny) Validate(config json.RawMessage) error { + if !a.c.AuthorizerIsEnabled(a.GetID()) { + return NewErrAuthorizerNotEnabled(a) } + if err := a.c.AuthorizerConfig(a.GetID(), config, nil); err != nil { + return NewErrAuthorizerMisconfigured(a, err) + } return nil } diff --git a/pipeline/authz/authorizer_deny_test.go b/pipeline/authz/authorizer_deny_test.go index c958e59d99..52c53481e7 100644 --- a/pipeline/authz/authorizer_deny_test.go +++ b/pipeline/authz/authorizer_deny_test.go @@ -46,9 +46,9 @@ func TestAuthorizerDeny(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthorizerDenyIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(nil)) viper.Set(configuration.ViperKeyAuthorizerDenyIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(nil)) }) } diff --git a/pipeline/authz/keto_engine_acp_ory.go b/pipeline/authz/keto_engine_acp_ory.go index c4001b337a..349a25b7a1 100644 --- a/pipeline/authz/keto_engine_acp_ory.go +++ b/pipeline/authz/keto_engine_acp_ory.go @@ -25,17 +25,18 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "text/template" "time" + "github.com/ory/x/httpx" + "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/pipeline" "github.com/ory/oathkeeper/pipeline/authn" - "github.com/ory/x/httpx" "github.com/ory/x/urlx" - "github.com/asaskevich/govalidator" "github.com/pkg/errors" "github.com/tomasen/realip" @@ -43,10 +44,11 @@ import ( ) type AuthorizerKetoEngineACPORYConfiguration struct { - RequiredAction string `json:"required_action" valid:",required"` - RequiredResource string `json:"required_resource" valid:",required"` + RequiredAction string `json:"required_action"` + RequiredResource string `json:"required_resource"` Subject string `json:"subject"` Flavor string `json:"flavor"` + BaseURL string `json:"base_url"` } type AuthorizerKetoEngineACPORY struct { @@ -87,22 +89,9 @@ func (a *AuthorizerKetoEngineACPORY) WithContextCreator(f authorizerKetoWardenCo } func (a *AuthorizerKetoEngineACPORY) Authorize(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, rule pipeline.Rule) error { - var cf AuthorizerKetoEngineACPORYConfiguration - - if len(config) == 0 { - config = []byte("{}") - } - - d := json.NewDecoder(bytes.NewBuffer(config)) - d.DisallowUnknownFields() - if err := d.Decode(&cf); err != nil { - return errors.WithStack(err) - } - - if result, err := govalidator.ValidateStruct(&cf); err != nil { - return errors.WithStack(err) - } else if !result { - return errors.New("Unable to validate keto warden configuration") + cf, err := a.Config(config) + if err != nil { + return err } compiled, err := rule.CompileURL() @@ -134,7 +123,13 @@ func (a *AuthorizerKetoEngineACPORY) Authorize(r *http.Request, session *authn.A }); err != nil { return errors.WithStack(err) } - req, err := http.NewRequest("POST", urlx.AppendPaths(a.c.AuthorizerKetoEngineACPORYBaseURL(), "/engines/acp/ory", flavor, "/allowed").String(), &b) + + baseURL, err := url.ParseRequestURI(cf.BaseURL) + if err != nil { + return errors.WithStack(err) + } + + req, err := http.NewRequest("POST", urlx.AppendPaths(baseURL, "/engines/acp/ory", flavor, "/allowed").String(), &b) if err != nil { return errors.WithStack(err) } @@ -190,14 +185,20 @@ func (a *AuthorizerKetoEngineACPORY) ParseSubject(session *authn.AuthenticationS return subject.String(), nil } -func (a *AuthorizerKetoEngineACPORY) Validate() error { - if !a.c.AuthorizerKetoEngineACPORYIsEnabled() { - return errors.WithStack(ErrAuthorizerNotEnabled.WithReasonf(`Authorizer "%s" is disabled per configuration.`, a.GetID())) +func (a *AuthorizerKetoEngineACPORY) Validate(config json.RawMessage) error { + if !a.c.AuthorizerIsEnabled(a.GetID()) { + return NewErrAuthorizerNotEnabled(a) } - if a.c.AuthorizerKetoEngineACPORYBaseURL() == nil { - return errors.WithStack(ErrAuthorizerNotEnabled.WithReasonf(`Configuration for authorizer "%s" did not specify any values for configuration key "%s" and is thus disabled.`, a.GetID(), configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL)) + _, err := a.Config(config) + return err +} + +func (a *AuthorizerKetoEngineACPORY) Config(config json.RawMessage) (*AuthorizerKetoEngineACPORYConfiguration, error) { + var c AuthorizerKetoEngineACPORYConfiguration + if err := a.c.AuthorizerConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrAuthorizerMisconfigured(a, err) } - return nil + return &c, nil } diff --git a/pipeline/authz/keto_engine_acp_ory_test.go b/pipeline/authz/keto_engine_acp_ory_test.go index 2cc7efda49..9208f99404 100644 --- a/pipeline/authz/keto_engine_acp_ory_test.go +++ b/pipeline/authz/keto_engine_acp_ory_test.go @@ -28,6 +28,8 @@ import ( "net/url" "testing" + "github.com/tidwall/sjson" + "github.com/ory/viper" "github.com/ory/oathkeeper/driver/configuration" @@ -195,17 +197,18 @@ func TestAuthorizerKetoWarden(t *testing.T) { c := gomock.NewController(t) defer c.Finish() - viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL, "http://73fa403f-7e9c-48ef-870f-d21b2c34fc80c6cb6404-bb36-4e70-8b90-45155657fda6/") + baseURL := "http://73fa403f-7e9c-48ef-870f-d21b2c34fc80c6cb6404-bb36-4e70-8b90-45155657fda6/" if tc.setup != nil { ts := tc.setup(t) defer ts.Close() - viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL, ts.URL) + baseURL = ts.URL } a.(*AuthorizerKetoEngineACPORY).WithContextCreator(func(r *http.Request) map[string]interface{} { return map[string]interface{}{} }) + tc.config, _ = sjson.SetBytes(tc.config, "base_url", baseURL) err := a.Authorize(tc.r, tc.session, tc.config, tc.rule) if tc.expectErr { require.Error(t, err) @@ -217,19 +220,15 @@ func TestAuthorizerKetoWarden(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYIsEnabled, false) - viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL, "") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"base_url":"","required_action":"foo","required_resource":"bar"}`))) viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYIsEnabled, true) - viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL, "") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"base_url":"","required_action":"foo","required_resource":"bar"}`))) viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYIsEnabled, false) - viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL, "http://foo/bar") - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"base_url":"http://foo/bar","required_action":"foo","required_resource":"bar"}`))) viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYIsEnabled, true) - viper.Set(configuration.ViperKeyAuthorizerKetoEngineACPORYBaseURL, "http://foo/bar") - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(json.RawMessage(`{"base_url":"http://foo/bar","required_action":"foo","required_resource":"bar"}`))) }) } diff --git a/pipeline/mutate/mutator.go b/pipeline/mutate/mutator.go index a88926b864..2e9b63e326 100644 --- a/pipeline/mutate/mutator.go +++ b/pipeline/mutate/mutator.go @@ -24,7 +24,10 @@ import ( "encoding/json" "net/http" + "github.com/pkg/errors" + "github.com/ory/herodot" + "github.com/ory/oathkeeper/pipeline" "github.com/ory/oathkeeper/pipeline/authn" ) @@ -35,8 +38,20 @@ var ErrMutatorNotEnabled = herodot.DefaultError{ StatusField: http.StatusText(http.StatusInternalServerError), } +func NewErrMutatorNotEnabled(a Mutator) *herodot.DefaultError { + return ErrMutatorNotEnabled.WithTrace(errors.New("")).WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID()) +} + +func NewErrMutatorMisconfigured(a Mutator, err error) *herodot.DefaultError { + return ErrMutatorNotEnabled.WithTrace(err).WithReasonf( + `Configuration for mutator "%s" could not be validated: %s`, + a.GetID(), + err, + ) +} + type Mutator interface { Mutate(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error GetID() string - Validate() error + Validate(config json.RawMessage) error } diff --git a/pipeline/mutate/mutator_broken.go b/pipeline/mutate/mutator_broken.go index f282f264a2..512a8c0501 100644 --- a/pipeline/mutate/mutator_broken.go +++ b/pipeline/mutate/mutator_broken.go @@ -48,7 +48,7 @@ func (a *MutatorBroken) Mutate(r *http.Request, session *authn.AuthenticationSes return errors.New("forced denial of credentials") } -func (a *MutatorBroken) Validate() error { +func (a *MutatorBroken) Validate(_ json.RawMessage) error { if !a.enabled { return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID())) } diff --git a/pipeline/mutate/mutator_broken_test.go b/pipeline/mutate/mutator_broken_test.go index 6a2ea1b85b..10ca2fbf8b 100644 --- a/pipeline/mutate/mutator_broken_test.go +++ b/pipeline/mutate/mutator_broken_test.go @@ -42,7 +42,7 @@ func TestCredentialsIssuerBroken(t *testing.T) { }) t.Run("method=validate", func(t *testing.T) { - require.Error(t, mutate.NewMutatorBroken(false).Validate()) - require.NoError(t, mutate.NewMutatorBroken(true).Validate()) + require.Error(t, mutate.NewMutatorBroken(false).Validate(nil)) + require.NoError(t, mutate.NewMutatorBroken(true).Validate(nil)) }) } diff --git a/pipeline/mutate/mutator_cookie.go b/pipeline/mutate/mutator_cookie.go index cd3baef32d..1c5cbf9d4b 100644 --- a/pipeline/mutate/mutator_cookie.go +++ b/pipeline/mutate/mutator_cookie.go @@ -38,10 +38,6 @@ func (a *MutatorCookie) WithCache(t *template.Template) { } func (a *MutatorCookie) Mutate(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, rl pipeline.Rule) error { - if len(config) == 0 { - config = []byte("{}") - } - // Cache request cookies requestCookies := r.Cookies() @@ -50,11 +46,9 @@ func (a *MutatorCookie) Mutate(r *http.Request, session *authn.AuthenticationSes // Keep track of rule cookies in a map cookies := map[string]bool{} - var cfg CredentialsCookiesConfig - d := json.NewDecoder(bytes.NewBuffer(config)) - d.DisallowUnknownFields() - if err := d.Decode(&cfg); err != nil { - return errors.WithStack(err) + cfg, err := a.config(config) + if err != nil { + return err } for cookie, templateString := range cfg.Cookies { @@ -98,10 +92,20 @@ func (a *MutatorCookie) Mutate(r *http.Request, session *authn.AuthenticationSes return nil } -func (a *MutatorCookie) Validate() error { - if !a.c.MutatorCookieIsEnabled() { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID())) +func (a *MutatorCookie) Validate(config json.RawMessage) error { + if !a.c.MutatorIsEnabled(a.GetID()) { + return NewErrMutatorNotEnabled(a) } - return nil + _, err := a.config(config) + return err +} + +func (a *MutatorCookie) config(config json.RawMessage) (*CredentialsCookiesConfig, error) { + var c CredentialsCookiesConfig + if err := a.c.MutatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrMutatorMisconfigured(a, err) + } + + return &c, nil } diff --git a/pipeline/mutate/mutator_cookie_test.go b/pipeline/mutate/mutator_cookie_test.go index b60e897912..41bf89dc06 100644 --- a/pipeline/mutate/mutator_cookie_test.go +++ b/pipeline/mutate/mutator_cookie_test.go @@ -54,7 +54,7 @@ func TestCredentialsIssuerCookies(t *testing.T) { Config: json.RawMessage([]byte(`{"bar": "baz"}`)), Request: &http.Request{Header: http.Header{}}, Match: []*http.Cookie{}, - Err: errors.New(`json: unknown field "bar"`), + Err: errors.New(`mutator matching this route is misconfigured or disabled`), }, "Complex Subject": { Session: &authn.AuthenticationSession{Subject: "foo"}, @@ -207,10 +207,10 @@ func TestCredentialsIssuerCookies(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyMutatorCookieIsEnabled, true) - require.NoError(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{}`))) viper.Set(configuration.ViperKeyMutatorCookieIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"cookies":{}}`))) }) }) } diff --git a/pipeline/mutate/mutator_header.go b/pipeline/mutate/mutator_header.go index baa5caba1e..69df0b8ec7 100644 --- a/pipeline/mutate/mutator_header.go +++ b/pipeline/mutate/mutator_header.go @@ -36,15 +36,9 @@ func (a *MutatorHeader) WithCache(t *template.Template) { } func (a *MutatorHeader) Mutate(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, rl pipeline.Rule) error { - if len(config) == 0 { - config = []byte("{}") - } - - var cfg MutatorHeaderConfig - d := json.NewDecoder(bytes.NewBuffer(config)) - d.DisallowUnknownFields() - if err := d.Decode(&cfg); err != nil { - return errors.WithStack(err) + cfg, err := a.config(config) + if err != nil { + return err } for hdr, templateString := range cfg.Headers { @@ -71,10 +65,20 @@ func (a *MutatorHeader) Mutate(r *http.Request, session *authn.AuthenticationSes return nil } -func (a *MutatorHeader) Validate() error { - if !a.c.MutatorHeaderIsEnabled() { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID())) +func (a *MutatorHeader) Validate(config json.RawMessage) error { + if !a.c.MutatorIsEnabled(a.GetID()) { + return NewErrMutatorNotEnabled(a) } - return nil + _, err := a.config(config) + return err +} + +func (a *MutatorHeader) config(config json.RawMessage) (*MutatorHeaderConfig, error) { + var c MutatorHeaderConfig + if err := a.c.MutatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrMutatorMisconfigured(a, err) + } + + return &c, nil } diff --git a/pipeline/mutate/mutator_header_test.go b/pipeline/mutate/mutator_header_test.go index d2f8d23333..eb27e1776f 100644 --- a/pipeline/mutate/mutator_header_test.go +++ b/pipeline/mutate/mutator_header_test.go @@ -202,9 +202,9 @@ func TestCredentialsIssuerHeaders(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyMutatorHeaderIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(json.RawMessage(`{"headers":{}}`))) viper.Set(configuration.ViperKeyMutatorHeaderIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(json.RawMessage(`{"headers":{}}`))) }) } diff --git a/pipeline/mutate/mutator_hydrator.go b/pipeline/mutate/mutator_hydrator.go index 0fd3ef7f73..b104a86521 100644 --- a/pipeline/mutate/mutator_hydrator.go +++ b/pipeline/mutate/mutator_hydrator.go @@ -62,19 +62,19 @@ type BasicAuth struct { Password string `json:"password"` } -type Auth struct { +type auth struct { Basic BasicAuth `json:"basic"` } -type RetryConfig struct { - NumberOfRetries int `json:"number"` - DelayInMilliseconds int `json:"delayInMilliseconds"` +type retryConfig struct { + NumberOfRetries int `json:"number_of_retries"` + DelayInMilliseconds int `json:"delay_in_milliseconds"` } type externalAPIConfig struct { - Url string `json:"url"` - Auth *Auth `json:"auth,omitempty"` - Retry *RetryConfig `json:"retry,omitempty"` + URL string `json:"url"` + Auth *auth `json:"auth"` + Retry *retryConfig `json:"retry"` } type MutatorHydratorConfig struct { @@ -90,28 +90,22 @@ func (a *MutatorHydrator) GetID() string { } func (a *MutatorHydrator) Mutate(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error { - if len(config) == 0 { - config = []byte("{}") - } - var cfg MutatorHydratorConfig - d := json.NewDecoder(bytes.NewBuffer(config)) - d.DisallowUnknownFields() - if err := d.Decode(&cfg); err != nil { - return errors.WithStack(err) + cfg, err := a.Config(config) + if err != nil { + return err } var b bytes.Buffer - err := json.NewEncoder(&b).Encode(session) - if err != nil { + if err := json.NewEncoder(&b).Encode(session); err != nil { return errors.WithStack(err) } - if cfg.Api.Url == "" { + if cfg.Api.URL == "" { return errors.New(ErrMissingAPIURL) - } else if _, err := url.ParseRequestURI(cfg.Api.Url); err != nil { + } else if _, err := url.ParseRequestURI(cfg.Api.URL); err != nil { return errors.New(ErrInvalidAPIURL) } - req, err := http.NewRequest("POST", cfg.Api.Url, &b) + req, err := http.NewRequest("POST", cfg.Api.URL, &b) if err != nil { return errors.WithStack(err) } @@ -126,7 +120,7 @@ func (a *MutatorHydrator) Mutate(r *http.Request, session *authn.AuthenticationS } req.Header.Set(contentTypeHeaderKey, contentTypeJSONHeaderValue) - retryConfig := RetryConfig{defaultNumberOfRetries, defaultDelayInMilliseconds} + retryConfig := retryConfig{defaultNumberOfRetries, defaultDelayInMilliseconds} if cfg.Api.Retry != nil { retryConfig = *cfg.Api.Retry } @@ -166,9 +160,20 @@ func (a *MutatorHydrator) Mutate(r *http.Request, session *authn.AuthenticationS return nil } -func (a *MutatorHydrator) Validate() error { - if !a.c.MutatorHydratorIsEnabled() { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID())) +func (a *MutatorHydrator) Validate(config json.RawMessage) error { + if !a.c.MutatorIsEnabled(a.GetID()) { + return NewErrMutatorNotEnabled(a) } - return nil + + _, err := a.Config(config) + return err +} + +func (a *MutatorHydrator) Config(config json.RawMessage) (*MutatorHydratorConfig, error) { + var c MutatorHydratorConfig + if err := a.c.MutatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrMutatorMisconfigured(a, err) + } + + return &c, nil } diff --git a/pipeline/mutate/mutator_hydrator_test.go b/pipeline/mutate/mutator_hydrator_test.go index 01719e3f13..7b1f81ba80 100644 --- a/pipeline/mutate/mutator_hydrator_test.go +++ b/pipeline/mutate/mutator_hydrator_test.go @@ -114,7 +114,7 @@ func configWithBasicAuthnForMutator(user, password string) func(*httptest.Server func configWithRetriesForMutator(numberOfRetries, retriesDelayInMilliseconds int) func(*httptest.Server) json.RawMessage { return func(s *httptest.Server) json.RawMessage { - return []byte(fmt.Sprintf(`{"api": {"url": "%s", "retry": {"number": %d, "delayInMilliseconds": %d}}}`, s.URL, numberOfRetries, retriesDelayInMilliseconds)) + return []byte(fmt.Sprintf(`{"api": {"url": "%s", "retry": {"number_of_retries": %d, "delay_in_milliseconds": %d}}}`, s.URL, numberOfRetries, retriesDelayInMilliseconds)) } } @@ -218,7 +218,7 @@ func TestMutatorHydrator(t *testing.T) { }, Request: &http.Request{}, Match: newAuthenticationSession(), - Err: errors.New(mutate.ErrMissingAPIURL), + Err: errors.New("mutator matching this route is misconfigured or disabled"), }, "Improper Config": { Setup: defaultRouterSetup(), @@ -229,7 +229,7 @@ func TestMutatorHydrator(t *testing.T) { }, Request: &http.Request{}, Match: newAuthenticationSession(), - Err: errors.New(`json: unknown field "foo"`), + Err: errors.New("mutator matching this route is misconfigured or disabled"), }, "Not Found": { Setup: func(t *testing.T) http.Handler { @@ -244,7 +244,7 @@ func TestMutatorHydrator(t *testing.T) { Config: defaultConfigForMutator(), Request: &http.Request{}, Match: newAuthenticationSession(), - Err: errors.New(mutate.ErrNon200ResponseFromAPI), + Err: errors.New("The call to an external API returned a non-200 HTTP response"), }, "Wrong API URL": { Setup: defaultRouterSetup(), @@ -255,7 +255,7 @@ func TestMutatorHydrator(t *testing.T) { }, Request: &http.Request{}, Match: newAuthenticationSession(), - Err: errors.New(mutate.ErrInvalidAPIURL), + Err: errors.New("mutator matching this route is misconfigured or disabled"), }, "Successful Basic Authentication": { Setup: withBasicAuth(defaultRouterSetup(setExtra(sampleKey, sampleValue)), sampleUserId, sampleValidPassword), @@ -348,15 +348,16 @@ func TestMutatorHydrator(t *testing.T) { shouldPass bool }{ {enabled: false, shouldPass: false}, - {enabled: true, shouldPass: true}, + {enabled: true, shouldPass: true, apiUrl: "http://api/bar"}, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { viper.Set(configuration.ViperKeyMutatorHydratorIsEnabled, testCase.enabled) + err := a.Validate(json.RawMessage(`{"api":{"url":"` + testCase.apiUrl + `"}}`)) if testCase.shouldPass { - require.NoError(t, a.Validate()) + require.NoError(t, err) } else { - require.Error(t, a.Validate()) + require.Error(t, err) } }) } diff --git a/pipeline/mutate/mutator_id_token.go b/pipeline/mutate/mutator_id_token.go index c2c99d9edd..d5cc825cc9 100644 --- a/pipeline/mutate/mutator_id_token.go +++ b/pipeline/mutate/mutator_id_token.go @@ -26,11 +26,10 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "text/template" "time" - "github.com/ory/x/jsonx" - "github.com/dgrijalva/jwt-go" "github.com/pborman/uuid" @@ -53,7 +52,10 @@ type MutatorIDToken struct { } type CredentialsIDTokenConfig struct { - Claims string `json:"claims"` + Claims string `json:"claims"` + IssuerURL string `json:"issuer_url"` + JWKSURL string `json:"jwks_url"` + TTL string `json:"ttl"` } func (c *CredentialsIDTokenConfig) ClaimsTemplateID() string { @@ -74,13 +76,9 @@ func (a *MutatorIDToken) WithCache(t *template.Template) { func (a *MutatorIDToken) Mutate(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, rl pipeline.Rule) error { var claims = jwt.MapClaims{} - if len(config) == 0 { - config = json.RawMessage("{}") - } - - var c CredentialsIDTokenConfig - if err := jsonx.NewStrictDecoder(bytes.NewBuffer(config)).Decode(&c); err != nil { - return errors.WithStack(err) + c, err := a.Config(config) + if err != nil { + return err } if len(c.Claims) > 0 { @@ -103,17 +101,31 @@ func (a *MutatorIDToken) Mutate(r *http.Request, session *authn.AuthenticationSe } } + if c.TTL == "" { + c.TTL = "1m" + } + + ttl, err := time.ParseDuration(c.TTL) + if err != nil { + return errors.WithStack(err) + } + now := time.Now().UTC() - claims["exp"] = now.Add(a.c.MutatorIDTokenTTL()).Unix() + claims["exp"] = now.Add(ttl).Unix() claims["jti"] = uuid.New() claims["iat"] = now.Unix() - claims["iss"] = a.c.MutatorIDTokenIssuerURL().String() + claims["iss"] = c.IssuerURL claims["nbf"] = now.Unix() claims["sub"] = session.Subject + jwks, err := url.Parse(c.JWKSURL) + if err != nil { + return errors.WithStack(err) + } + signed, err := a.r.CredentialsSigner().Sign( r.Context(), - a.c.MutatorIDTokenJWKSURL(), + jwks, claims, ) if err != nil { @@ -124,18 +136,20 @@ func (a *MutatorIDToken) Mutate(r *http.Request, session *authn.AuthenticationSe return nil } -func (a *MutatorIDToken) Validate() error { - if !a.c.MutatorIDTokenIsEnabled() { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID())) +func (a *MutatorIDToken) Validate(config json.RawMessage) error { + if !a.c.MutatorIsEnabled(a.GetID()) { + return NewErrMutatorNotEnabled(a) } - if a.c.MutatorIDTokenIssuerURL() == nil { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Configuration for mutator "%s" did not specify any values for configuration key "%s" and is thus disabled.`, a.GetID(), configuration.ViperKeyMutatorIDTokenIssuerURL)) - } + _, err := a.Config(config) + return err +} - if a.c.MutatorIDTokenJWKSURL() == nil { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Configuration for mutator "%s" did not specify any values for configuration key "%s" and is thus disabled.`, a.GetID(), configuration.ViperKeyMutatorIDTokenJWKSURL)) +func (a *MutatorIDToken) Config(config json.RawMessage) (*CredentialsIDTokenConfig, error) { + var c CredentialsIDTokenConfig + if err := a.c.MutatorConfig(a.GetID(), config, &c); err != nil { + return nil, NewErrMutatorMisconfigured(a, err) } - return nil + return &c, nil } diff --git a/pipeline/mutate/mutator_id_token_test.go b/pipeline/mutate/mutator_id_token_test.go index 35f86cd0e7..84f58c7290 100644 --- a/pipeline/mutate/mutator_id_token_test.go +++ b/pipeline/mutate/mutator_id_token_test.go @@ -33,6 +33,8 @@ import ( "text/template" "time" + "github.com/tidwall/sjson" + "github.com/ory/oathkeeper/rule" "github.com/dgrijalva/jwt-go" @@ -69,7 +71,7 @@ func TestMutatorIDToken(t *testing.T) { require.NoError(t, err) assert.Equal(t, "id_token", a.GetID()) - viper.Set(configuration.ViperKeyMutatorIDTokenIssuerURL, "/foo/bar") + viper.Set("mutators.id_token.config.issuer_url", "/foo/bar") t.Run("method=mutate", func(t *testing.T) { @@ -111,7 +113,7 @@ func TestMutatorIDToken(t *testing.T) { Config: json.RawMessage([]byte(`{"bad": "key"}`)), Match: jwt.MapClaims{}, K: "file://../../test/stub/jwks-hs.json", - Err: errors.New(`json: unknown field "bad"`), + Err: errors.New(`mutator matching this route is misconfigured or disabled`), }, { Rule: &rule.Rule{ID: "test-rule6"}, @@ -158,9 +160,8 @@ func TestMutatorIDToken(t *testing.T) { for i, tc := range testCases { t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { - viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, tc.K) - viper.Set(configuration.ViperKeyMutatorIDTokenTTL, tc.Ttl) - + tc.Config, _ = sjson.SetBytes(tc.Config, "jwks_url", tc.K) + tc.Config, _ = sjson.SetBytes(tc.Config, "ttl", tc.Ttl.String()) err := a.Mutate(r, tc.Session, tc.Config, tc.Rule) if tc.Err == nil { require.NoError(t, err) @@ -198,12 +199,12 @@ func TestMutatorIDToken(t *testing.T) { tc := testCase{ Rule: &rule.Rule{ID: "test-rule"}, Session: &authn.AuthenticationSession{Subject: "foo", Extra: map[string]interface{}{"abc": "value1", "def": "value2"}}, - Config: json.RawMessage([]byte(`{"claims": "{\"custom-claim\": \"{{ print .Extra.abc }}/{{ print .Extra.def }}\", \"aud\": [\"foo\", \"bar\"]}"}`)), + Config: json.RawMessage([]byte(`{"claims": "{\"custom-claim\": \"{{ print .Extra.abc }}/{{ print .Extra.def }}\", \"aud\": [\"foo\", \"bar\"]}", "jwks_url": "file://../../test/stub/jwks-ecdsa.json"}`)), K: "file://../../test/stub/jwks-ecdsa.json", } - viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, tc.K) - viper.Set(configuration.ViperKeyMutatorIDTokenTTL, tc.Ttl) + // viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, tc.K) + // viper.Set(configuration.ViperKeyMutatorIDTokenTTL, tc.Ttl) cache := template.New("rules") @@ -248,18 +249,19 @@ func TestMutatorIDToken(t *testing.T) { }{ {e: false, pass: false}, {e: true, pass: false}, - {e: true, i: "/foo", pass: false}, - {e: true, j: "/foo", pass: false}, - {e: true, i: "/foo", j: "/foo", pass: true}, + {e: true, i: "http://baz/foo", pass: false}, + {e: true, j: "", pass: false}, + {e: true, i: "http://baz/foo", j: "http://baz/foo", pass: true}, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { viper.Set(configuration.ViperKeyMutatorIDTokenIsEnabled, tc.e) - viper.Set(configuration.ViperKeyMutatorIDTokenIssuerURL, tc.i) - viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, tc.j) + // viper.Set(configuration.ViperKeyMutatorIDTokenIssuerURL, tc.i) + // viper.Set(configuration.ViperKeyMutatorIDTokenJWKSURL, tc.j) + err := a.Validate(json.RawMessage(`{"issuer_url":"` + tc.i + `", "jwks_url": "` + tc.j + `"}`)) if tc.pass { - require.NoError(t, a.Validate()) + require.NoError(t, err) } else { - require.Error(t, a.Validate()) + require.Error(t, err) } }) } diff --git a/pipeline/mutate/mutator_noop.go b/pipeline/mutate/mutator_noop.go index 7ccfb3b1f3..aacd365b7e 100644 --- a/pipeline/mutate/mutator_noop.go +++ b/pipeline/mutate/mutator_noop.go @@ -24,8 +24,6 @@ import ( "encoding/json" "net/http" - "github.com/pkg/errors" - "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/pipeline" "github.com/ory/oathkeeper/pipeline/authn" @@ -46,10 +44,13 @@ func (a *MutatorNoop) Mutate(r *http.Request, session *authn.AuthenticationSessi return nil } -func (a *MutatorNoop) Validate() error { - if !a.c.MutatorNoopIsEnabled() { - return errors.WithStack(ErrMutatorNotEnabled.WithReasonf(`Mutator "%s" is disabled per configuration.`, a.GetID())) +func (a *MutatorNoop) Validate(config json.RawMessage) error { + if !a.c.MutatorIsEnabled(a.GetID()) { + return NewErrMutatorNotEnabled(a) } + if err := a.c.MutatorConfig(a.GetID(), config, nil); err != nil { + return NewErrMutatorMisconfigured(a, err) + } return nil } diff --git a/pipeline/mutate/mutator_noop_test.go b/pipeline/mutate/mutator_noop_test.go index 7e15d55535..d85f9107ff 100644 --- a/pipeline/mutate/mutator_noop_test.go +++ b/pipeline/mutate/mutator_noop_test.go @@ -53,9 +53,9 @@ func TestMutatorNoop(t *testing.T) { t.Run("method=validate", func(t *testing.T) { viper.Set(configuration.ViperKeyMutatorNoopIsEnabled, true) - require.NoError(t, a.Validate()) + require.NoError(t, a.Validate(nil)) viper.Set(configuration.ViperKeyMutatorNoopIsEnabled, false) - require.Error(t, a.Validate()) + require.Error(t, a.Validate(nil)) }) } diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 864dd188e3..faefb763b6 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -32,10 +32,11 @@ import ( "github.com/ory/viper" + "github.com/ory/x/urlx" + "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/internal" "github.com/ory/oathkeeper/proxy" - "github.com/ory/x/urlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -43,34 +44,34 @@ import ( "github.com/ory/oathkeeper/rule" ) -//type jurorDenyAll struct{} +// type jurorDenyAll struct{} // -//func (j *jurorDenyAll) GetID() string { +// func (j *jurorDenyAll) GetID() string { // return "pass_through_deny" -//} +// } // -//func (j jurorDenyAll) Try(r *http.Request, rl *rule.Rule, u *url.URL) (*Session, error) { +// func (j jurorDenyAll) Try(r *http.Request, rl *rule.Rule, u *url.URL) (*Session, error) { // return nil, errors.WithStack(helper.ErrUnauthorized) -//} +// } // -//type jurorAcceptAll struct{} +// type jurorAcceptAll struct{} // -//func (j *jurorAcceptAll) GetID() string { +// func (j *jurorAcceptAll) GetID() string { // return "pass_through_accept" -//} +// } // -//func (j jurorAcceptAll) Try(r *http.Request, rl *rule.Rule, u *url.URL) (*Session, error) { +// func (j jurorAcceptAll) Try(r *http.Request, rl *rule.Rule, u *url.URL) (*Session, error) { // return &Session{ // Subject: "", // Anonymous: true, // ClientID: "", // Disabled: true, // }, nil -//} +// } func TestProxy(t *testing.T) { backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - //assert.NotEmpty(t, helper.BearerTokenFromRequest(r)) + // assert.NotEmpty(t, helper.BearerTokenFromRequest(r)) fmt.Fprint(w, "authorization="+r.Header.Get("Authorization")+"\n") fmt.Fprint(w, "host="+r.Host+"\n") fmt.Fprint(w, "url="+r.URL.String()) @@ -106,10 +107,10 @@ func TestProxy(t *testing.T) { Upstream: rule.Upstream{URL: backend.URL, StripPath: "/strip-path/", PreserveHost: true}, } - //acceptRuleStripHost := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_accept", Upstream: rule.Upstream{URLParsed: u, StripPath: "/users/", PreserveHost: true}} - //acceptRuleStripHostWithoutTrailing := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_accept", Upstream: rule.Upstream{URLParsed: u, StripPath: "/users", PreserveHost: true}} - //acceptRuleStripHostWithoutTrailing2 := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_accept", Upstream: rule.Upstream{URLParsed: u, StripPath: "users", PreserveHost: true}} - //denyRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_deny", Upstream: rule.Upstream{URLParsed: u}} + // acceptRuleStripHost := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_accept", Upstream: rule.Upstream{URLParsed: u, StripPath: "/users/", PreserveHost: true}} + // acceptRuleStripHostWithoutTrailing := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_accept", Upstream: rule.Upstream{URLParsed: u, StripPath: "/users", PreserveHost: true}} + // acceptRuleStripHostWithoutTrailing2 := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_accept", Upstream: rule.Upstream{URLParsed: u, StripPath: "users", PreserveHost: true}} + // denyRule := rule.Rule{MatchesMethods: []string{"GET"}, MatchesURLCompiled: mustCompileRegex(t, proxy.URL+"/users/<[0-9]+>"), Mode: "pass_through_deny", Upstream: rule.Upstream{URLParsed: u}} for k, tc := range []struct { url string @@ -274,7 +275,7 @@ func TestProxy(t *testing.T) { require.NoError(t, res.Body.Close()) require.NoError(t, err) - assert.Equal(t, tc.code, res.StatusCode) + assert.Equal(t, tc.code, res.StatusCode, "%s", res.Body) for _, m := range tc.messages { assert.True(t, strings.Contains(string(greeting), m), `Value "%s" not found in message: %s @@ -350,16 +351,16 @@ func TestConfigureBackendURL(t *testing.T) { } } -//func panicCompileRegex(pattern string) *regexp.Regexp { +// func panicCompileRegex(pattern string) *regexp.Regexp { // exp, err := regexp.Compile(pattern) // if err != nil { // panic(err.Error()) // } // return exp -//} +// } // -//func BenchmarkDirector(b *testing.B) { +// func BenchmarkDirector(b *testing.B) { // backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // fmt.Fprint(w, "authorization="+r.Header.Get("Authorization")) // fmt.Fprint(w, "host="+r.Header.Get("Host")) @@ -399,4 +400,4 @@ func TestConfigureBackendURL(t *testing.T) { // } // } // }) -//} +// } diff --git a/proxy/request_handler.go b/proxy/request_handler.go index 320af43717..e6ab5bba26 100644 --- a/proxy/request_handler.go +++ b/proxy/request_handler.go @@ -78,7 +78,7 @@ func (d *RequestHandler) HandleRequest(r *http.Request, rl *rule.Rule) (http.Hea return nil, err } - if err := anh.Validate(); err != nil { + if err := anh.Validate(a.Config); err != nil { d.r.Logger().WithError(err). WithField("granted", false). WithField("access_url", r.URL.String()). @@ -135,7 +135,7 @@ func (d *RequestHandler) HandleRequest(r *http.Request, rl *rule.Rule) (http.Hea return nil, err } - if err := azh.Validate(); err != nil { + if err := azh.Validate(rl.Authorizer.Config); err != nil { d.r.Logger().WithError(err). WithField("granted", false). WithField("access_url", r.URL.String()). @@ -178,7 +178,7 @@ func (d *RequestHandler) HandleRequest(r *http.Request, rl *rule.Rule) (http.Hea return nil, err } - if err := sh.Validate(); err != nil { + if err := sh.Validate(m.Config); err != nil { d.r.Logger().WithError(err). WithField("granted", false). WithField("access_url", r.URL.String()). diff --git a/rule/fetcher_default.go b/rule/fetcher_default.go index 4033fcf204..25da8ec20f 100644 --- a/rule/fetcher_default.go +++ b/rule/fetcher_default.go @@ -128,6 +128,7 @@ func (f *FetcherDefault) configUpdate(ctx context.Context, watcher *fsnotify.Wat // If there are no more sources to watch we reset the rule repository as a whole if len(replace) == 0 { + f.r.Logger().WithField("repos", viper.AllSettings()).Warn("No access rule repositories have been defined in the updated config.") if err := f.r.RuleRepository().Set(ctx, []Rule{}); err != nil { return err } @@ -241,6 +242,7 @@ func (f *FetcherDefault) watch(ctx context.Context, watcher *fsnotify.Watcher, e case e, ok := <-events: if !ok { // channel was closed + f.r.Logger().Debug("The events channel was closed") return nil } @@ -249,7 +251,7 @@ func (f *FetcherDefault) watch(ctx context.Context, watcher *fsnotify.Watcher, e f.r.Logger(). WithField("event", "config_change"). WithField("source", e.source). - Debugf("Access rule watcher received an update.") + Debugf("Viper detected a configuration change, reloading config.") if err := f.configUpdate(ctx, watcher, f.c.AccessRuleRepositories(), events); err != nil { return err } @@ -258,7 +260,7 @@ func (f *FetcherDefault) watch(ctx context.Context, watcher *fsnotify.Watcher, e WithField("event", "repository_change"). WithField("source", e.source). WithField("file", e.path.String()). - Debugf("Access rule watcher received an update.") + Debugf("One or more access rule repositories changed, reloading access rules.") rules, err := f.sourceUpdate(e) if err != nil { diff --git a/rule/fetcher_default_test.go b/rule/fetcher_default_test.go index 2b5612968f..f965f5f799 100644 --- a/rule/fetcher_default_test.go +++ b/rule/fetcher_default_test.go @@ -14,6 +14,7 @@ import ( "time" "github.com/google/uuid" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,19 +29,25 @@ import ( const testRule = `[{"id":"test-rule-5","upstream":{"preserve_host":true,"strip_path":"/api","url":"mybackend.com/api"},"match":{"url":"myproxy.com/api","methods":["GET","POST"]},"authenticators":[{"handler":"noop"},{"handler":"anonymous"}],"authorizer":{"handler":"allow"},"mutators":[{"handler":"noop"}]}]` func TestFetcherWatchConfig(t *testing.T) { + viper.Reset() + conf := internal.NewConfigurationWithDefaults() // this resets viper and must be at the top + r := internal.NewRegistry(conf) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(testRule)) })) defer ts.Close() + tempdir := os.TempDir() + id := uuid.New().String() - require.NoError(t, ioutil.WriteFile(filepath.Join(os.TempDir(), ".oathkeeper-"+id+".yml"), []byte(""), 0666)) + configFile := filepath.Join(tempdir, ".oathkeeper-"+id+".yml") + require.NoError(t, ioutil.WriteFile(configFile, []byte(""), 0666)) - viper.Reset() - viperx.InitializeConfig("oathkeeper-"+id, os.TempDir(), nil) - viperx.WatchConfig(nil, nil) - conf := internal.NewConfigurationWithDefaults() - r := internal.NewRegistry(conf) + l := logrus.New() + l.Level = logrus.TraceLevel + viperx.InitializeConfig("oathkeeper-"+id, tempdir, nil) + viperx.WatchConfig(l, nil) go func() { require.NoError(t, r.RuleFetcher().Watch(context.TODO())) @@ -78,7 +85,7 @@ access_rules: config: ` access_rules: repositories: - - file://../test/stub/rules.yaml + - file://../test/stub/rules.yaml `, expectIDs: []string{"test-rule-1-yaml"}, }, @@ -90,7 +97,7 @@ access_rules: }, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { - require.NoError(t, ioutil.WriteFile(filepath.Join(os.TempDir(), ".oathkeeper-"+id+".yml"), []byte(tc.config), 0666)) + require.NoError(t, ioutil.WriteFile(configFile, []byte(tc.config), 0666)) time.Sleep(time.Millisecond * 500) rules, err := r.RuleRepository().List(context.Background(), 500, 0) @@ -110,6 +117,10 @@ access_rules: } func TestFetcherWatchRepositoryFromFS(t *testing.T) { + viper.Reset() + conf := internal.NewConfigurationWithDefaults() // this resets viper!! + r := internal.NewRegistry(conf) + id := uuid.New().String() repository := path.Join(os.TempDir(), "access-rules-"+id+".json") require.NoError(t, ioutil.WriteFile(repository, []byte("[]"), 0666)) @@ -120,13 +131,9 @@ access_rules: - file://`+repository+` `), 0666)) - viper.Reset() viperx.InitializeConfig("oathkeeper-"+id, os.TempDir(), nil) viperx.WatchConfig(nil, nil) - conf := internal.NewConfigurationWithDefaults() - r := internal.NewRegistry(conf) - go func() { require.NoError(t, r.RuleFetcher().Watch(context.TODO())) }() @@ -162,6 +169,8 @@ access_rules: func TestFetcherWatchRepositoryFromKubernetesConfigMap(t *testing.T) { viper.Reset() + conf := internal.NewConfigurationWithDefaults() // this must be at the top because it resets viper + r := internal.NewRegistry(conf) // Set up temp dir and file to watch watchDir, err := ioutil.TempDir("", uuid.New().String()) @@ -170,8 +179,6 @@ func TestFetcherWatchRepositoryFromKubernetesConfigMap(t *testing.T) { // Configure watcher viper.Set(configuration.ViperKeyAccessRuleRepositories, []string{"file://" + watchFile}) - conf := internal.NewConfigurationWithDefaults() - r := internal.NewRegistry(conf) // This emulates a config map update // drwxr-xr-x 2 root root 4096 Aug 1 07:42 ..2019_08_01_07_42_33.068812649 diff --git a/rule/repository_memory.go b/rule/repository_memory.go index 3d04f82107..c631130846 100644 --- a/rule/repository_memory.go +++ b/rule/repository_memory.go @@ -27,6 +27,8 @@ import ( "github.com/pkg/errors" + "github.com/ory/x/viperx" + "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/x" @@ -91,7 +93,8 @@ func (m *RepositoryMemory) Get(ctx context.Context, id string) (*Rule, error) { func (m *RepositoryMemory) Set(ctx context.Context, rules []Rule) error { for _, check := range rules { if err := m.r.RuleValidator().Validate(&check); err != nil { - m.r.Logger().WithError(err).Errorf("A rule uses a malformed configuration and all URLs matching this rule will not work. You should resolve this issue now.") + viperx.LoggerWithValidationErrorFields(m.r.Logger(), err).WithError(err). + Errorf("A rule uses a malformed configuration and all URLs matching this rule will not work. You should resolve this issue now.") } } diff --git a/rule/repository_test.go b/rule/repository_test.go index 78dacc3fe1..6ba7f5b1a9 100644 --- a/rule/repository_test.go +++ b/rule/repository_test.go @@ -122,7 +122,7 @@ func TestRepository(t *testing.T) { } var index int - mr := &mockRepositoryRegistry{v: validatorNoop{ret: errors.New("")}} + mr := &mockRepositoryRegistry{v: validatorNoop{ret: errors.New("this is a forced test error and can be ignored")}} for name, repo := range map[string]Repository{ "memory": NewRepositoryMemory(mr), } { diff --git a/rule/validator.go b/rule/validator.go index 336d872367..a7042d39e3 100644 --- a/rule/validator.go +++ b/rule/validator.go @@ -74,7 +74,7 @@ func (v *ValidatorDefault) validateAuthenticators(r *Rule) error { return herodot.ErrInternalServerError.WithReasonf(`Value "%s" of "authenticators[%d]" is not in list of supported authenticators: %v`, a.Handler, k, v.r.AvailablePipelineAuthenticators()).WithTrace(err).WithDebug(err.Error()) } - if err := auth.Validate(); err != nil { + if err := auth.Validate(a.Config); err != nil { return err } } @@ -92,7 +92,7 @@ func (v *ValidatorDefault) validateAuthorizer(r *Rule) error { return errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`Value "%s" of "authorizer.handler" is not in list of supported authorizers: %v`, r.Authorizer.Handler, v.r.AvailablePipelineAuthorizers()).WithTrace(err).WithDebug(err.Error())) } - return auth.Validate() + return auth.Validate(r.Authorizer.Config) } func (v *ValidatorDefault) validateMutators(r *Rule) error { @@ -107,7 +107,7 @@ func (v *ValidatorDefault) validateMutators(r *Rule) error { v.r.AvailablePipelineMutators()).WithTrace(err).WithDebug(err.Error()) } - if err := mutator.Validate(); err != nil { + if err := mutator.Validate(m.Config); err != nil { return err } } diff --git a/test/e2e/config.yml b/test/e2e/config.yml index 9629fa7438..40d6d9058f 100644 --- a/test/e2e/config.yml +++ b/test/e2e/config.yml @@ -5,8 +5,9 @@ access_rules: mutators: id_token: enabled: true - issuer_url: https://my-oathkeeper/ - jwks_url: file://./jwks-idt.json + config: + issuer_url: https://my-oathkeeper/ + jwks_url: file://./jwks-idt.json authorizers: allow: @@ -15,9 +16,10 @@ authorizers: authenticators: jwt: enabled: true - jwks_urls: - - file://./jwks-authn.json - scope_strategy: none + config: + jwks_urls: + - file://./jwks-authn.json + scope_strategy: none serve: proxy: diff --git a/test/stub/rules.json b/test/stub/rules.json index 292d261e86..9d9b270a52 100644 --- a/test/stub/rules.json +++ b/test/stub/rules.json @@ -96,7 +96,10 @@ }, "mutators": [ { - "handler": "id_token" + "handler": "id_token", + "config": { + "jwks_url": "http://stub/" + } } ] } diff --git a/x/deepcopy.go b/x/deepcopy.go new file mode 100644 index 0000000000..f2fda249a0 --- /dev/null +++ b/x/deepcopy.go @@ -0,0 +1,43 @@ +/* + * Copyright © 2017-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2017-2018 Aeneas Rekkas + * @license Apache-2.0 + */ + +package x + +import ( + "bytes" + "encoding/json" +) + +// Deepcopy performs a deep copy of the given map m. +func Deepcopy(m map[string]interface{}) (map[string]interface{}, error) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + dec := json.NewDecoder(&buf) + err := enc.Encode(m) + if err != nil { + return nil, err + } + var copy map[string]interface{} + err = dec.Decode(©) + if err != nil { + return nil, err + } + return copy, nil +} diff --git a/x/deepcopy_test.go b/x/deepcopy_test.go new file mode 100644 index 0000000000..e97813a347 --- /dev/null +++ b/x/deepcopy_test.go @@ -0,0 +1,129 @@ +package x + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + testCases := []struct { + // original and expectedOriginal are the same value in each test case. We do + // this to avoid unintentionally asserting against a mutated + // expectedOriginal and having the test pass erroneously. We also do not + // want to rely on the deep copy function we are testing to ensure this does + // not happen. + original map[string]interface{} + transformer func(m map[string]interface{}) map[string]interface{} + expectedCopy map[string]interface{} + expectedOriginal map[string]interface{} + }{ + // reassignment of entire map, should be okay even without deepcopy. + { + original: nil, + transformer: func(m map[string]interface{}) map[string]interface{} { + return map[string]interface{}{} + }, + expectedCopy: map[string]interface{}{}, + expectedOriginal: nil, + }, + { + original: map[string]interface{}{}, + transformer: func(m map[string]interface{}) map[string]interface{} { + return nil + }, + expectedCopy: nil, + expectedOriginal: map[string]interface{}{}, + }, + // mutation of map + { + original: map[string]interface{}{}, + transformer: func(m map[string]interface{}) map[string]interface{} { + m["foo"] = "bar" + return m + }, + expectedCopy: map[string]interface{}{ + "foo": "bar", + }, + expectedOriginal: map[string]interface{}{}, + }, + { + original: map[string]interface{}{ + "foo": "bar", + }, + transformer: func(m map[string]interface{}) map[string]interface{} { + m["foo"] = "car" + return m + }, + expectedCopy: map[string]interface{}{ + "foo": "car", + }, + expectedOriginal: map[string]interface{}{ + "foo": "bar", + }, + }, + // mutation of nested maps + { + original: map[string]interface{}{}, + transformer: func(m map[string]interface{}) map[string]interface{} { + m["foo"] = map[string]interface{}{ + "biz": "baz", + } + return m + }, + expectedCopy: map[string]interface{}{ + "foo": map[string]interface{}{ + "biz": "baz", + }, + }, + expectedOriginal: map[string]interface{}{}, + }, + { + original: map[string]interface{}{ + "foo": map[string]interface{}{ + "biz": "booz", + "gaz": "gooz", + }, + }, + transformer: func(m map[string]interface{}) map[string]interface{} { + m["foo"] = map[string]interface{}{ + "biz": "baz", + } + return m + }, + expectedCopy: map[string]interface{}{ + "foo": map[string]interface{}{ + "biz": "baz", + }, + }, + expectedOriginal: map[string]interface{}{ + "foo": map[string]interface{}{ + "biz": "booz", + "gaz": "gooz", + }, + }, + }, + // mutation of slice values + { + original: map[string]interface{}{ + "foo": []interface{}{"biz", "baz"}, + }, + transformer: func(m map[string]interface{}) map[string]interface{} { + m["foo"].([]interface{})[0] = "hiz" + return m + }, + expectedCopy: map[string]interface{}{ + "foo": []interface{}{"hiz", "baz"}, + }, + expectedOriginal: map[string]interface{}{ + "foo": []interface{}{"biz", "baz"}, + }, + }, + } + for i, tc := range testCases { + copy, err := Deepcopy(tc.original) + assert.NoError(t, err) + assert.Exactly(t, tc.expectedCopy, tc.transformer(copy), "copy was not mutated. test case: %d", i) + assert.Exactly(t, tc.expectedOriginal, tc.original, "original was mutated. test case: %d", i) + } +}