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

349 lines
12 KiB
Lua

---
-- The service module contains a set of functions to manipulate the connection
-- aspect of the request to the Service, such as connecting to a given host, IP
-- address/port, or choosing a given Upstream entity for load-balancing and
-- healthchecking.
--
-- @module kong.service
local balancer = require "kong.runloop.balancer"
local phase_checker = require "kong.pdk.private.phases"
local type = type
local error = error
local floor = math.floor
local ngx = ngx
local check_phase = phase_checker.check
local PHASES = phase_checker.phases
local access_and_rewrite_and_balancer_preread =
phase_checker.new(PHASES.rewrite, PHASES.access, PHASES.balancer, PHASES.preread)
local function new()
local service = {}
---
-- Sets the desired Upstream entity to handle the load-balancing step for
-- this request. Using this method is equivalent to creating a Service with a
-- `host` property equal to that of an Upstream entity (in which case, the
-- request would be proxied to one of the Targets associated with that
-- Upstream).
--
-- The `host` argument should receive a string equal to the name of one of the
-- Upstream entities currently configured.
--
-- @function kong.service.set_upstream
-- @phases access
-- @tparam string host
-- @treturn boolean|nil `true` on success, or `nil` if no upstream entities
-- where found
-- @treturn string|nil An error message describing the error if there was
-- one.
--
-- @usage
-- local ok, err = kong.service.set_upstream("service.prod")
-- if not ok then
-- kong.log.err(err)
-- return
-- end
function service.set_upstream(host)
check_phase(PHASES.access)
if type(host) ~= "string" then
error("host must be a string", 2)
end
local upstream = balancer.get_upstream_by_name(host)
if not upstream then
return nil, "could not find an Upstream named '" .. host .. "'"
end
ngx.ctx.balancer_data.host = host
return true
end
---
-- Sets the host and port on which to connect to for proxying the request.
-- Using this method is equivalent to ask Kong to not run the load-balancing
-- phase for this request, and consider it manually overridden.
-- Load-balancing components such as retries and health-checks will also be
-- ignored for this request. Use `kong.service.set_retries` to overwrite
-- retries count.
--
-- The `host` argument expects the hostname or IP address of the upstream
-- server, and the `port` expects a port number.
--
-- @function kong.service.set_target
-- @phases access
-- @tparam string host
-- @tparam number port
-- @usage
-- kong.service.set_target("service.local", 443)
-- kong.service.set_target("192.168.130.1", 80)
function service.set_target(host, port)
check_phase(access_and_rewrite_and_balancer_preread)
if type(host) ~= "string" then
error("host must be a string", 2)
end
if type(port) ~= "number" or floor(port) ~= port then
error("port must be an integer", 2)
end
if port < 0 or port > 65535 then
error("port must be an integer between 0 and 65535: given " .. port, 2)
end
ngx.var.upstream_host = host
local ctx = ngx.ctx
ctx.balancer_data.host = host
ctx.balancer_data.port = port
end
-- Sets the retry callback function when the target set by `service.set_target`
-- failed to connect. The callback function will be called with no argument and
-- must return `host`, `port` and `err` if any.
--
--
-- @function kong.service.set_target_retry_callback
-- @phases access
-- @tparam function retry_callback
-- @usage
-- kong.service.set_target_retry_callback(function() return "service.local", 443 end)
function service.set_target_retry_callback(retry_callback)
check_phase(PHASES.access)
if type(retry_callback) ~= "function" then
error("retry_callback must be a function", 2)
end
ngx.ctx.balancer_data.retry_callback = retry_callback
end
---
-- Sets the retries count for the current request. This will override the
-- default retries count set in the Upstream entity.
--
-- The `retries` argument expects an integer between 0 and 32767.
--
-- @function kong.service.set_retries
-- @phases access
-- @tparam number retries
-- @usage
-- kong.service.set_retries(233)
function service.set_retries(retries)
check_phase(PHASES.access)
if type(retries) ~= "number" or floor(retries) ~= retries then
error("retries must be an integer", 2)
end
if retries < 0 or retries > 32767 then
error("retries must be an integer between 0 and 32767: given " .. retries, 2)
end
local ctx = ngx.ctx
ctx.balancer_data.retries = retries
end
---
-- Sets the timeouts for the current request. This will override the
-- default timeouts set in the Upstream entity.
--
-- The `connect_timeout`, `write_timeout`, and `read_timeout` arguments expect
-- an integer between 1 and 2147483646.
--
-- @function kong.service.set_timeouts
-- @phases access
-- @tparam number connect_timeout
-- @tparam number write_timeout
-- @tparam number read_timeout
-- @usage
-- kong.service.set_timeouts(233, 233, 233)
function service.set_timeouts(connect_timeout, write_timeout, read_timeout)
check_phase(PHASES.access)
if type(connect_timeout) ~= "number" or floor(connect_timeout) ~= connect_timeout then
error("connect_timeout must be an integer", 2)
end
if connect_timeout < 1 or connect_timeout > 2147483646 then
error("connect_timeout must be an integer between 1 and 2147483646: given " .. connect_timeout, 2)
end
if type(write_timeout) ~= "number" or floor(write_timeout) ~= write_timeout then
error("write_timeout must be an integer", 2)
end
if write_timeout < 1 or write_timeout > 2147483646 then
error("write_timeout must be an integer between 1 and 2147483646: given " .. write_timeout, 2)
end
if type(read_timeout) ~= "number" or floor(read_timeout) ~= read_timeout then
error("read_timeout must be an integer", 2)
end
if read_timeout < 1 or read_timeout > 2147483646 then
error("read_timeout must be an integer between 1 and 2147483646: given " .. read_timeout, 2)
end
local ctx = ngx.ctx
ctx.balancer_data.connect_timeout = connect_timeout
ctx.balancer_data.write_timeout = write_timeout
ctx.balancer_data.read_timeout = read_timeout
end
local tls = require("resty.kong.tls")
local set_upstream_cert_and_key = tls.set_upstream_cert_and_key
local set_upstream_ssl_verify = tls.set_upstream_ssl_verify
local set_upstream_ssl_verify_depth = tls.set_upstream_ssl_verify_depth
local set_upstream_ssl_trusted_store = tls.set_upstream_ssl_trusted_store
---
-- Sets the client certificate used while handshaking with the Service.
--
-- The `chain` argument is the client certificate and intermediate chain (if any)
-- returned by functions such as [ngx.ssl.parse\_pem\_cert](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_pem_cert).
--
-- The `key` argument is the private key corresponding to the client certificate
-- returned by functions such as [ngx.ssl.parse\_pem\_priv\_key](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_pem_priv_key).
--
-- @function kong.service.set_tls_cert_key
-- @phases `rewrite`, `access`, `balancer`, `preread`
-- @tparam cdata chain The client certificate chain
-- @tparam cdata key The client certificate private key
-- @treturn boolean|nil `true` if the operation succeeded, `nil` if an error occurred
-- @treturn string|nil An error message describing the error if there was one
-- @usage
-- local chain = assert(ssl.parse_pem_cert(cert_data))
-- local key = assert(ssl.parse_pem_priv_key(key_data))
--
-- local ok, err = kong.service.set_tls_cert_key(chain, key)
-- if not ok then
-- -- do something with error
-- end
service.set_tls_cert_key = function(chain, key)
check_phase(access_and_rewrite_and_balancer_preread)
if type(chain) ~= "cdata" then
error("chain must be a parsed cdata object", 2)
end
if type(key) ~= "cdata" then
error("key must be a parsed cdata object", 2)
end
local res, err = set_upstream_cert_and_key(chain, key)
return res, err
end
---
-- Sets whether TLS verification is enabled while handshaking with the Service.
--
-- The `on` argument is a boolean flag, where `true` means upstream verification
-- is enabled and `false` disables it.
--
-- This call affects only the current request. If the trusted certificate store is
-- not set already (via [proxy_ssl_trusted_certificate](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_trusted_certificate)
-- or [kong.service.set_upstream_ssl_trusted_store](#kongserviceset_upstream_ssl_trusted_store)),
-- then TLS verification will always fail with "unable to get local issuer certificate" error.
--
-- @function kong.service.set_tls_verify
-- @phases `rewrite`, `access`, `balancer`, `preread`
-- @tparam boolean on Whether to enable TLS certificate verification for the current request
-- @treturn boolean|nil `true` if the operation succeeded, `nil` if an error occurred
-- @treturn string|nil An error message describing the error if there was one
-- @usage
-- local ok, err = kong.service.set_tls_verify(true)
-- if not ok then
-- -- do something with error
-- end
service.set_tls_verify = function(on)
check_phase(access_and_rewrite_and_balancer_preread)
if type(on) ~= "boolean" then
error("argument must be a boolean", 2)
end
return set_upstream_ssl_verify(on)
end
---
-- Sets the maximum depth of verification when validating upstream server's TLS certificate.
--
-- This call affects only the current request. For the depth to be actually used the verification
-- has to be enabled with either the [proxy_ssl_verify](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_verify)
-- directive or using the [kong.service.set_tls_verify](#kongserviceset_tls_verify) function.
--
-- @function kong.service.set_tls_verify_depth
-- @phases `rewrite`, `access`, `balancer`, `preread`
-- @tparam number depth Depth to use when validating. Must be non-negative
-- @treturn boolean|nil `true` if the operation succeeded, `nil` if an error occurred
-- @treturn string|nil An error message describing the error if there was one
-- @usage
-- local ok, err = kong.service.set_tls_verify_depth(3)
-- if not ok then
-- -- do something with error
-- end
service.set_tls_verify_depth = function(depth)
check_phase(access_and_rewrite_and_balancer_preread)
if type(depth) ~= "number" then
error("argument must be a number", 2)
end
return set_upstream_ssl_verify_depth(depth)
end
---
-- Sets the CA trust store to use when validating upstream server's TLS certificate.
--
-- This call affects only the current request. For the store to be actually used the verification
-- has to be enabled with either the [proxy_ssl_verify](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_verify)
-- directive or using the [kong.service.set_tls_verify](#kongserviceset_tls_verify) function.
--
-- The resty.openssl.x509.store object can be created by following
-- [examples](https://github.com/Kong/lua-kong-nginx-module#restykongtlsset_upstream_ssl_trusted_store) from the Kong/lua-kong-nginx-module repo.
--
-- @function kong.service.set_tls_verify_store
-- @phases `rewrite`, `access`, `balancer`, `preread`
-- @tparam table store resty.openssl.x509.store object to use
-- @treturn boolean|nil `true` if the operation succeeded, `nil` if an error occurred
-- @treturn string|nil An error message describing the error if there was one
-- @usage
-- local store = require("resty.openssl.x509.store")
-- local st = assert(store.new())
-- -- st:add(...certificate)
--
-- local ok, err = kong.service.set_tls_verify_store(st)
-- if not ok then
-- -- do something with error
-- end
service.set_tls_verify_store = function(store)
check_phase(access_and_rewrite_and_balancer_preread)
if type(store) ~= "table" then
error("argument must be a resty.openssl.x509.store object", 2)
end
return set_upstream_ssl_trusted_store(store)
end
return service
end
return {
new = new,
}