Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin-api) support for SSL in the admin API #1706

Merged
merged 2 commits into from
Oct 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -236,6 +245,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 @@ -79,6 +84,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 @@ -177,6 +183,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 @@ -343,16 +364,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 @@ -365,6 +389,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 @@ -116,6 +116,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