membphis commented on a change in pull request #1701: URL: https://github.com/apache/incubator-apisix/pull/1701#discussion_r439355808
########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -0,0 +1,167 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- +local core = require("apisix.core") +local http = require "resty.http" +local sub_str = string.sub +local url = require "net.url" +local tostring = tostring +local ngx = ngx +local plugin_name = "authz-keycloak" + + +local schema = { + type = "object", + properties = { + token_endpoint = {type = "string", minLength = 1, maxLength = 4096}, + permissions = { + type = "array", + items = { + type = "string", + minLength = 1, maxLength = 100 + }, + uniqueItems = true + }, + grant_type = { + type = "string", + default="urn:ietf:params:oauth:grant-type:uma-ticket" + }, + audience = {type = "string"}, Review comment: ditoo ########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -0,0 +1,167 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- +local core = require("apisix.core") +local http = require "resty.http" +local sub_str = string.sub +local url = require "net.url" +local tostring = tostring +local ngx = ngx +local plugin_name = "authz-keycloak" + + +local schema = { + type = "object", + properties = { + token_endpoint = {type = "string", minLength = 1, maxLength = 4096}, + permissions = { + type = "array", + items = { + type = "string", + minLength = 1, maxLength = 100 + }, + uniqueItems = true + }, + grant_type = { + type = "string", + default="urn:ietf:params:oauth:grant-type:uma-ticket" + }, + audience = {type = "string"}, + timeout = {type = "integer", minimum = 1000, default = 3000}, + policy_enforcement_mode = { + type = "string", + enum = {"ENFORCING", "PERMISSIVE"}, + default = "ENFORCING" + }, + keepalive = {type = "boolean", default = true}, + keepalive_timeout = {type = "integer", minimum = 1000, default = 60000}, + keepalive_pool = {type = "integer", minimum = 1, default = 5}, + + }, + required = {"token_endpoint"} +} + + +local _M = { + version = 0.1, + priority = 2000, + type = 'auth', + name = plugin_name, + schema = schema, +} + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) + if not ok then + return false, err + end + + return true +end + +local function is_path_protected(conf) + -- TODO if permissions are empty lazy load paths from Keycloak + if conf.permissions == nil then + return false + end + return true +end + + +local function evaluate_permissions(conf, token) + local url_decoded = url.parse(conf.token_endpoint) + local host = url_decoded.host + local port = url_decoded.port + + if ((not port) and url_decoded.scheme == "https") then Review comment: bad style, this is better: ``` if not port then if ... then ... else ... end end ``` ########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -0,0 +1,167 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- +local core = require("apisix.core") +local http = require "resty.http" +local sub_str = string.sub +local url = require "net.url" +local tostring = tostring +local ngx = ngx +local plugin_name = "authz-keycloak" + + +local schema = { + type = "object", + properties = { + token_endpoint = {type = "string", minLength = 1, maxLength = 4096}, + permissions = { + type = "array", + items = { + type = "string", + minLength = 1, maxLength = 100 + }, + uniqueItems = true + }, + grant_type = { + type = "string", + default="urn:ietf:params:oauth:grant-type:uma-ticket" + }, + audience = {type = "string"}, + timeout = {type = "integer", minimum = 1000, default = 3000}, + policy_enforcement_mode = { + type = "string", + enum = {"ENFORCING", "PERMISSIVE"}, + default = "ENFORCING" + }, + keepalive = {type = "boolean", default = true}, + keepalive_timeout = {type = "integer", minimum = 1000, default = 60000}, + keepalive_pool = {type = "integer", minimum = 1, default = 5}, + + }, + required = {"token_endpoint"} +} + + +local _M = { + version = 0.1, + priority = 2000, + type = 'auth', + name = plugin_name, + schema = schema, +} + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) + if not ok then + return false, err + end + + return true +end + +local function is_path_protected(conf) + -- TODO if permissions are empty lazy load paths from Keycloak + if conf.permissions == nil then + return false + end + return true +end + + +local function evaluate_permissions(conf, token) + local url_decoded = url.parse(conf.token_endpoint) + local host = url_decoded.host + local port = url_decoded.port + + if ((not port) and url_decoded.scheme == "https") then + port = 443 + elseif not port then + port = 80 + end + + if not is_path_protected(conf) and conf.policy_enforcement_mode == "ENFORCING" then + core.response.exit(403) + return + end + + local httpc = http.new() + httpc:set_timeout(conf.timeout) + + local permissions_encoded = ngx.encode_args({permission = conf.permissions}) + + local params = { + method = "POST", + body = ngx.encode_args({ + grant_type = conf.grant_type, + audience = conf.audience, + response_mode = "decision" + }) .. "&" .. permissions_encoded, + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded", + ["Authorization"] = token + } + } + + if conf.keepalive then + params.keepalive_timeout = conf.keepalive_timeout + params.keepalive_pool = conf.keepalive_pool + else + params.keepalive = conf.keepalive + end + + local httpc_res, httpc_err = httpc:request_uri(conf.token_endpoint, params) + + if not httpc_res then + core.log.error("error while sending authz request to [", host ,"] port[", + tostring(port), "] ", httpc_err) + core.response.exit(500, httpc_err) + return + end + + if httpc_res.status >= 400 then + core.log.error("status code: ", httpc_res.status, " msg: ", httpc_res.body) + core.response.exit(httpc_res.status, httpc_res.body) + end +end + + +local function fetch_jwt_token(ctx) + local token = core.request.header(ctx, "authorization") + if not token then + return nil, "authorization header not available" + end + + local prefix = sub_str(token, 1, 7) + if prefix ~= 'Bearer ' and prefix ~= 'bearer ' then + return "Bearer " .. token + end + return token +end + + +function _M.rewrite(conf, ctx) + local jwt_token, err = fetch_jwt_token(ctx) + if not jwt_token then + core.log.error("failed to fetch JWT token: ", err) + return 401, {message = "Missing JWT token in request"} + end + + evaluate_permissions(conf, jwt_token) + core.log.debug("hit keycloak-auth rewrite") Review comment: we should move this line to No.156 ![image](https://user-images.githubusercontent.com/6814606/84497181-d423f700-ace0-11ea-9a6f-472ffb0bc449.png) ########## File path: doc/plugins/authz-keycloak.md ########## @@ -0,0 +1,135 @@ +<!-- +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +--> + +[Chinese](authz-keycloak-cn.md) + +# Summary +- [**Name**](#name) +- [**Attributes**](#attributes) +- [**How To Enable**](#how-to-enable) +- [**Test Plugin**](#test-plugin) +- [**Disable Plugin**](#disable-plugin) +- [**Examples**](#examples) + + +## Name + +`authz-keycloak` is an authorization plugin to be used with the Keycloak Identity Server. Keycloak is an OAuth/OIDC and +UMA compliant Ideneity Server. Although, its developed to working in conjunction with Keycloak it should work with any +OAuth/OIDC and UMA compliant identity providers as well. + +For more information on JWT, refer to [Keycloak Authorization Docs](https://www.keycloak.org/docs/latest/authorization_services) for more information. + +## Attributes + +|Name |Requirement |Description| +|--------- |-------- |-----------| +| token_endpoint|required |A OAuth2-compliant Token Endpoint that supports the urn:ietf:params:oauth:grant-type:uma-ticket grant type.| +| grant_type |required |This parameter is required. Must be urn:ietf:params:oauth:grant-type:uma-ticket.| Review comment: is this an optional field? https://github.com/apache/incubator-apisix/pull/1701/files#diff-3f122a686f16b26c4f875474dd1859bdR54 ########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -0,0 +1,167 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- +local core = require("apisix.core") +local http = require "resty.http" +local sub_str = string.sub +local url = require "net.url" +local tostring = tostring +local ngx = ngx +local plugin_name = "authz-keycloak" + + +local schema = { + type = "object", + properties = { + token_endpoint = {type = "string", minLength = 1, maxLength = 4096}, + permissions = { + type = "array", + items = { + type = "string", + minLength = 1, maxLength = 100 + }, + uniqueItems = true + }, + grant_type = { + type = "string", + default="urn:ietf:params:oauth:grant-type:uma-ticket" + }, + audience = {type = "string"}, + timeout = {type = "integer", minimum = 1000, default = 3000}, + policy_enforcement_mode = { + type = "string", + enum = {"ENFORCING", "PERMISSIVE"}, + default = "ENFORCING" + }, + keepalive = {type = "boolean", default = true}, + keepalive_timeout = {type = "integer", minimum = 1000, default = 60000}, + keepalive_pool = {type = "integer", minimum = 1, default = 5}, + + }, + required = {"token_endpoint"} +} + + +local _M = { + version = 0.1, + priority = 2000, + type = 'auth', + name = plugin_name, + schema = schema, +} + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) Review comment: we can use `return core.schema.check(schema, conf)`, it is simpler ########## File path: apisix/plugins/authz-keycloak.lua ########## @@ -0,0 +1,167 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- +local core = require("apisix.core") +local http = require "resty.http" +local sub_str = string.sub +local url = require "net.url" +local tostring = tostring +local ngx = ngx +local plugin_name = "authz-keycloak" + + +local schema = { + type = "object", + properties = { + token_endpoint = {type = "string", minLength = 1, maxLength = 4096}, + permissions = { + type = "array", + items = { + type = "string", + minLength = 1, maxLength = 100 + }, + uniqueItems = true + }, + grant_type = { + type = "string", + default="urn:ietf:params:oauth:grant-type:uma-ticket" + }, + audience = {type = "string"}, + timeout = {type = "integer", minimum = 1000, default = 3000}, + policy_enforcement_mode = { + type = "string", + enum = {"ENFORCING", "PERMISSIVE"}, + default = "ENFORCING" + }, + keepalive = {type = "boolean", default = true}, + keepalive_timeout = {type = "integer", minimum = 1000, default = 60000}, + keepalive_pool = {type = "integer", minimum = 1, default = 5}, + + }, + required = {"token_endpoint"} +} + + +local _M = { + version = 0.1, + priority = 2000, + type = 'auth', + name = plugin_name, + schema = schema, +} + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) + if not ok then + return false, err + end + + return true +end + +local function is_path_protected(conf) + -- TODO if permissions are empty lazy load paths from Keycloak + if conf.permissions == nil then + return false + end + return true +end + + +local function evaluate_permissions(conf, token) + local url_decoded = url.parse(conf.token_endpoint) + local host = url_decoded.host + local port = url_decoded.port + + if ((not port) and url_decoded.scheme == "https") then + port = 443 + elseif not port then + port = 80 + end + + if not is_path_protected(conf) and conf.policy_enforcement_mode == "ENFORCING" then + core.response.exit(403) + return + end + + local httpc = http.new() + httpc:set_timeout(conf.timeout) + + local permissions_encoded = ngx.encode_args({permission = conf.permissions}) Review comment: ![image](https://user-images.githubusercontent.com/6814606/84497011-93c47900-ace0-11ea-85c6-1c3fa1a0ea45.png) ---------------------------------------------------------------- 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