204 lines
5.3 KiB
Lua
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
|