ShaoZeMing commented on issue #6404:
URL: https://github.com/apache/apisix/issues/6404#issuecomment-1047048261


   my lua file  auth-hook.lua:
   
   ```
   --
   -- 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 init = require("apisix.init")
   local ipmatcher = require("resty.ipmatcher")
   local http = require("resty.http")
   local ck = require("resty.cookie")
   local ngx = ngx
   local ipairs = ipairs
   local type = type
   local rawget = rawget
   local rawset = rawset
   local setmetatable = setmetatable
   local string = string
   
   local plugin_name = "auth-hook"
   local hook_lrucache = core.lrucache.new({
       ttl = 60, count = 1024
   })
   
   local schema = {
       type = "object",
       properties = {
           auth_hook_id = {
               type = "string",
               minLength = 1,
               maxLength = 100,
               default = "unset"
           },
           auth_hook_uri = {
               type = "string",
               minLength = 1,
               maxLength = 4096
           },
           auth_hook_method = {
               type = "string",
               default = "GET",
               enum = { "GET", "POST" },
           },
           hook_headers = {
               type = "array",
               items = {
                   type = "string",
                   minLength = 1,
                   maxLength = 100
               },
               uniqueItems = true
           },
           hook_args = {
               type = "array",
               items = {
                   type = "string",
                   minLength = 1,
                   maxLength = 100
               },
               uniqueItems = true
           },
           hook_res_to_headers = {
               type = "array",
               items = {
                   type = "string",
                   minLength = 1,
                   maxLength = 100
               },
               uniqueItems = true
           },
           hook_keepalive = {
               type = "boolean",
               default = true
           },
           hook_keepalive_timeout = {
               type = "integer",
               minimum = 1000,
               default = 60000
           },
           hook_keepalive_pool = {
               type = "integer",
               minimum = 1,
               default = 50
           },
           hook_res_to_header_prefix = {
               type = "string",
               default = "X-",
               minLength = 1,
               maxLength = 100
           },
           hook_cache = {
               type = "boolean",
               default = false
           },
           check_termination = {
               type = "boolean",
               default = true
           },
           is_debug = {
               type = "boolean",
               default = false
           },
       },
       required = { "auth_hook_uri" },
   }
   
   local _M = {
       version = 0.1,
       priority = 1007,
       name = plugin_name,
       schema = schema,
   }
   
   function _M.check_schema(conf)
   
       return core.schema.check(schema, conf)
   
   end
   
   local function get_auth_token(ctx)
   
       local token = ctx.var.http_x_auth_token
       if token then
           return token
       end
   
       token = ctx.var.http_authorization
       if token then
           return token
       end
   
       token = ctx.var.arg_auth_token
       if token then
           return token
       end
   
       local cookie, err = ck:new()
       if not cookie or err then
           return nil
       end
   
       local val, error = cookie:get("auth-token")
       if error then
           return nil
       end
       return val
   
   end
   
   local function fail_response(message, init_values)
   
       local response = init_values or {}
       response.message = message
       return response
   
   end
   
   local function new_table()
       local t = {}
       local lt = {}
       local _mt = {
           __index = function(t, k)
               return rawget(lt, string.lower(k))
           end,
           __newindex = function(t, k, v)
               rawset(t, k, v)
               rawset(lt, string.lower(k), v)
           end,
       }
       return setmetatable(t, _mt)
   end
   
   --proxy headers
   local function request_headers(config, ctx)
   
       local req_headers = new_table()
       local headers = core.request.headers(ctx)
       local hook_headers = config.hook_headers
       if not hook_headers then
           return req_headers
       end
       for i, field in ipairs(hook_headers) do
           local v = headers[field]
           if v then
               req_headers[field] = v
           end
       end
       return req_headers
   
   end
   
   local function urlEncode(s)
       s = string.gsub(s, "([^%w%.%-])", function(c)
           if c == " " then
               return "+"
           end
           return string.format("%%%02X", string.byte(c))
       end)
       return s
   end
   
   --init headers
   local function res_init_headers(config, ctx)
       if config.is_debug then
           return
       end
       local prefix = config.hook_res_to_header_prefix or ''
       local hook_res_to_headers = config.hook_res_to_headers
   
       if type(hook_res_to_headers) ~= "table" then
           return
       end
   
       core.request.set_header(ctx, prefix .. "auth-data", nil)
       for i, val in ipairs(hook_res_to_headers) do
           local f = string.gsub(val, '_', '-')
           core.request.set_header(ctx, prefix .. f, nil)
           core.response.set_header(prefix .. f, nil)
   
       end
       return
   end
   
   --res headers
   local function res_to_headers(config, data, ctx)
   
       local prefix = config.hook_res_to_header_prefix or ''
       local hook_res_to_headers = config.hook_res_to_headers
       if type(hook_res_to_headers) ~= "table" or type(data) ~= "table" then
           return
       end
   
       for i, val in ipairs(hook_res_to_headers) do
           local v = data[val]
           if v then
               if type(v) == "table" then
                   v = core.json.encode(v)
               end
               v = urlEncode(v)
               local f = string.gsub(val, '_', '-')
               core.request.set_header(ctx, prefix .. f, v)
               if config.is_debug then
                   core.response.set_header(prefix .. f, v)
               end
           end
       end
   
       return
   end
   
   
   --proxy args
   local function get_hook_args(hook_args)
   
       local req_args = new_table()
       if not hook_args then
           return req_args
       end
       local args = ngx.req.get_uri_args()
       for i, field in ipairs(hook_args) do
           local v = args[field]
           if v then
               req_args[field] = v
           end
       end
       return req_args
   
   end
   
   -- Configure request parameters.
   local function hook_configure_params(args, config, hook_headers)
   
       hook_headers["Content-Type"] = "application/json;charset=utf-8"
       local auth_hook_params = {
           ssl_verify = false,
           method = config.auth_hook_method,
           headers = hook_headers,
       }
       if config.hook_keepalive then
           auth_hook_params.keepalive_timeout = config.hook_keepalive_timeout
           auth_hook_params.keepalive_pool = config.hook_keepalive_pool
       else
           auth_hook_params.keepalive = config.hook_keepalive
       end
       local url = config.auth_hook_uri .. "?" .. ngx.encode_args(args)
       return auth_hook_params, url
   
   end
   
   -- dns
   local function parse_domain_for_node(node)
       if not ipmatcher.parse_ipv4(node)
               and not ipmatcher.parse_ipv6(node)
       then
           local ip, err = init.parse_domain(node)
           if ip then
               return ip
           end
   
           if err then
               return nil, err
           end
       end
   
       return node
   end
   
   -- timeout in ms
   local function http_req(url, auth_hook_params)
   
       -- get domain
       local domain = ngx.re.match(url, [[//([\S]+?)/]])
       domain = (domain and 1 == #domain and domain[1]) or nil
       if not domain then
           ngx.log(ngx.ERR, "get the domain fail from url:", url)
           return { status = ngx.HTTP_BAD_REQUEST } ,"get the domain fail from 
url:" .. url
       end
   
       -- add param
       if not auth_hook_params.headers then
           auth_hook_params.headers = {}
       end
       auth_hook_params.headers.Host = domain
   
       -- get domain's ip
       local domain_ip, err1 = parse_domain_for_node(domain)
       if not domain_ip then
           ngx.log(ngx.ERR, "get the domain[", domain, "] ip by dns failed:", 
err1)
           return { status = ngx.HTTP_SERVICE_UNAVAILABLE},
           "get the domain[".. domain .. "] ip by dns failed:".. err1
       end
   
       -- http request
       local httpc = http.new()
       local temp_url = ngx.re.gsub(url, "//" .. domain .. "/", 
string.format("//%s/", domain_ip))
       httpc:set_timeout(1000 * 10)
       local res, err = httpc:request_uri(temp_url, auth_hook_params)
       if err then
           core.log.error("FAIL REQUEST [ ", core.json.encode(auth_hook_params),
                   " ] failed! res is nil, err:", err)
           return nil, err
       end
       return res
   
   end
   
   local function get_auth_info(config, ctx, action, path, client_ip, 
auth_token)
   
       local hook_headers = request_headers(config, ctx)
       hook_headers["X-Client-Ip"] = client_ip
       hook_headers["Authorization"] = auth_token
       hook_headers["Auth-Hook-Id"] = config.auth_hook_id
       local args = get_hook_args(config.hook_args)
       args['hook_path'] = path
       args['hook_action'] = action
       args['hook_client_ip'] = client_ip
       core.response.set_header("APISIX-Hook-Cache", 'no-cache')
       local auth_hook_params, url = hook_configure_params(args, config, 
hook_headers)
   
       if config.is_debug then
           core.log.warn("auth_hook_params:  ", 
core.json.encode(auth_hook_params))
       end
   
       local res, err = http_req(url, auth_hook_params)
       return { res = res, err = err }
   
   end
   
   function _M.rewrite(conf, ctx)
   
       local upstream_uri = ctx.var.upstream_uri
       if not upstream_uri then
           upstream_uri = ctx.var.uri
       end
       local index = string.find(upstream_uri, "?", 1)
       if index then
           upstream_uri = string.sub(upstream_uri, 1, index-1)
       end
   
       local action = ctx.var.request_method
       local client_ip = ctx.var.http_x_real_ip or core.request.get_ip(ctx)
       local config = conf
       local auth_token = get_auth_token(ctx)
       res_init_headers(config, ctx)
   
       if auth_token then
           local res
           if config.hook_cache then
               core.response.set_header("APISIX-Hook-Cache", 'cache')
               res = hook_lrucache(plugin_name .. "#" .. auth_token, 
config.version,
                       get_auth_info, config, ctx, action, upstream_uri, 
client_ip, auth_token)
           else
               res = get_auth_info(config, ctx, action, upstream_uri, 
client_ip, auth_token)
           end
   
           if res.err then
               core.response.set_header("Content-Type", 
"application/json;charset=utf-8")
               return 500, fail_response(res.err, { status_code = 
hook_res.status })
           end
   
           local hook_res = res.res
           local hook_body, err = core.json.decode(hook_res.body)
           if err then
               core.response.set_header("Content-Type", 
"application/json;charset=utf-8")
               return 500, fail_response("JSON decoding failed: " .. err,
                       { status_code = 500 })
           end
   
           if hook_res.status ~= 200 and config.check_termination then
               core.response.set_header("Content-Type", 
"application/json;charset=utf-8")
               return hook_res.status, fail_response(hook_body.message or 
'auth-hook check permission failed',
                       hook_body)
           end
   
           if  hook_body.data and  type(hook_body.data) == "table" then
               res_to_headers(config, hook_body.data, ctx)
           end
   
       elseif config.check_termination then
           core.response.set_header("Content-Type", 
"application/json;charset=utf-8")
           return 401, fail_response("Missing auth token in request",
                   { status_code = 401 })
       end
       core.log.info("auth-hook check permission passed")
   
   end
   
   return _M
   
   ```


-- 
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.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to