Skip to content

Commit

Permalink
feat(conf) new listen directives for increased flexibility
Browse files Browse the repository at this point in the history
Updates to the `proxy_listen` and `admin_listen` configuration
properties allow users to configure Kong in more flexible ways,
potentially listening on multiple interfaces/ports at once, with
granular options, or not at all.

Disabling proxy and/or admin interfaces allows a Kong node to run in
"proxy only" or "admin only" modes, opening the way for "data-plane" and
"control-plane" modes.

Overall, the new format is closer to that of the NGINX `listen`
directive for compatibility and flexibility reasons.

From #3147

Signed-off-by: Thibault Charbonnier <thibaultcha@me.com>
  • Loading branch information
Tieske authored and thibaultcha committed Feb 8, 2018
1 parent da53316 commit 1b9976f
Show file tree
Hide file tree
Showing 18 changed files with 576 additions and 216 deletions.
90 changes: 37 additions & 53 deletions kong.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,31 @@
# NGINX
#------------------------------------------------------------------------------

#proxy_listen = 0.0.0.0:8000 # Address and port on which Kong will accept
# HTTP requests.
# This is the public-facing entrypoint of
# Kong, to which your consumers will make
# requests.
#proxy_listen = 0.0.0.0:8000, 0.0.0.0:8443 ssl
# Comma-separated list of addresses and ports on
# which the proxy server should listen to. The proxy
# server is the public entrypoint of Kong, which
# proxies traffic from your consumers. The `ssl`
# suffix requires that all connections through this
# port be made through SSL, and `http2` allows for
# HTTP/2 connections. And finally the `proxy_protocol`
# flag is allowed.
# If set to `off` the proxy will be disabled.
# Note: See http://nginx.org/en/docs/http/ngx_http_core_module.html#listen for
# a description of the accepted formats for this and other *_listen values.
#
# See https://www.nginx.com/resources/admin-guide/proxy-protocol/ for more
# details about the `proxy_protocol` parameter.

#proxy_listen_ssl = 0.0.0.0:8443 # Address and port on which Kong will accept
# HTTPS requests if `ssl` is enabled.

#admin_listen = 127.0.0.1:8001 # Address and port on which Kong will expose
# an entrypoint to the Admin API.
# This API lets you configure and manage Kong,
# and should be kept private and secured.

#admin_listen_ssl = 127.0.0.1:8444 # Address and port on which Kong will
# accept HTTPS requests to the admin API,
# if `admin_ssl` is enabled.
#admin_listen = 127.0.0.1:8000, 127.0.0.1:8443 ssl
# Comma-separated list of addresses and ports on
# which the admin interface is listening.
# This API lets you configure and manage Kong,
# and should be kept private and secured.
# The `ssl` suffix requires that all connections
# through this port be made through SSL, and `http2`
# allows for HTTP/2 connections.
# If set to `off` the Admin API will be disabled.

#nginx_user = nobody nobody # Defines user and group credentials used by
# worker processes. If group is omitted, a
Expand All @@ -110,12 +116,6 @@
# `m`, with a minimum recommended value of
# a few MBs.

#ssl = on # Determines if Nginx should be listening for
# HTTPS traffic on the `proxy_listen_ssl`
# address. If disabled, Nginx will only bind
# itself on `proxy_listen`, and all SSL
# settings will be ignored.

#ssl_cipher_suite = modern # Defines the TLS ciphers served by Nginx.
# Accepted values are `modern`, `intermediate`,
# `old`, or `custom`.
Expand All @@ -128,16 +128,13 @@
# This value is ignored if `ssl_cipher_suite`
# is not `custom`.

#ssl_cert = # If `ssl` is enabled, the absolute path to
#ssl_cert = # If ssl is enabled, the absolute path to
# the SSL certificate for the
# `proxy_listen_ssl` address.

#ssl_cert_key = # If `ssl` is enabled, the absolute path to
# the SSL key for the `proxy_listen_ssl`
# address.
# `proxy_listen` ssl addresses.

#http2 = off # Enables HTTP2 support for HTTPS traffic on
# the `proxy_listen_ssl` address.
#ssl_cert_key = # If ssl is enabled, the absolute path to
# the SSL key for the `proxy_listen` ssl
# addresses.

#client_ssl = off # Determines if Nginx should send client-side
# SSL certificates when proxying requests.
Expand All @@ -156,22 +153,13 @@
# and currently cannot be configured on a
# per-API basis.

#admin_ssl = on # Determines if Nginx should be listening for
# HTTPS traffic on the `admin_listen_ssl`
# address. If disabled, Nginx will only bind
# itself on `admin_listen`, and all SSL
# settings will be ignored.
#admin_ssl_cert = # If ssl is enabled on the admin interface,
# the absolute path to the SSL certificate
# for the `admin_listen` addresses.

#admin_ssl_cert = # If `admin_ssl` is enabled, the absolute path
# to the SSL certificate for the
# `admin_listen_ssl` address.

#admin_ssl_cert_key = # If `admin_ssl` is enabled, the absolute path
# to the SSL key for the `admin_listen_ssl`
# address.

#admin_http2 = off # Enables HTTP2 support for HTTPS traffic on
# the `admin_listen_ssl` address.
#admin_ssl_cert_key = # If ssl is enabled on the admin interface,
# the absolute path to the SSL key for the
# `admin_listen` addresses.

#upstream_keepalive = 60 # Sets the maximum number of idle keepalive
# connections to upstream servers that are
Expand Down Expand Up @@ -216,18 +204,13 @@
# This value sets the ngx_http_realip_module
# directive of the same name in the Nginx
# configuration.
#
# If this value receives `proxy_protocol`, the
# `proxy_protocol` parameter will be appended
# to the `listen` directive of the Nginx
# template.
# If set to `proxy_protocol` then at least one
# of the `proxy_listen` entries should have
# the `proxy_protocol` flag enabled.
# Note:
#
# See http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header
# for a description of this directive.
#
# See https://www.nginx.com/resources/admin-guide/proxy-protocol/ for more
# details about the `proxy_protocol` parameter.

#real_ip_recursive = off # This value sets the ngx_http_realip_module
# directive of the same name in the Nginx
Expand Down Expand Up @@ -492,3 +475,4 @@
#lua_socket_pool_size = 30 # Specifies the size limit for every cosocket
# connection pool associated with every remote
# server

19 changes: 11 additions & 8 deletions kong/cmd/utils/prefix_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ local function get_ulimit()
end
end

local function gather_system_infos(compile_env)
local function gather_system_infos()
local infos = {}

local ulimit, err = get_ulimit()
Expand Down Expand Up @@ -110,10 +110,6 @@ local function compile_conf(kong_config, conf_template)
compile_env = pl_tablex.merge(compile_env, kong_config, true) -- union
compile_env.dns_resolver = table.concat(compile_env.dns_resolver, " ")

compile_env.http2 = kong_config.http2 and " http2" or ""
compile_env.admin_http2 = kong_config.admin_http2 and " http2" or ""
compile_env.proxy_protocol = kong_config.real_ip_header == "proxy_protocol" and " proxy_protocol" or ""

local post_template = pl_template.substitute(conf_template, compile_env)
return string.gsub(post_template, "(${%b{}})", function(w)
local name = w:sub(4, -3)
Expand Down Expand Up @@ -202,7 +198,8 @@ local function prepare_prefix(kong_config, nginx_custom_template_path)
end

-- generate default SSL certs if needed
if kong_config.ssl and not kong_config.ssl_cert and not kong_config.ssl_cert_key then
if kong_config.proxy_ssl_enabled and not kong_config.ssl_cert and
not kong_config.ssl_cert_key then
log.verbose("SSL enabled, no custom certificate set: using default certificate")
local ok, err = gen_default_ssl_cert(kong_config)
if not ok then
Expand All @@ -211,7 +208,8 @@ local function prepare_prefix(kong_config, nginx_custom_template_path)
kong_config.ssl_cert = kong_config.ssl_cert_default
kong_config.ssl_cert_key = kong_config.ssl_cert_key_default
end
if kong_config.admin_ssl and not kong_config.admin_ssl_cert and not kong_config.admin_ssl_cert_key then
if kong_config.admin_ssl_enabled and not kong_config.admin_ssl_cert and
not kong_config.admin_ssl_cert_key then
log.verbose("Admin SSL enabled, no custom certificate set: using default certificate")
local ok, err = gen_default_ssl_cert(kong_config, true)
if not ok then
Expand Down Expand Up @@ -266,7 +264,12 @@ local function prepare_prefix(kong_config, nginx_custom_template_path)

for k, v in pairs(kong_config) do
if type(v) == "table" then
v = table.concat(v, ",")
if (getmetatable(v) or {}).__tostring then
-- the 'tostring' meta-method knows how to serialize
v = tostring(v)
else
v = table.concat(v, ",")
end
end
if v ~= "" then
buf[#buf+1] = k .. " = " .. tostring(v)
Expand Down
145 changes: 117 additions & 28 deletions kong/conf_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,8 @@ local PREFIX_PATHS = {
-- `array`: a comma-separated list
local CONF_INFERENCES = {
-- forced string inferences (or else are retrieved as numbers)
proxy_listen = {typ = "string"},
proxy_listen_ssl = {typ = "string"},
admin_listen = {typ = "string"},
admin_listen_ssl = {typ = "string"},
proxy_listen = {typ = "array"},
admin_listen = {typ = "array"},
db_update_frequency = { typ = "number" },
db_update_propagation = { typ = "number" },
db_cache_ttl = { typ = "number" },
Expand Down Expand Up @@ -102,11 +100,7 @@ local CONF_INFERENCES = {
dns_error_ttl = {typ = "number"},
dns_no_sync = {typ = "boolean"},

http2 = {typ = "boolean"},
admin_http2 = {typ = "boolean"},
ssl = {typ = "boolean"},
client_ssl = {typ = "boolean"},
admin_ssl = {typ = "boolean"},

proxy_access_log = {typ = "string"},
proxy_error_log = {typ = "string"},
Expand Down Expand Up @@ -230,7 +224,7 @@ local function check_and_infer(conf)
end
end

if conf.ssl then
if (table.concat(conf.proxy_listen, ",") .. " "):find("%sssl[%s,]") then
if conf.ssl_cert and not conf.ssl_cert_key then
errors[#errors+1] = "ssl_cert_key must be specified"
elseif conf.ssl_cert_key and not conf.ssl_cert then
Expand Down Expand Up @@ -260,7 +254,7 @@ local function check_and_infer(conf)
end
end

if conf.admin_ssl then
if (table.concat(conf.admin_listen, ",") .. " "):find("%sssl[%s,]") then
if conf.admin_ssl_cert and not conf.admin_ssl_cert_key then
errors[#errors+1] = "admin_ssl_cert_key must be specified"
elseif conf.admin_ssl_cert_key and not conf.admin_ssl_cert then
Expand Down Expand Up @@ -357,6 +351,85 @@ local function overrides(k, default_v, file_conf, arg_conf)
return value, k
end

-- @param value The options string to check for flags (whitespace separated)
-- @param flags List of boolean flags to check for.
-- @returns 1) remainder string after all flags removed, 2) table with flag
-- booleans, 3) sanitized flags string
local function parse_option_flags(value, flags)
assert(type(value) == "string")

value = " " .. value .. " "

local sanitized = ""
local result = {}

for _, flag in ipairs(flags) do
local count
local patt = "%s" .. flag .. "%s"

value, count = value:gsub(patt, " ")

if count > 0 then
result[flag] = true
sanitized = sanitized .. " " .. flag

else
result[flag] = false
end
end

return pl_stringx.strip(value), result, pl_stringx.strip(sanitized)
end

-- Parses a listener address line.
-- Supports multiple (comma separated) addresses, with 'ssl' and 'http2' flags.
-- Pre- and postfixed whitespace as well as comma's are allowed.
-- "off" as a first entry will return empty tables.
-- @value list of entries (strings)
-- @return list of parsed entries, each entry having fields `ip` (normalized string)
-- `port` (number), `ssl` (bool), `http2` (bool), `listener` (string, full listener)
local function parse_listeners(values)
local list = {}
local flags = { "ssl", "http2", "proxy_protocol" }
local usage = "must be of form: [off] | <ip>:<port> [" ..
table.concat(flags, "] [") .. "], [... next entry ...]"

if pl_stringx.strip(values[1]) == "off" then
return list
end

for _, entry in ipairs(values) do
-- parse the flags
local remainder, listener, cleaned_flags = parse_option_flags(entry, flags)

-- verify IP for remainder
local ip

if utils.hostname_type(remainder) == "name" then
-- it's not an IP address, so a name/wildcard/regex
ip = {}
ip.host, ip.port = remainder:match("(.+):([%d]+)$")

else
-- It's an IPv4 or IPv6, just normalize it
ip = utils.normalize_ip(remainder)
end

if not ip or not ip.port then
return nil, usage
end

listener.ip = ip.host
listener.port = ip.port
listener.listener = ip.host .. ":" .. ip.port ..
(#cleaned_flags == 0 and "" or " " .. cleaned_flags)

table.insert(list, listener)
end

return list
end

--- Load Kong configuration
-- The loaded configuration will have all properties from the default config
-- merged with the (optionally) specified config file, environment variables
Expand Down Expand Up @@ -478,24 +551,40 @@ local function load(path, custom_conf)

-- extract ports/listen ips
do
local ip_port_pat = "(.+):([%d]+)$"
local admin_ip, admin_port = string.match(conf.admin_listen, ip_port_pat)
local admin_ssl_ip, admin_ssl_port = string.match(conf.admin_listen_ssl, ip_port_pat)
local proxy_ip, proxy_port = string.match(conf.proxy_listen, ip_port_pat)
local proxy_ssl_ip, proxy_ssl_port = string.match(conf.proxy_listen_ssl, ip_port_pat)

if not admin_port then return nil, "admin_listen must be of form 'address:port'"
elseif not admin_ssl_port then return nil, "admin_listen_ssl must be of form 'address:port'"
elseif not proxy_port then return nil, "proxy_listen must be of form 'address:port'"
elseif not proxy_ssl_port then return nil, "proxy_listen_ssl must be of form 'address:port'" end
conf.admin_ip = admin_ip
conf.admin_ssl_ip = admin_ssl_ip
conf.proxy_ip = proxy_ip
conf.proxy_ssl_ip = proxy_ssl_ip
conf.admin_port = tonumber(admin_port)
conf.admin_ssl_port = tonumber(admin_ssl_port)
conf.proxy_port = tonumber(proxy_port)
conf.proxy_ssl_port = tonumber(proxy_ssl_port)
local err
-- this meta table will prevent the parsed table to be passed on in the
-- intermediate Kong config file in the prefix directory
local mt = { __tostring = function() return "" end }

conf.proxy_listeners, err = parse_listeners(conf.proxy_listen)
if err then
return nil, "proxy_listen " .. err
end

setmetatable(conf.proxy_listeners, mt) -- do not pass on, parse again
conf.proxy_ssl_enabled = false

for _, listener in ipairs(conf.proxy_listeners) do
if listener.ssl == true then
conf.proxy_ssl_enabled = true
break
end
end

conf.admin_listeners, err = parse_listeners(conf.admin_listen)
if err then
return nil, "admin_listen " .. err
end

setmetatable(conf.admin_listeners, mt) -- do not pass on, parse again
conf.admin_ssl_enabled = false

for _, listener in ipairs(conf.admin_listeners) do
if listener.ssl == true then
conf.admin_ssl_enabled = true
break
end
end
end

-- load absolute paths
Expand Down
Loading

0 comments on commit 1b9976f

Please sign in to comment.