From 3e6e6d7c01c0634296426a19c1c14bbb352819cc Mon Sep 17 00:00:00 2001 From: mpw Date: Fri, 10 Mar 2023 18:59:26 -0300 Subject: [PATCH 1/6] add authz-plugin, update session and oauth2 plugins, update chart --- charts/hub-gateway/Chart.yaml | 2 +- charts/hub-gateway/plugins/authz-helper.lua | 245 ++++++++++++++++++ charts/hub-gateway/plugins/authz.lua | 173 +++++++++++++ charts/hub-gateway/plugins/oauth2.lua | 20 +- charts/hub-gateway/plugins/session.lua | 27 +- charts/hub-gateway/templates/apisixroute.yaml | 15 +- charts/hub-gateway/values.yaml | 24 +- 7 files changed, 472 insertions(+), 34 deletions(-) create mode 100644 charts/hub-gateway/plugins/authz-helper.lua create mode 100644 charts/hub-gateway/plugins/authz.lua diff --git a/charts/hub-gateway/Chart.yaml b/charts/hub-gateway/Chart.yaml index 50b872e..9616246 100644 --- a/charts/hub-gateway/Chart.yaml +++ b/charts/hub-gateway/Chart.yaml @@ -19,7 +19,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.7.3" +version: "0.9.0" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/hub-gateway/plugins/authz-helper.lua b/charts/hub-gateway/plugins/authz-helper.lua new file mode 100644 index 0000000..eadb25c --- /dev/null +++ b/charts/hub-gateway/plugins/authz-helper.lua @@ -0,0 +1,245 @@ +-- 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 get_service = require("apisix.http.service").get +local http = require("resty.http") +local json = require("apisix.core.json") +local ngx = ngx +local ngx_time = ngx.time +local re_match = ngx.re.match +local pairs = pairs +local ipairs = ipairs +local type = type +local lrucache = core.lrucache.new({ + type = "plugin", +}) +local _M = {} + + +-- build a table of Nginx variables with some generality +-- between http subsystem and stream subsystem +local function build_var(conf, ctx) + return { + server_addr = ctx.var.server_addr, + server_port = ctx.var.server_port, + remote_addr = ctx.var.remote_addr, + remote_port = ctx.var.remote_port, + timestamp = ngx_time(), + } +end + +local function build_http_request(conf, ctx) + local request = { + scheme = core.request.get_scheme(ctx), + method = core.request.get_method(), + host = core.request.get_host(ctx), + port = core.request.get_port(ctx), + path = ctx.var.uri, + headers = core.request.headers(ctx), + query = core.request.get_uri_args(ctx), + } + + if conf.with_body then + request.body = core.request.get_body() + end + return request +end + + +local function build_http_route(conf, ctx, remove_upstream) + local route = core.table.clone(ctx.matched_route).value + + if remove_upstream and route and route.upstream then + route.upstream = nil + end + + return route +end + + +local function build_http_service(conf, ctx) + local service_id = ctx.service_id + + -- possible that there is no service bound to the route + if service_id then + local service = core.table.clone(get_service(service_id)).value + + if service then + if service.upstream then + service.upstream = nil + end + return service + else + core.log.error("failed to get service") + end + end + + return nil +end + +local function build_graphql_data(conf, ctx) + local input = json.decode(core.request.get_body()) + return { + query = input['query'], + variables = input['variables'], + operation = ctx.var.graphql_operation, + + } +end + +local function build_http_consumer(conf, ctx) + -- possible that there is no consumer bound to the route + if ctx.consumer then + return core.table.clone(ctx.consumer) + end + + return nil +end + +local function check_set_inputs(inputs) + for field, value in pairs(inputs) do + if type(field) ~= 'string' then + return false, 'invalid type as input field' + end + + if type(value) ~= 'string' and type(value) ~= 'number' then + return false, 'invalid type as input value' + end + + if #field == 0 then + return false, 'invalid field length in input' + end + end + + return true +end + +local function is_new_inputs_conf(inputs) + return + (inputs.add and type(inputs.add) == "table") or + (inputs.set and type(inputs.set) == "table") or + (inputs.remove and type(inputs.remove) == "table") +end + +local function build_extra_inputs(inputs) + local set = {} + local add = {} + if is_new_inputs_conf(inputs) then + if inputs.add then + for _, value in ipairs(inputs.add) do + local m, err = re_match(value, [[^([^:\s]+)\s*:\s*([^:]+)$]], "jo") + if not m then + return nil, err + end + core.table.insert_tail(add, m[1], m[2]) + end + end + + if inputs.set then + for field, value in pairs(inputs.set) do + --reform header from object into array, so can avoid use pairs, which is NYI + core.table.insert_tail(set, field, value) + end + end + + else + for field, value in pairs(inputs) do + core.table.insert_tail(set, field, value) + end + end + + return { + add = add, + set = set, + remove = inputs.remove or {}, + } +end + +function _M.build_opa_input(conf, ctx, subsystem) + local data = { + type = subsystem, + request = build_http_request(conf, ctx), + var = build_var(conf, ctx), + graphql = build_graphql_data(conf, ctx), + keto = { + endpoint = conf.keto_endpoint, + subject_set = {}, + }, + extra = {}, + } + + if conf.with_route then + data.route = build_http_route(conf, ctx, true) + end + + if conf.with_consumer then + data.consumer = build_http_consumer(conf, ctx) + end + + if conf.with_service then + data.service = build_http_service(conf, ctx) + end + + local cookie_name = "_hub_org" + local var_name = "cookie_" .. cookie_name + local cookie_value = ngx.var[var_name] + + -- Prepare object + local org_id = core.request.header(ctx, 'X-CLIENT-OWNER-ID') or cookie_value + data.keto.object = org_id or "undefined" + -- relation will be assigned by OPA, based on the GraphQL operation + + -- prepare subject + data.keto.subject_set.namespace = "User" + if core.request.header(ctx, 'X-CLIENT-ID') then + data.keto.subject_set.relation = "oauth2" + data.keto.subject_set.object = core.request.header(ctx, 'X-CLIENT-ID') + else + data.keto.subject_set.relation = "session" + data.keto.subject_set.object = core.request.header(ctx, 'X-USER-ID') + end + + if conf.extra_inputs then + if conf.inputs then + if not is_new_inputs_conf(conf.inputs) then + local ok, err = check_set_inputs(conf.inputs) + if not ok then + return false + end + end + end + + local input_op, err = core.lrucache.plugin_ctx(lrucache, ctx, nil, build_extra_inputs, conf.inputs) + if not input_op then + core.log.error("failed to create inputs: ", err) + return 503, "failed to create inputs" + end + local field_cnt = #input_op.set + for i = 1, field_cnt, 2 do + local val = core.utils.resolve_var(input_op.set[i+1], ctx.var) + data.extra[input_op.set[i]] = val + end + + end + + return { + input = data, + } +end + + +return _M diff --git a/charts/hub-gateway/plugins/authz.lua b/charts/hub-gateway/plugins/authz.lua new file mode 100644 index 0000000..5892e4b --- /dev/null +++ b/charts/hub-gateway/plugins/authz.lua @@ -0,0 +1,173 @@ +-- +-- 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 helper = require("apisix.plugins.authz.helper") +local json = require("apisix.core.json") +local type = type + + +local schema = { + type = "object", + properties = { + host = {type = "string"}, + ssl_verify = { + type = "boolean", + default = true, + }, + policy = {type = "string"}, + timeout = { + type = "integer", + minimum = 1, + maximum = 60000, + default = 3000, + description = "timeout in milliseconds", + }, + keepalive = {type = "boolean", default = true}, + keepalive_timeout = {type = "integer", minimum = 1000, default = 60000}, + keepalive_pool = {type = "integer", minimum = 1, default = 5}, + with_route = {type = "boolean", default = false}, + with_service = {type = "boolean", default = false}, + with_consumer = {type = "boolean", default = false}, + with_body = {type = "boolean", default = false}, + extra_inputs = {type = "boolean", default = false}, + inputs = { + description = "extra inputs for opa", + anyOf = { + { + type = "object", + minProperties = 1, + patternProperties = { + ["^[^:]+$"] = { + oneOf = { + {type = "string"}, + {type = "number"}, + } + } + }, + }, + { + properties = { + set = { + type = "object", + minProperties = 1, + patternProperties = { + ["^[^:]+$"] = { + oneOf = { + {type = "string"}, + {type = "number"}, + } + } + }, + }, + }, + } + } + } + }, + required = {"host", "policy"} +} + + +local _M = { + version = 0.1, + priority = 1, + name = "authz", + 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 + +function _M.access(conf, ctx) + local body = helper.build_opa_input(conf, ctx, "http") + + local params = { + method = "POST", + body = json.encode(body), + headers = { + ["Content-Type"] = "application/json", + }, + keepalive = conf.keepalive, + ssl_verify = conf.ssl_verify + } + + if conf.keepalive then + params.keepalive_timeout = conf.keepalive_timeout + params.keepalive_pool = conf.keepalive_pool + end + + local endpoint = conf.host .. "/v1/data/" .. conf.policy + + local httpc = http.new() + httpc:set_timeout(conf.timeout) + + local res, err = httpc:request_uri(endpoint, params) + + -- block by default when decision is unavailable + if not res then + return 403, json.encode({ + message = "OPA Decision is unavailable. Errors found in policy" + }) + end + + -- parse the results of the decision + local data, err = json.decode(res.body) + + if not data then + return 503, json.encode({ + message = "Invalid response body" + }) + end + + if not data.result then + return 503, json.encode({ + message = "Invalid decision format: " .. res.body .. " Err: `result field does not exist" + }) + end + + local result = data.result + + if not result.allow then + if result.headers then + core.response.set_header(result.headers) + end + + local status_code = 403 + if result.status_code then + status_code = result.status_code + end + + local reason = nil + if result.reason then + reason = type(result.reason) == "table" + and json.encode(result.reason) + or result.reason + end + + return status_code, reason + end +end + + +return _M diff --git a/charts/hub-gateway/plugins/oauth2.lua b/charts/hub-gateway/plugins/oauth2.lua index 9fa2425..a7a2b67 100644 --- a/charts/hub-gateway/plugins/oauth2.lua +++ b/charts/hub-gateway/plugins/oauth2.lua @@ -53,6 +53,10 @@ local schema = { type = "boolean", default = false }, + expose_owner = { + type = "boolean", + default = false + }, }, required = {"host"} } @@ -118,16 +122,12 @@ function _M.access(conf, ctx) }) end - -- Expose hydra_client_id id on $hydra_client_id variable if conf.expose_client_id then core.request.set_header(ctx, "X-CLIENT-ID", data.client_id) core.response.set_header("X-CLIENT-ID", data.client_id) - core.ctx.register_var("hydra_client_id", function(ctx) - return data.client_id - end) end - - -- Get kratos user id from hydra client contacts + + -- Lookup full hydra client data local params = { method = "GET", headers = { @@ -151,9 +151,17 @@ function _M.access(conf, ctx) return 401, err end + -- Get kratos user id from hydra client contacts and expose on x-user-id header core.request.set_header(ctx, "X-USER-ID", data.contacts[1]) core.response.set_header("X-USER-ID", data.contacts[1]) + -- Expose hydra client owner id on $oauth2_client_owner variable + if conf.expose_owner then + if not data.owner then + core.log.error("unable to get owner from response:", json.encode(data)) + end + core.request.set_header(ctx, "X-CLIENT-OWNER-ID", data.owner) + end end return _M diff --git a/charts/hub-gateway/plugins/session.lua b/charts/hub-gateway/plugins/session.lua index 71ed16d..0d5b79a 100644 --- a/charts/hub-gateway/plugins/session.lua +++ b/charts/hub-gateway/plugins/session.lua @@ -49,10 +49,6 @@ local schema = { minimum = 1, default = 5 }, - expose_user_data = { - type = "boolean", - default = false - }, expose_user_id = { type = "boolean", default = false @@ -77,6 +73,7 @@ end function _M.access(conf, ctx) local session_cookie_name = string.lower(conf.session_cookie_name or "ory_kratos_session") + local cookie_header = string.lower("cookie_" .. session_cookie_name) local cookie_value = ngx.var[cookie_header] @@ -134,32 +131,12 @@ function _M.access(conf, ctx) }) end - -- Expose user data response on $kratos_user_data variable - if conf.expose_user_data then - local user_data = ngx.encode_base64(res.body) - if not user_data then - return 401, json.encode({ - message = "Error while reading user_data" - }) - end - core.ctx.register_var("kratos_user_data", function(ctx) - return user_data - end) - end - - -- Expose user id on $kratos_user_id variable - -- Expose user email on $kratos_user_email variable + -- Expose user email and id on headers if conf.expose_user_id then core.request.set_header(ctx, "X-USER-ID", data.identity.id) core.response.set_header("X-USER-ID", data.identity.id) core.request.set_header(ctx, "X-USER-EMAIL", data.identity.traits.email) core.response.set_header("X-USER-EMAIL", data.identity.traits.email) - core.ctx.register_var("kratos_user_id", function(ctx) - return data.identity.id - end) - core.ctx.register_var("kratos_user_email", function(ctx) - return data.identity.traits.email - end) end end diff --git a/charts/hub-gateway/templates/apisixroute.yaml b/charts/hub-gateway/templates/apisixroute.yaml index 5373f06..67a6eb0 100644 --- a/charts/hub-gateway/templates/apisixroute.yaml +++ b/charts/hub-gateway/templates/apisixroute.yaml @@ -33,7 +33,6 @@ spec: enable: true config: host: {{ print "http://" $apisixPlugins.session.serviceName "." $namespace ".svc:" $apisixPlugins.session.servicePort | quote }} - expose_user_data: true expose_user_id: true session_cookie_name: {{ $sessionCookie }} {{- end }} @@ -45,6 +44,7 @@ spec: config: host: {{ print "http://" $apisixPlugins.oauth2.serviceName "." $namespace ".svc:" $apisixPlugins.oauth2.servicePort | quote }} expose_client_id: true + expose_owner: true {{- end }} {{- end }} {{- with .sessionRedirect }} @@ -56,6 +56,19 @@ spec: redirect_to: {{ .redirectTo | default false }} {{- end }} {{- end }} + {{- with .authz }} + {{- if .enabled }} + - name: authz + enable: true + config: + host: {{ print "http://" $apisixPlugins.authz.serviceName "." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} + policy: {{ .policy }} + keto_endpoint: {{ print "http://" $apisixPlugins.authz.serviceName "-keto-read." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} + with_route: true + with_service: true + extra_inputs: false + {{- end }} + {{- end }} {{- if .regexUri }} - name: proxy-rewrite enable: true diff --git a/charts/hub-gateway/values.yaml b/charts/hub-gateway/values.yaml index c534911..de6590a 100644 --- a/charts/hub-gateway/values.yaml +++ b/charts/hub-gateway/values.yaml @@ -16,6 +16,9 @@ routes: - OPTIONS oauth2: enabled: true + authz: + enabled: true + policy: 'hub/graphql/main' regexUri: - "/graphql" - "/" @@ -31,6 +34,9 @@ routes: - OPTIONS session: enabled: true + authz: + enabled: true + policy: 'hub/graphql/main' regexUri: - "/graphql" - "/" @@ -48,6 +54,9 @@ routes: enabled: true sessionJson: enabled: true + authz: + enabled: true + policy: 'hub/graphql/main' regexUri: - "/graphql" - "/" @@ -155,7 +164,12 @@ apisixPlugins: sessionJson: files: - plugins/session-json.lua - + authz: + serviceName: hub-permissions + servicePort: 80 + files: + - plugins/authz.lua + - plugins/authz-helper.lua apisix: enabled: true fullnameOverride: apisix @@ -248,6 +262,14 @@ apisix: mounts: - key: "session-json.lua" path: "/opts/custom_plugins/apisix/plugins/session-json.lua" + - name: "authz" + configMap: + name: "authz-plugin" + mounts: + - key: "authz.lua" + path: "/opts/custom_plugins/apisix/plugins/authz.lua" + - key: "authz-helper.lua" + path: "/opts/custom_plugins/apisix/plugins/authz/helper.lua" logs: enableAccessLog: true From fb4c80aadc032fa38e458c8534f3edab0357e90f Mon Sep 17 00:00:00 2001 From: mpw Date: Sat, 11 Mar 2023 12:39:14 -0300 Subject: [PATCH 2/6] remove unused extra_inputs variable, reading directly from request headers --- charts/hub-gateway/plugins/authz-helper.lua | 98 +------------------ charts/hub-gateway/plugins/authz.lua | 35 ------- charts/hub-gateway/templates/apisixroute.yaml | 6 +- 3 files changed, 5 insertions(+), 134 deletions(-) diff --git a/charts/hub-gateway/plugins/authz-helper.lua b/charts/hub-gateway/plugins/authz-helper.lua index eadb25c..e2949f6 100644 --- a/charts/hub-gateway/plugins/authz-helper.lua +++ b/charts/hub-gateway/plugins/authz-helper.lua @@ -21,12 +21,7 @@ local json = require("apisix.core.json") local ngx = ngx local ngx_time = ngx.time local re_match = ngx.re.match -local pairs = pairs -local ipairs = ipairs local type = type -local lrucache = core.lrucache.new({ - type = "plugin", -}) local _M = {} @@ -43,7 +38,7 @@ local function build_var(conf, ctx) end local function build_http_request(conf, ctx) - local request = { + return { scheme = core.request.get_scheme(ctx), method = core.request.get_method(), host = core.request.get_host(ctx), @@ -52,11 +47,6 @@ local function build_http_request(conf, ctx) headers = core.request.headers(ctx), query = core.request.get_uri_args(ctx), } - - if conf.with_body then - request.body = core.request.get_body() - end - return request end @@ -110,65 +100,6 @@ local function build_http_consumer(conf, ctx) return nil end -local function check_set_inputs(inputs) - for field, value in pairs(inputs) do - if type(field) ~= 'string' then - return false, 'invalid type as input field' - end - - if type(value) ~= 'string' and type(value) ~= 'number' then - return false, 'invalid type as input value' - end - - if #field == 0 then - return false, 'invalid field length in input' - end - end - - return true -end - -local function is_new_inputs_conf(inputs) - return - (inputs.add and type(inputs.add) == "table") or - (inputs.set and type(inputs.set) == "table") or - (inputs.remove and type(inputs.remove) == "table") -end - -local function build_extra_inputs(inputs) - local set = {} - local add = {} - if is_new_inputs_conf(inputs) then - if inputs.add then - for _, value in ipairs(inputs.add) do - local m, err = re_match(value, [[^([^:\s]+)\s*:\s*([^:]+)$]], "jo") - if not m then - return nil, err - end - core.table.insert_tail(add, m[1], m[2]) - end - end - - if inputs.set then - for field, value in pairs(inputs.set) do - --reform header from object into array, so can avoid use pairs, which is NYI - core.table.insert_tail(set, field, value) - end - end - - else - for field, value in pairs(inputs) do - core.table.insert_tail(set, field, value) - end - end - - return { - add = add, - set = set, - remove = inputs.remove or {}, - } -end - function _M.build_opa_input(conf, ctx, subsystem) local data = { type = subsystem, @@ -201,8 +132,7 @@ function _M.build_opa_input(conf, ctx, subsystem) -- Prepare object local org_id = core.request.header(ctx, 'X-CLIENT-OWNER-ID') or cookie_value data.keto.object = org_id or "undefined" - -- relation will be assigned by OPA, based on the GraphQL operation - + -- prepare subject data.keto.subject_set.namespace = "User" if core.request.header(ctx, 'X-CLIENT-ID') then @@ -213,33 +143,9 @@ function _M.build_opa_input(conf, ctx, subsystem) data.keto.subject_set.object = core.request.header(ctx, 'X-USER-ID') end - if conf.extra_inputs then - if conf.inputs then - if not is_new_inputs_conf(conf.inputs) then - local ok, err = check_set_inputs(conf.inputs) - if not ok then - return false - end - end - end - - local input_op, err = core.lrucache.plugin_ctx(lrucache, ctx, nil, build_extra_inputs, conf.inputs) - if not input_op then - core.log.error("failed to create inputs: ", err) - return 503, "failed to create inputs" - end - local field_cnt = #input_op.set - for i = 1, field_cnt, 2 do - local val = core.utils.resolve_var(input_op.set[i+1], ctx.var) - data.extra[input_op.set[i]] = val - end - - end - return { input = data, } end - return _M diff --git a/charts/hub-gateway/plugins/authz.lua b/charts/hub-gateway/plugins/authz.lua index 5892e4b..c380a19 100644 --- a/charts/hub-gateway/plugins/authz.lua +++ b/charts/hub-gateway/plugins/authz.lua @@ -44,41 +44,6 @@ local schema = { with_route = {type = "boolean", default = false}, with_service = {type = "boolean", default = false}, with_consumer = {type = "boolean", default = false}, - with_body = {type = "boolean", default = false}, - extra_inputs = {type = "boolean", default = false}, - inputs = { - description = "extra inputs for opa", - anyOf = { - { - type = "object", - minProperties = 1, - patternProperties = { - ["^[^:]+$"] = { - oneOf = { - {type = "string"}, - {type = "number"}, - } - } - }, - }, - { - properties = { - set = { - type = "object", - minProperties = 1, - patternProperties = { - ["^[^:]+$"] = { - oneOf = { - {type = "string"}, - {type = "number"}, - } - } - }, - }, - }, - } - } - } }, required = {"host", "policy"} } diff --git a/charts/hub-gateway/templates/apisixroute.yaml b/charts/hub-gateway/templates/apisixroute.yaml index 67a6eb0..bf12fcd 100644 --- a/charts/hub-gateway/templates/apisixroute.yaml +++ b/charts/hub-gateway/templates/apisixroute.yaml @@ -64,9 +64,9 @@ spec: host: {{ print "http://" $apisixPlugins.authz.serviceName "." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} policy: {{ .policy }} keto_endpoint: {{ print "http://" $apisixPlugins.authz.serviceName "-keto-read." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} - with_route: true - with_service: true - extra_inputs: false + with_route: false + with_service: false + with_consumer: false {{- end }} {{- end }} {{- if .regexUri }} From 64db55adca7ea018493c0b78a72243b46a6a1792 Mon Sep 17 00:00:00 2001 From: mpw Date: Sat, 11 Mar 2023 12:40:32 -0300 Subject: [PATCH 3/6] remove trailing space --- charts/hub-gateway/plugins/authz-helper.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/hub-gateway/plugins/authz-helper.lua b/charts/hub-gateway/plugins/authz-helper.lua index e2949f6..97252c1 100644 --- a/charts/hub-gateway/plugins/authz-helper.lua +++ b/charts/hub-gateway/plugins/authz-helper.lua @@ -130,7 +130,7 @@ function _M.build_opa_input(conf, ctx, subsystem) local cookie_value = ngx.var[var_name] -- Prepare object - local org_id = core.request.header(ctx, 'X-CLIENT-OWNER-ID') or cookie_value + local org_id = core.request.header(ctx, 'X-CLIENT-OWNER-ID') or cookie_value data.keto.object = org_id or "undefined" -- prepare subject From 579111b1776afe5be9959d634c005333b42fc93a Mon Sep 17 00:00:00 2001 From: mpw Date: Sat, 11 Mar 2023 12:56:09 -0300 Subject: [PATCH 4/6] Update oauth2.lua update comment --- charts/hub-gateway/plugins/oauth2.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/hub-gateway/plugins/oauth2.lua b/charts/hub-gateway/plugins/oauth2.lua index a7a2b67..cc7f870 100644 --- a/charts/hub-gateway/plugins/oauth2.lua +++ b/charts/hub-gateway/plugins/oauth2.lua @@ -155,7 +155,7 @@ function _M.access(conf, ctx) core.request.set_header(ctx, "X-USER-ID", data.contacts[1]) core.response.set_header("X-USER-ID", data.contacts[1]) - -- Expose hydra client owner id on $oauth2_client_owner variable + -- Expose hydra client owner id on request header if conf.expose_owner then if not data.owner then core.log.error("unable to get owner from response:", json.encode(data)) From 461afd4c074690c0b01e42b92b7b789af993485a Mon Sep 17 00:00:00 2001 From: mpw Date: Sat, 11 Mar 2023 15:33:56 -0300 Subject: [PATCH 5/6] Update apisixroute.yaml append -opa to hub-permissions service endpoint --- charts/hub-gateway/templates/apisixroute.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/hub-gateway/templates/apisixroute.yaml b/charts/hub-gateway/templates/apisixroute.yaml index bf12fcd..ae0ec3b 100644 --- a/charts/hub-gateway/templates/apisixroute.yaml +++ b/charts/hub-gateway/templates/apisixroute.yaml @@ -61,7 +61,7 @@ spec: - name: authz enable: true config: - host: {{ print "http://" $apisixPlugins.authz.serviceName "." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} + host: {{ print "http://" $apisixPlugins.authz.serviceName "-opa" "." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} policy: {{ .policy }} keto_endpoint: {{ print "http://" $apisixPlugins.authz.serviceName "-keto-read." $namespace ".svc:" $apisixPlugins.authz.servicePort | quote }} with_route: false From f570a5557363efd69fe856fb7781e4bb3c096303 Mon Sep 17 00:00:00 2001 From: mpw Date: Fri, 24 Mar 2023 13:26:37 -0300 Subject: [PATCH 6/6] remove keto object building from authz plugin --- charts/hub-gateway/plugins/authz-helper.lua | 24 +-------------------- charts/hub-gateway/plugins/oauth2.lua | 6 +----- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/charts/hub-gateway/plugins/authz-helper.lua b/charts/hub-gateway/plugins/authz-helper.lua index 97252c1..95e9e53 100644 --- a/charts/hub-gateway/plugins/authz-helper.lua +++ b/charts/hub-gateway/plugins/authz-helper.lua @@ -106,11 +106,7 @@ function _M.build_opa_input(conf, ctx, subsystem) request = build_http_request(conf, ctx), var = build_var(conf, ctx), graphql = build_graphql_data(conf, ctx), - keto = { - endpoint = conf.keto_endpoint, - subject_set = {}, - }, - extra = {}, + keto_endpoint = conf.keto_endpoint } if conf.with_route then @@ -125,24 +121,6 @@ function _M.build_opa_input(conf, ctx, subsystem) data.service = build_http_service(conf, ctx) end - local cookie_name = "_hub_org" - local var_name = "cookie_" .. cookie_name - local cookie_value = ngx.var[var_name] - - -- Prepare object - local org_id = core.request.header(ctx, 'X-CLIENT-OWNER-ID') or cookie_value - data.keto.object = org_id or "undefined" - - -- prepare subject - data.keto.subject_set.namespace = "User" - if core.request.header(ctx, 'X-CLIENT-ID') then - data.keto.subject_set.relation = "oauth2" - data.keto.subject_set.object = core.request.header(ctx, 'X-CLIENT-ID') - else - data.keto.subject_set.relation = "session" - data.keto.subject_set.object = core.request.header(ctx, 'X-USER-ID') - end - return { input = data, } diff --git a/charts/hub-gateway/plugins/oauth2.lua b/charts/hub-gateway/plugins/oauth2.lua index cc7f870..8758f1e 100644 --- a/charts/hub-gateway/plugins/oauth2.lua +++ b/charts/hub-gateway/plugins/oauth2.lua @@ -149,11 +149,7 @@ function _M.access(conf, ctx) local data, err = json.decode(res.body) if not data then return 401, err - end - - -- Get kratos user id from hydra client contacts and expose on x-user-id header - core.request.set_header(ctx, "X-USER-ID", data.contacts[1]) - core.response.set_header("X-USER-ID", data.contacts[1]) + end -- Expose hydra client owner id on request header if conf.expose_owner then