Files
2026-07-13 12:32:21 +08:00

316 lines
11 KiB
Lua

--- Client information module.
--
-- A set of functions to retrieve information about the client connecting to
-- Kong in the context of a given request.
--
-- See also:
-- [nginx.org/en/docs/http/ngx_http_realip_module.html](http://nginx.org/en/docs/http/ngx_http_realip_module.html)
-- @module kong.client
local phase_checker = require "kong.pdk.private.phases"
local is_valid_uuid = require("kong.tools.uuid").is_valid_uuid
local check_https = require("kong.tools.http").check_https
local ngx = ngx
local tonumber = tonumber
local check_phase = phase_checker.check
local check_not_phase = phase_checker.check_not
local PHASES = phase_checker.phases
local AUTH_AND_LATER = phase_checker.new(PHASES.access,
PHASES.header_filter,
PHASES.response,
PHASES.body_filter,
PHASES.log)
local TABLE_OR_NIL = { ["table"] = true, ["nil"] = true }
local stream_subsystem = ngx.config.subsystem == "stream"
local function new(self)
local _CLIENT = {}
---
-- Returns the remote address of the client making the request. This module
-- **always** returns the address of the client directly connecting to Kong.
-- That is, in cases when a load balancer is in front of Kong, this function
-- returns the load balancer's address, and **not** that of the
-- downstream client.
--
-- @function kong.client.get_ip
-- @phases certificate, rewrite, access, header_filter, response, body_filter, log
-- @treturn string The remote IP address of the client making the request.
-- @usage
-- -- Given a client with IP 127.0.0.1 making connection through
-- -- a load balancer with IP 10.0.0.1 to Kong answering the request for
-- -- https://example.com:1234/v1/movies
-- kong.client.get_ip() -- "10.0.0.1"
function _CLIENT.get_ip()
check_not_phase(PHASES.init_worker)
-- when proxying TLS request in second layer or doing TLS passthrough
-- realip_remote_addr is always the previous layer of nginx thus always unix:
if stream_subsystem and
(ngx.var.kong_tls_passthrough_block == "1" or ngx.var.ssl_protocol) then
return ngx.var.remote_addr
end
return ngx.var.realip_remote_addr or ngx.var.remote_addr
end
---
-- Returns the remote address of the client making the request. Unlike
-- `kong.client.get_ip`, this function will consider forwarded addresses in
-- cases when a load balancer is in front of Kong. Whether this function
-- returns a forwarded address or not depends on several Kong configuration
-- parameters:
--
-- * [trusted\_ips](https://docs.konghq.com/gateway/latest/reference/configuration/#trusted_ips)
-- * [real\_ip\_header](https://docs.konghq.com/gateway/latest/reference/configuration/#real_ip_header)
-- * [real\_ip\_recursive](https://docs.konghq.com/gateway/latest/reference/configuration/#real_ip_recursive)
--
-- @function kong.client.get_forwarded_ip
-- @phases certificate, rewrite, access, header_filter, response, body_filter, log
-- @treturn string The remote IP address of the client making the request,
-- considering forwarded addresses.
--
-- @usage
-- -- Given a client with IP 127.0.0.1 making connection through
-- -- a load balancer with IP 10.0.0.1 to Kong answering the request for
-- -- https://username:password@example.com:1234/v1/movies
--
-- kong.client.get_forwarded_ip() -- "127.0.0.1"
--
-- -- Note: This example assumes that 10.0.0.1 is one of the trusted IPs, and that
-- -- the load balancer adds the right headers matching with the configuration
-- -- of `real_ip_header`, e.g. `proxy_protocol`.
function _CLIENT.get_forwarded_ip()
check_not_phase(PHASES.init_worker)
return ngx.var.remote_addr
end
---
-- Returns the remote port of the client making the request. This
-- **always** returns the port of the client directly connecting to Kong. That
-- is, in cases when a load balancer is in front of Kong, this function
-- returns the load balancer's port, and **not** that of the downstream client.
-- @function kong.client.get_port
-- @phases certificate, rewrite, access, header_filter, response, body_filter, log
-- @treturn number The remote client port.
-- @usage
-- -- [client]:40000 <-> 80:[balancer]:30000 <-> 80:[kong]:20000 <-> 80:[service]
-- kong.client.get_port() -- 30000
function _CLIENT.get_port()
check_not_phase(PHASES.init_worker)
-- when proxying TLS request in second layer or doing TLS passthrough
-- realip_remote_addr is always the previous layer of nginx thus always unix:
if stream_subsystem and
(ngx.var.kong_tls_passthrough_block == "1" or ngx.var.ssl_protocol) then
return tonumber(ngx.var.remote_port)
end
return tonumber(ngx.var.realip_remote_port or ngx.var.remote_port)
end
---
-- Returns the remote port of the client making the request. Unlike
-- `kong.client.get_port`, this function will consider forwarded ports in cases
-- when a load balancer is in front of Kong. Whether this function returns a
-- forwarded port or not depends on several Kong configuration parameters:
--
-- * [trusted\_ips](https://docs.konghq.com/gateway/latest/reference/configuration/#trusted_ips)
-- * [real\_ip\_header](https://docs.konghq.com/gateway/latest/reference/configuration/#real_ip_header)
-- * [real\_ip\_recursive](https://docs.konghq.com/gateway/latest/reference/configuration/#real_ip_recursive)
-- @function kong.client.get_forwarded_port
-- @phases certificate, rewrite, access, header_filter, response, body_filter, log
-- @treturn number The remote client port, considering forwarded ports.
-- @usage
-- -- [client]:40000 <-> 80:[balancer]:30000 <-> 80:[kong]:20000 <-> 80:[service]
-- kong.client.get_forwarded_port() -- 40000
--
-- -- Note: This example assumes that [balancer] is one of the trusted IPs, and that
-- -- the load balancer adds the right headers matching with the configuration
-- -- of `real_ip_header`, e.g. `proxy_protocol`.
function _CLIENT.get_forwarded_port()
check_not_phase(PHASES.init_worker)
return tonumber(ngx.var.remote_port)
end
---
-- Returns the credentials of the currently authenticated consumer.
-- If not set yet, it returns `nil`.
-- @function kong.client.get_credential
-- @phases access, header_filter, response, body_filter, log
-- @treturn string The authenticated credential.
-- @usage
-- local credential = kong.client.get_credential()
-- if credential then
-- consumer_id = credential.consumer_id
-- else
-- -- request not authenticated yet
-- end
function _CLIENT.get_credential()
check_phase(AUTH_AND_LATER)
return ngx.ctx.authenticated_credential
end
---
-- Returns the consumer from the datastore.
-- Looks up the consumer by ID, and can optionally do a second search by name.
-- @function kong.client.load_consumer
-- @phases access, header_filter, response, body_filter, log
-- @tparam string consumer_id The consumer ID to look up.
-- @tparam[opt] boolean search_by_username If truthy,
-- and if the consumer is not found by ID,
-- then a second search by username will be performed.
-- @treturn table|nil Consumer entity or `nil`.
-- @treturn nil|err `nil` if successful, or an error message if it fails.
-- @usage
-- local consumer_id = "john_doe"
-- local consumer = kong.client.load_consumer(consumer_id, true)
function _CLIENT.load_consumer(consumer_id, search_by_username)
check_phase(AUTH_AND_LATER)
if not consumer_id or type(consumer_id) ~= "string" then
error("consumer_id must be a string", 2)
end
if not is_valid_uuid(consumer_id) and not search_by_username then
error("cannot load a consumer with an id that is not a uuid", 2)
end
if is_valid_uuid(consumer_id) then
local result, err = kong.db.consumers:select({ id = consumer_id })
if result then
return result
end
if err then
return nil, err
end
end
-- no error and if search_by_username, look up by username
if search_by_username then
return kong.db.consumers:select_by_username(consumer_id)
end
end
---
-- Returns the `consumer` entity of the currently authenticated consumer.
-- If not set yet, it returns `nil`.
-- @function kong.client.get_consumer
-- @phases access, header_filter, response, body_filter, log
-- @treturn table The authenticated consumer entity.
-- @usage
-- local consumer = kong.client.get_consumer()
-- if consumer then
-- consumer_id = consumer.id
-- else
-- -- request not authenticated yet, or a credential
-- -- without a consumer (external auth)
-- end
function _CLIENT.get_consumer()
check_phase(AUTH_AND_LATER)
return ngx.ctx.authenticated_consumer
end
---
-- Sets the authenticated consumer and/or credential for the current request.
-- While both `consumer` and `credential` can be `nil`,
-- at least one of them must exist. Otherwise, this function will throw an
-- error.
-- @function kong.client.authenticate
-- @phases access
-- @tparam table|nil consumer The consumer to set. If no
-- value is provided, then any existing value will be cleared.
-- @tparam table|nil credential The credential to set. If
-- no value is provided, then any existing value will be cleared.
-- @usage
-- -- assuming `credential` and `consumer` have been set by some authentication code
-- kong.client.authenticate(consumer, credentials)
function _CLIENT.authenticate(consumer, credential)
check_phase(PHASES.access)
if not TABLE_OR_NIL[type(consumer)] then
error("consumer must be a table or nil", 2)
elseif not TABLE_OR_NIL[type(credential)] then
error("credential must be a table or nil", 2)
elseif credential == nil and consumer == nil then
error("either credential or consumer must be provided", 2)
end
local ctx = ngx.ctx
ctx.authenticated_consumer = consumer
ctx.authenticated_credential = credential
end
---
-- Returns the protocol matched by the current route (`"http"`, `"https"`, `"tcp"` or
-- `"tls"`), or `nil`, if no route has been matched, which can happen when dealing with
-- erroneous requests.
-- @function kong.client.get_protocol
-- @phases access, header_filter, response, body_filter, log
-- @tparam[opt] boolean allow_terminated If set, the `X-Forwarded-Proto` header is checked when checking for HTTPS.
-- @treturn string|nil Can be one of `"http"`, `"https"`, `"tcp"`, `"tls"` or `nil`.
-- @treturn nil|err `nil` if successful, or an error message if it fails.
-- @usage
-- kong.client.get_protocol() -- "http"
function _CLIENT.get_protocol(allow_terminated)
check_phase(AUTH_AND_LATER)
local route = ngx.ctx.route
if not route then
return nil, "No active route found"
end
local protocols = route.protocols
if #protocols == 1 then
return protocols[1]
end
if ngx.config.subsystem == "http" then
local is_trusted = self.ip.is_trusted(self.client.get_ip())
local is_https, err = check_https(is_trusted, allow_terminated)
if err then
return nil, err
end
return is_https and "https" or "http"
end
-- else subsystem is stream
local balancer_data = ngx.ctx.balancer_data
local is_tls = balancer_data and balancer_data.scheme == "tls"
return is_tls and "tls" or "tcp"
end
return _CLIENT
end
return {
new = new,
}