jenskeiner commented on a change in pull request #3308: URL: https://github.com/apache/apisix/pull/3308#discussion_r561675160
########## File path: doc/plugins/authz-keycloak.md ########## @@ -38,24 +38,37 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/ ## Attributes -| Name | Type | Requirement | Default | Valid | Description | -| ----------------------- | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | -| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | -| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | -| audience | string | optional | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | -| permissions | array[string] | optional | | | A string representing a set of one or more resources and scopes the client is seeking access. The format of the string must be: `RESOURCE_ID#SCOPE_ID`. | -| timeout | integer | optional | 3000 | [1000, ...] | Timeout(ms) for the http connection with the Identity Server. | -| ssl_verify | boolean | optional | true | | Verify if SSL cert matches hostname. | -| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | - -### Endpoints - -Endpoints can optionally be discovered by providing a URL pointing to Keycloak's discovery document for Authorization Services for the realm -in the `discovery` attribute. The token endpoint URL will then be determined from that document. Alternatively, the token endpoint can be -specified explicitly via the `token_endpoint` attribute. - -One of `discovery` and `token_endpoint` has to be set. If both are given, the value from `token_endpoint` is used. +| Name | Type | Requirement | Default | Valid | Description | +| ------------------------------ | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | +| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | +| resource_registration_endpoint | string | optional | | https://host.domain/auth/realms/foo/authz/protection/resource_set | A Keycloak Protection API-compliant resource registration endpoint. Overrides value from discovery, if given. | +| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | +| client_id | string | required | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | +| client_secret | string | optional | | | The client secret, if required. | +| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | +| permissions | array[string] | optional | | | Static permission to request, an array of strings each representing a resources and optionally one or more scopes the client is seeking access. | +| lazy_load_paths | boolean | optional | false | | Dynamically resolve the request URI to resource(s) using the resource registration endpoint instead of using the static permission. | +| http_method_as_scope | boolean | optional | false | | Map HTTP request type to scope of same name and add to all permissions requested. | Review comment: Correct. Will add. ########## File path: doc/plugins/authz-keycloak.md ########## @@ -38,24 +38,37 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/ ## Attributes -| Name | Type | Requirement | Default | Valid | Description | -| ----------------------- | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | -| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | -| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | -| audience | string | optional | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | -| permissions | array[string] | optional | | | A string representing a set of one or more resources and scopes the client is seeking access. The format of the string must be: `RESOURCE_ID#SCOPE_ID`. | -| timeout | integer | optional | 3000 | [1000, ...] | Timeout(ms) for the http connection with the Identity Server. | -| ssl_verify | boolean | optional | true | | Verify if SSL cert matches hostname. | -| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | - -### Endpoints - -Endpoints can optionally be discovered by providing a URL pointing to Keycloak's discovery document for Authorization Services for the realm -in the `discovery` attribute. The token endpoint URL will then be determined from that document. Alternatively, the token endpoint can be -specified explicitly via the `token_endpoint` attribute. - -One of `discovery` and `token_endpoint` has to be set. If both are given, the value from `token_endpoint` is used. +| Name | Type | Requirement | Default | Valid | Description | +| ------------------------------ | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | +| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | +| resource_registration_endpoint | string | optional | | https://host.domain/auth/realms/foo/authz/protection/resource_set | A Keycloak Protection API-compliant resource registration endpoint. Overrides value from discovery, if given. | +| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | +| client_id | string | required | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | +| client_secret | string | optional | | | The client secret, if required. | +| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | +| permissions | array[string] | optional | | | Static permission to request, an array of strings each representing a resources and optionally one or more scopes the client is seeking access. | +| lazy_load_paths | boolean | optional | false | | Dynamically resolve the request URI to resource(s) using the resource registration endpoint instead of using the static permission. | +| http_method_as_scope | boolean | optional | false | | Map HTTP request type to scope of same name and add to all permissions requested. | Review comment: Added. ########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -171,13 +222,13 @@ local function authz_keycloak_discover(url, ssl_verify, keepalive, timeout, keepalive = (keepalive ~= "no") Review comment: My bad, was copied over from `openidc` module. Fixed it and consolidated the HTTP client setup -- which is done in various places -- to apply options like `timeout`, `keepalive`, `ssl_verify`and others consistently. ########## File path: doc/plugins/authz-keycloak.md ########## @@ -38,24 +38,37 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/ ## Attributes -| Name | Type | Requirement | Default | Valid | Description | -| ----------------------- | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | -| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | -| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | -| audience | string | optional | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | -| permissions | array[string] | optional | | | A string representing a set of one or more resources and scopes the client is seeking access. The format of the string must be: `RESOURCE_ID#SCOPE_ID`. | -| timeout | integer | optional | 3000 | [1000, ...] | Timeout(ms) for the http connection with the Identity Server. | -| ssl_verify | boolean | optional | true | | Verify if SSL cert matches hostname. | -| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | - -### Endpoints - -Endpoints can optionally be discovered by providing a URL pointing to Keycloak's discovery document for Authorization Services for the realm -in the `discovery` attribute. The token endpoint URL will then be determined from that document. Alternatively, the token endpoint can be -specified explicitly via the `token_endpoint` attribute. - -One of `discovery` and `token_endpoint` has to be set. If both are given, the value from `token_endpoint` is used. +| Name | Type | Requirement | Default | Valid | Description | +| ------------------------------ | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | +| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | +| resource_registration_endpoint | string | optional | | https://host.domain/auth/realms/foo/authz/protection/resource_set | A Keycloak Protection API-compliant resource registration endpoint. Overrides value from discovery, if given. | +| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | +| client_id | string | required | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | Review comment: Ok, so I'm now again requiring one of `client_id` or `audience`, the latter for better backwards compatibility. Rationale: The plugin always uses the token endpoint to get a decision from Keycloak on the requested permissions. As per https://www.keycloak.org/docs/4.8/authorization_services/#_service_obtaining_permissions the `audience` parameter (what is either in `client_id` or `audience` for the plugin) is required when the `permission` parameter is set (which the plugin always does). If `lazy_load_paths` is `true`, the the plugin also needs to obtain an access token for itself from Keycloak. To that end, it most likely also needs `client_secret` to be set, but that depends on how Keycloak is configured. Hence, this is still optional. Also added test cases to check if one of `client_id` or `audience` is correctly required and schema verification fails if both are absent. ########## File path: doc/plugins/authz-keycloak.md ########## @@ -38,24 +38,37 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/ ## Attributes -| Name | Type | Requirement | Default | Valid | Description | -| ----------------------- | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | -| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | -| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | -| audience | string | optional | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | -| permissions | array[string] | optional | | | A string representing a set of one or more resources and scopes the client is seeking access. The format of the string must be: `RESOURCE_ID#SCOPE_ID`. | -| timeout | integer | optional | 3000 | [1000, ...] | Timeout(ms) for the http connection with the Identity Server. | -| ssl_verify | boolean | optional | true | | Verify if SSL cert matches hostname. | -| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | - -### Endpoints - -Endpoints can optionally be discovered by providing a URL pointing to Keycloak's discovery document for Authorization Services for the realm -in the `discovery` attribute. The token endpoint URL will then be determined from that document. Alternatively, the token endpoint can be -specified explicitly via the `token_endpoint` attribute. - -One of `discovery` and `token_endpoint` has to be set. If both are given, the value from `token_endpoint` is used. +| Name | Type | Requirement | Default | Valid | Description | +| ------------------------------ | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | +| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | +| resource_registration_endpoint | string | optional | | https://host.domain/auth/realms/foo/authz/protection/resource_set | A Keycloak Protection API-compliant resource registration endpoint. Overrides value from discovery, if given. | +| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | +| client_id | string | required | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | Review comment: Ok, so I'm now again requiring one of `client_id` or `audience`, the latter for better backwards compatibility. Rationale: The plugin always uses the token endpoint to get a decision from Keycloak on the requested permissions. As per https://www.keycloak.org/docs/4.8/authorization_services/#_service_obtaining_permissions the `audience` parameter (what is either in `client_id` or `audience` for the plugin) is required when the `permission` parameter is set (which the plugin always does). If `lazy_load_paths` is `true`, the plugin also needs to obtain an access token for itself from Keycloak. To that end, it most likely also needs `client_secret` to be set, but that depends on how Keycloak is configured. Hence, this is still optional. Also added test cases to check if one of `client_id` or `audience` is correctly required and schema verification fails if both are absent. ########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -224,31 +275,326 @@ local function authz_keycloak_get_token_endpoint(conf) end -local function is_path_protected(conf) - -- TODO if permissions are empty lazy load paths from Keycloak - if conf.permissions == nil then - return false +local function authz_keycloak_get_resource_registration_endpoint(conf) + return authz_keycloak_get_endpoint(conf, "resource_registration_endpoint") +end + + +-- computes access_token expires_in value (in seconds) +local function authz_keycloak_access_token_expires_in(opts, expires_in) + return (expires_in or opts.access_token_expires_in or 300) + - 1 - (opts.access_token_expires_leeway or 0) +end + + +-- computes refresh_token expires_in value (in seconds) +local function authz_keycloak_refresh_token_expires_in(opts, expires_in) + return (expires_in or opts.refresh_token_expires_in or 3600) + - 1 - (opts.refresh_token_expires_leeway or 0) +end + + +local function authz_keycloak_ensure_sa_access_token(conf) + local client_id = authz_keycloak_get_client_id(conf) + local ttl = conf.cache_ttl_seconds + local token_endpoint = authz_keycloak_get_token_endpoint(conf) + + if not token_endpoint then + log.error("Unable to determine token endpoint.") + return 500, "Unable to determine token endpoint." + end + + local session = authz_keycloak_cache_get("access_tokens", token_endpoint .. ":" + .. client_id) Review comment: See above, we're now requiring `client_id` (or `audience`). ########## File path: t/plugin/authz-keycloak.t ########## @@ -137,7 +224,7 @@ done "authz-keycloak": { "token_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token", "permissions": ["course_resource#view"], - "audience": "course_management", Review comment: Sure, I can add some cases that use it. ########## File path: doc/plugins/authz-keycloak.md ########## @@ -38,24 +38,37 @@ For more information on Keycloak, refer to [Keycloak Authorization Docs](https:/ ## Attributes -| Name | Type | Requirement | Default | Valid | Description | -| ----------------------- | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | -| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | -| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | -| audience | string | optional | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | -| permissions | array[string] | optional | | | A string representing a set of one or more resources and scopes the client is seeking access. The format of the string must be: `RESOURCE_ID#SCOPE_ID`. | -| timeout | integer | optional | 3000 | [1000, ...] | Timeout(ms) for the http connection with the Identity Server. | -| ssl_verify | boolean | optional | true | | Verify if SSL cert matches hostname. | -| policy_enforcement_mode | string | optional | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | | - -### Endpoints - -Endpoints can optionally be discovered by providing a URL pointing to Keycloak's discovery document for Authorization Services for the realm -in the `discovery` attribute. The token endpoint URL will then be determined from that document. Alternatively, the token endpoint can be -specified explicitly via the `token_endpoint` attribute. - -One of `discovery` and `token_endpoint` has to be set. If both are given, the value from `token_endpoint` is used. +| Name | Type | Requirement | Default | Valid | Description | +| ------------------------------ | ------------- | ----------- | --------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| discovery | string | optional | | https://host.domain/auth/realms/foo/.well-known/uma2-configuration | URL to discovery document for Keycloak Authorization Services. | +| token_endpoint | string | optional | | https://host.domain/auth/realms/foo/protocol/openid-connect/token | A OAuth2-compliant Token Endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. Overrides value from discovery, if given. | +| resource_registration_endpoint | string | optional | | https://host.domain/auth/realms/foo/authz/protection/resource_set | A Keycloak Protection API-compliant resource registration endpoint. Overrides value from discovery, if given. | +| grant_type | string | optional | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | | +| client_id | string | required | | | The client identifier of the resource server to which the client is seeking access. <br>This parameter is mandatory when parameter permission is defined. | Review comment: Ok, so I'm now again requiring one of `client_id` or `audience`, the latter for better backwards compatibility. Rationale: The plugin always uses the token endpoint to get a decision from Keycloak on the requested permissions. As per https://www.keycloak.org/docs/latest/authorization_services/#_service_obtaining_permissions the `audience` parameter (what is either in `client_id` or `audience` for the plugin) is required when the `permission` parameter is set (which the plugin always does). If `lazy_load_paths` is `true`, the plugin also needs to obtain an access token for itself from Keycloak. To that end, it most likely also needs `client_secret` to be set, but that depends on how Keycloak is configured. Hence, this is still optional. Also added test cases to check if one of `client_id` or `audience` is correctly required and schema verification fails if both are absent. ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org