Skip to content

Commit

Permalink
feat(admin-api) support for SSL in the admin API (#1706)
Browse files Browse the repository at this point in the history
  • Loading branch information
subnetmarco authored Oct 28, 2016
1 parent f6b4801 commit 7c60809
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 68 deletions.
18 changes: 18 additions & 0 deletions kong.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
# This API lets you configure and manage Kong,
# and should be kept private and secured.

#admin_listen_ssl = 0.0.0.0:8444 # Address and port on which Kong will accept
# HTTPS requests to the admin API, if
# `admin_ssl` is enabled.

#nginx_worker_processes = auto # Determines the number of worker processes
# spawned by Nginx.

Expand Down Expand Up @@ -91,6 +95,20 @@
# the SSL key for the `proxy_listen_ssl`
# address.

#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 `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.

#------------------------------------------------------------------------------
# DATASTORE
#------------------------------------------------------------------------------
Expand Down
30 changes: 23 additions & 7 deletions kong/cmd/utils/prefix_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,25 @@ local function find_resty_bin()
return found
end

local function gen_default_ssl_cert(kong_config)
local function gen_default_ssl_cert(kong_config, admin)
-- create SSL folder
local ok, err = pl_dir.makepath(pl_path.join(kong_config.prefix, "ssl"))
if not ok then return nil, err end

local ssl_cert = kong_config.ssl_cert_default
local ssl_cert_key = kong_config.ssl_cert_key_default
local ssl_cert_csr = kong_config.ssl_cert_csr_default
local ssl_cert, ssl_cert_key, ssl_cert_csr
if admin then
ssl_cert = kong_config.admin_ssl_cert_default
ssl_cert_key = kong_config.admin_ssl_cert_key_default
ssl_cert_csr = kong_config.admin_ssl_cert_csr_default
else
ssl_cert = kong_config.ssl_cert_default
ssl_cert_key = kong_config.ssl_cert_key_default
ssl_cert_csr = kong_config.ssl_cert_csr_default
end

if not pl_path.exists(ssl_cert) and not pl_path.exists(ssl_cert_key) then
log.verbose("generating default SSL certificate and key")
log.verbose("generating %s SSL certificate and key",
admin and "admin" or "default")

local passphrase = utils.random_string()
local commands = {
Expand All @@ -118,11 +126,12 @@ local function gen_default_ssl_cert(kong_config)
for i = 1, #commands do
local ok, _, _, stderr = pl_utils.executeex(commands[i])
if not ok then
return nil, "could not generate default SSL certificate: "..stderr
return nil, "could not generate "..(admin and "admin" or "default").." SSL certificate: "..stderr
end
end
else
log.verbose("default SSL certificate found at %s", ssl_cert)
log.verbose("%s SSL certificate found at %s",
admin and "admin" or "default", ssl_cert)
end

return true
Expand Down Expand Up @@ -242,6 +251,13 @@ 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
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 return nil, err end
kong_config.admin_ssl_cert = kong_config.admin_ssl_cert_default
kong_config.admin_ssl_cert_key = kong_config.admin_ssl_cert_key_default
end

-- check ulimit
local ulimit, err = get_ulimit()
Expand Down
29 changes: 29 additions & 0 deletions kong/conf_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ local PREFIX_PATHS = {
ssl_cert_default = {"ssl", "kong-default.crt"},
ssl_cert_key_default = {"ssl", "kong-default.key"},
ssl_cert_csr_default = {"ssl", "kong-default.csr"}
;
admin_ssl_cert_default = {"ssl", "admin-kong-default.crt"},
admin_ssl_cert_key_default = {"ssl", "admin-kong-default.key"},
admin_ssl_cert_csr_default = {"ssl", "admin-kong-default.csr"}
}

-- By default, all properties in the configuration are considered to
Expand All @@ -51,6 +55,7 @@ local CONF_INFERENCES = {
proxy_listen = {typ = "string"},
proxy_listen_ssl = {typ = "string"},
admin_listen = {typ = "string"},
admin_listen_ssl = {typ = "string"},
cluster_listen = {typ = "string"},
cluster_listen_rpc = {typ = "string"},
cluster_advertise = {typ = "string"},
Expand Down Expand Up @@ -81,6 +86,7 @@ local CONF_INFERENCES = {
dnsmasq_port = {typ = "number"},

ssl = {typ = "boolean"},
admin_ssl = {typ = "boolean"},

log_level = {enum = {"debug", "info", "notice", "warn",
"error", "crit", "alert", "emerg"}},
Expand Down Expand Up @@ -185,6 +191,21 @@ local function check_and_infer(conf)
end
end

if conf.admin_ssl 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
errors[#errors+1] = "admin_ssl_cert must be specified"
end

if conf.admin_ssl_cert and not pl_path.exists(conf.admin_ssl_cert) then
errors[#errors+1] = "admin_ssl_cert: no such file at "..conf.admin_ssl_cert
end
if conf.admin_ssl_cert_key and not pl_path.exists(conf.admin_ssl_cert_key) then
errors[#errors+1] = "admin_ssl_cert_key: no such file at "..conf.admin_ssl_cert_key
end
end

if conf.dns_resolver and conf.dnsmasq then
errors[#errors+1] = "must disable dnsmasq when a custom DNS resolver is specified"
elseif not conf.dns_resolver and not conf.dnsmasq then
Expand Down Expand Up @@ -351,16 +372,19 @@ local function load(path, custom_conf)
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 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)
end
Expand All @@ -373,6 +397,11 @@ local function load(path, custom_conf)
conf.ssl_cert_key = pl_path.abspath(conf.ssl_cert_key)
end

if conf.admin_ssl_cert and conf.admin_ssl_cert_key then
conf.admin_ssl_cert = pl_path.abspath(conf.admin_ssl_cert)
conf.admin_ssl_cert_key = pl_path.abspath(conf.admin_ssl_cert_key)
end

-- attach prefix files paths
for property, t_path in pairs(PREFIX_PATHS) do
conf[property] = pl_path.join(conf.prefix, unpack(t_path))
Expand Down
4 changes: 4 additions & 0 deletions kong/templates/kong_defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ anonymous_reports = on
proxy_listen = 0.0.0.0:8000
proxy_listen_ssl = 0.0.0.0:8443
admin_listen = 0.0.0.0:8001
admin_listen_ssl = 0.0.0.0:8444
nginx_worker_processes = auto
nginx_optimizations = on
nginx_daemon = on
mem_cache_size = 128m
ssl = on
ssl_cert = NONE
ssl_cert_key = NONE
admin_ssl = on
admin_ssl_cert = NONE
admin_ssl_cert_key = NONE
database = postgres
pg_host = 127.0.0.1
Expand Down
7 changes: 7 additions & 0 deletions kong/templates/nginx_kong.lua
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ server {
client_max_body_size 10m;
client_body_buffer_size 10m;
> if admin_ssl then
listen ${{ADMIN_LISTEN_SSL}} ssl;
ssl_certificate ${{ADMIN_SSL_CERT}};
ssl_certificate_key ${{ADMIN_SSL_CERT_KEY}};
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
> end
location / {
default_type application/json;
content_by_lua_block {
Expand Down
150 changes: 106 additions & 44 deletions spec/01-unit/02-conf_loader_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ describe("Configuration loader", function()
assert.equal("0.0.0.0:8001", conf.admin_listen)
assert.equal("0.0.0.0:8000", conf.proxy_listen)
assert.equal("0.0.0.0:8443", conf.proxy_listen_ssl)
assert.equal("0.0.0.0:8444", conf.admin_listen_ssl)
assert.is_nil(conf.ssl_cert) -- check placeholder value
assert.is_nil(conf.ssl_cert_key)
assert.is_nil(conf.admin_ssl_cert)
assert.is_nil(conf.admin_ssl_cert_key)
assert.is_nil(getmetatable(conf))
end)
it("loads a given file, with higher precedence", function()
Expand Down Expand Up @@ -66,6 +69,8 @@ describe("Configuration loader", function()
local conf = assert(conf_loader())
assert.equal("0.0.0.0", conf.admin_ip)
assert.equal(8001, conf.admin_port)
assert.equal("0.0.0.0", conf.admin_ssl_ip)
assert.equal(8444, conf.admin_ssl_port)
assert.equal("0.0.0.0", conf.proxy_ip)
assert.equal(8000, conf.proxy_port)
assert.equal("0.0.0.0", conf.proxy_ssl_ip)
Expand All @@ -88,6 +93,9 @@ describe("Configuration loader", function()
assert.equal("/usr/local/kong/ssl/kong-default.crt", conf.ssl_cert_default)
assert.equal("/usr/local/kong/ssl/kong-default.key", conf.ssl_cert_key_default)
assert.equal("/usr/local/kong/ssl/kong-default.csr", conf.ssl_cert_csr_default)
assert.equal("/usr/local/kong/ssl/admin-kong-default.crt", conf.admin_ssl_cert_default)
assert.equal("/usr/local/kong/ssl/admin-kong-default.key", conf.admin_ssl_cert_key_default)
assert.equal("/usr/local/kong/ssl/admin-kong-default.csr", conf.admin_ssl_cert_csr_default)
end)
it("strips comments ending settings", function()
local conf = assert(conf_loader("spec/fixtures/to-strip.conf"))
Expand Down Expand Up @@ -264,53 +272,107 @@ describe("Configuration loader", function()
assert.is_nil(err)
assert.is_table(conf)
end)
it("requires both SSL cert and key", function()
local conf, err = conf_loader(nil, {
ssl_cert = "/path/cert.pem"
})
assert.equal("ssl_cert_key must be specified", err)
assert.is_nil(conf)
describe("SSL", function()
describe("proxy", function()
it("requires both proxy SSL cert and key", function()
local conf, err = conf_loader(nil, {
ssl_cert = "/path/cert.pem"
})
assert.equal("ssl_cert_key must be specified", err)
assert.is_nil(conf)

conf, err = conf_loader(nil, {
ssl_cert_key = "/path/key.pem"
})
assert.equal("ssl_cert must be specified", err)
assert.is_nil(conf)
conf, err = conf_loader(nil, {
ssl_cert_key = "/path/key.pem"
})
assert.equal("ssl_cert must be specified", err)
assert.is_nil(conf)

conf, err = conf_loader(nil, {
ssl_cert = "spec/fixtures/kong_spec.crt",
ssl_cert_key = "spec/fixtures/kong_spec.key"
})
assert.is_nil(err)
assert.is_table(conf)
end)
it("requires SSL cert and key to exist", function()
local conf, _, errors = conf_loader(nil, {
ssl_cert = "/path/cert.pem",
ssl_cert_key = "/path/cert_key.pem"
})
assert.equal(2, #errors)
assert.contains("ssl_cert: no such file at /path/cert.pem", errors)
assert.contains("ssl_cert_key: no such file at /path/cert_key.pem", errors)
assert.is_nil(conf)
conf, err = conf_loader(nil, {
ssl_cert = "spec/fixtures/kong_spec.crt",
ssl_cert_key = "spec/fixtures/kong_spec.key"
})
assert.is_nil(err)
assert.is_table(conf)
end)
it("requires SSL cert and key to exist", function()
local conf, _, errors = conf_loader(nil, {
ssl_cert = "/path/cert.pem",
ssl_cert_key = "/path/cert_key.pem"
})
assert.equal(2, #errors)
assert.contains("ssl_cert: no such file at /path/cert.pem", errors)
assert.contains("ssl_cert_key: no such file at /path/cert_key.pem", errors)
assert.is_nil(conf)

conf, _, errors = conf_loader(nil, {
ssl_cert = "spec/fixtures/kong_spec.crt",
ssl_cert_key = "/path/cert_key.pem"
})
assert.equal(1, #errors)
assert.contains("ssl_cert_key: no such file at /path/cert_key.pem", errors)
assert.is_nil(conf)
end)
it("resolves SSL cert/key to absolute path", function()
local conf, err = conf_loader(nil, {
ssl_cert = "spec/fixtures/kong_spec.crt",
ssl_cert_key = "spec/fixtures/kong_spec.key"
})
assert.is_nil(err)
assert.is_table(conf)
assert.True(helpers.path.isabs(conf.ssl_cert))
assert.True(helpers.path.isabs(conf.ssl_cert_key))
conf, _, errors = conf_loader(nil, {
ssl_cert = "spec/fixtures/kong_spec.crt",
ssl_cert_key = "/path/cert_key.pem"
})
assert.equal(1, #errors)
assert.contains("ssl_cert_key: no such file at /path/cert_key.pem", errors)
assert.is_nil(conf)
end)
it("resolves SSL cert/key to absolute path", function()
local conf, err = conf_loader(nil, {
ssl_cert = "spec/fixtures/kong_spec.crt",
ssl_cert_key = "spec/fixtures/kong_spec.key"
})
assert.is_nil(err)
assert.is_table(conf)
assert.True(helpers.path.isabs(conf.ssl_cert))
assert.True(helpers.path.isabs(conf.ssl_cert_key))
end)
end)
describe("admin", function()
it("requires both admin SSL cert and key", function()
local conf, err = conf_loader(nil, {
admin_ssl_cert = "/path/cert.pem"
})
assert.equal("admin_ssl_cert_key must be specified", err)
assert.is_nil(conf)

conf, err = conf_loader(nil, {
admin_ssl_cert_key = "/path/key.pem"
})
assert.equal("admin_ssl_cert must be specified", err)
assert.is_nil(conf)

conf, err = conf_loader(nil, {
admin_ssl_cert = "spec/fixtures/kong_spec.crt",
admin_ssl_cert_key = "spec/fixtures/kong_spec.key"
})
assert.is_nil(err)
assert.is_table(conf)
end)
it("requires SSL cert and key to exist", function()
local conf, _, errors = conf_loader(nil, {
admin_ssl_cert = "/path/cert.pem",
admin_ssl_cert_key = "/path/cert_key.pem"
})
assert.equal(2, #errors)
assert.contains("admin_ssl_cert: no such file at /path/cert.pem", errors)
assert.contains("admin_ssl_cert_key: no such file at /path/cert_key.pem", errors)
assert.is_nil(conf)

conf, _, errors = conf_loader(nil, {
admin_ssl_cert = "spec/fixtures/kong_spec.crt",
admin_ssl_cert_key = "/path/cert_key.pem"
})
assert.equal(1, #errors)
assert.contains("admin_ssl_cert_key: no such file at /path/cert_key.pem", errors)
assert.is_nil(conf)
end)
it("resolves SSL cert/key to absolute path", function()
local conf, err = conf_loader(nil, {
admin_ssl_cert = "spec/fixtures/kong_spec.crt",
admin_ssl_cert_key = "spec/fixtures/kong_spec.key"
})
assert.is_nil(err)
assert.is_table(conf)
assert.True(helpers.path.isabs(conf.admin_ssl_cert))
assert.True(helpers.path.isabs(conf.admin_ssl_cert_key))
end)
end)
end)
it("honors path if provided even if a default file exists", function()
conf_loader.add_default_path("spec/fixtures/to-strip.conf")
Expand Down
Loading

0 comments on commit 7c60809

Please sign in to comment.