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

204 lines
5.3 KiB
Lua

local constants = require("kong.constants")
local ws_client = require("resty.websocket.client")
local ws_server = require("resty.websocket.server")
local parse_url = require("socket.url").parse
local process_type = require("ngx.process").type
local cjson = require("cjson.safe")
local type = type
local table_insert = table.insert
local table_concat = table.concat
local encode_base64 = ngx.encode_base64
local unescape_uri = ngx.unescape_uri
local worker_id = ngx.worker.id
local fmt = string.format
local kong = kong
local ngx = ngx
local ngx_log = ngx.log
local ngx_DEBUG = ngx.DEBUG
local ngx_WARN = ngx.WARN
local ngx_ERR = ngx.ERR
local ngx_CLOSE = ngx.HTTP_CLOSE
local _log_prefix = "[clustering] "
local KONG_VERSION = kong.version
local CLUSTER_PROXY_SSL_TERMINATOR_SOCK = fmt("unix:%s/%s",
kong.configuration.socket_path,
constants.SOCKETS.CLUSTER_PROXY_SSL_TERMINATOR)
local _M = {}
function _M.parse_proxy_url(proxy_server)
local ret = {}
if proxy_server then
-- assume proxy_server is validated in conf_loader
local parsed = parse_url(proxy_server)
if parsed.scheme == "https" then
ret.proxy_url = CLUSTER_PROXY_SSL_TERMINATOR_SOCK
-- hide other fields to avoid it being accidently used
-- the connection details is statically rendered in nginx template
else -- http
ret.proxy_url = fmt("%s://%s:%s", parsed.scheme, unescape_uri(parsed.host), parsed.port or 443)
ret.scheme = parsed.scheme
ret.host = unescape_uri(parsed.host)
ret.port = parsed.port
end
if parsed.user and parsed.password then
ret.proxy_authorization = "Basic " .. encode_base64(unescape_uri(parsed.user) .. ":" .. unescape_uri(parsed.password))
end
end
return ret
end
local WS_OPTS = {
timeout = constants.CLUSTERING_TIMEOUT,
max_payload_len = kong.configuration.cluster_max_payload,
}
-- TODO: pick one random CP
function _M.connect_cp(dp, endpoint, protocols)
local conf = dp.conf
local address = conf.cluster_control_plane .. endpoint
local c = assert(ws_client:new(WS_OPTS))
local uri = "wss://" .. address .. "?node_id=" ..
kong.node.get_id() ..
"&node_hostname=" .. kong.node.get_hostname() ..
"&node_version=" .. KONG_VERSION
local opts = {
ssl_verify = true,
client_cert = dp.cert.cdata,
client_priv_key = dp.cert_key,
protocols = protocols,
}
if conf.cluster_use_proxy then
local proxy_opts = _M.parse_proxy_url(conf.proxy_server)
opts.proxy_opts = {
wss_proxy = proxy_opts.proxy_url,
wss_proxy_authorization = proxy_opts.proxy_authorization,
}
ngx_log(ngx_DEBUG, _log_prefix,
"using proxy ", proxy_opts.proxy_url, " to connect control plane")
end
if conf.cluster_mtls == "shared" then
opts.server_name = "kong_clustering"
else
-- server_name will be set to the host if it is not explicitly defined here
if conf.cluster_server_name ~= "" then
opts.server_name = conf.cluster_server_name
end
end
local ok, err = c:connect(uri, opts)
if not ok then
c:close()
return nil, uri, err
end
return c
end
function _M.connect_dp(dp_id, dp_hostname, dp_ip, dp_version)
local log_suffix = {}
if type(dp_id) == "string" then
table_insert(log_suffix, "id: " .. dp_id)
end
if type(dp_hostname) == "string" then
table_insert(log_suffix, "host: " .. dp_hostname)
end
if type(dp_ip) == "string" then
table_insert(log_suffix, "ip: " .. dp_ip)
end
if type(dp_version) == "string" then
table_insert(log_suffix, "version: " .. dp_version)
end
if #log_suffix > 0 then
log_suffix = " [" .. table_concat(log_suffix, ", ") .. "]"
else
log_suffix = ""
end
if not dp_id then
ngx_log(ngx_WARN, _log_prefix, "data plane didn't pass the id", log_suffix)
return nil, nil, 400
end
if not dp_version then
ngx_log(ngx_WARN, _log_prefix, "data plane didn't pass the version", log_suffix)
return nil, nil, 400
end
local wb, err = ws_server:new(WS_OPTS)
if not wb then
ngx_log(ngx_ERR, _log_prefix, "failed to perform server side websocket handshake: ", err, log_suffix)
return nil, nil, ngx_CLOSE
end
return wb, log_suffix
end
function _M.is_dp_worker_process()
if kong.configuration.role == "data_plane"
and not kong.sync -- privileged agent is only enabled when rpc sync is off
and kong.configuration.dedicated_config_processing == true then
return process_type() == "privileged agent"
end
return worker_id() == 0
end
-- encode/decode json with cjson or simdjson
local ok, simdjson_dec = pcall(require, "resty.simdjson.decoder")
if not ok or kong.configuration.cluster_cjson then
_M.json_decode = cjson.decode
_M.json_encode = cjson.encode
else
_M.json_decode = function(str)
-- enable yield and not reentrant for decode
local dec = simdjson_dec.new(true)
local res, err = dec:process(str)
dec:destroy()
return res, err
end
_M.json_encode = cjson.encode
--[[ TODO: make simdjson encoding more compatible with cjson
-- enable yield and reentrant for encode
local enc = require("resty.simdjson.encoder").new(true)
_M.json_encode = function(obj)
return enc:process(obj)
end
--]]
end
return _M