From ab81e24f97ef4b2bf8bc885d5198ce5f133ba24a Mon Sep 17 00:00:00 2001 From: Michal Cichra Date: Tue, 5 Jun 2018 12:42:43 +0200 Subject: [PATCH] [policy] introduce ssl_certificate phase * allows policies to serve ssl certificate * service is detected by host from SNI * policy should clear certificates before setting them --- gateway/conf/nginx.conf.liquid | 9 ++- gateway/conf/server.crt | 10 +++ gateway/conf/server.key | 8 +++ gateway/src/apicast/executor.lua | 2 +- gateway/src/apicast/policy.lua | 2 + .../policy/find_service/find_service.lua | 7 +- .../load_configuration/load_configuration.lua | 5 ++ spec/policy_spec.lua | 3 +- t/fixtures/policies/https/builtin/init.lua | 23 +++++++ t/listen-https.t | 64 ++++++++++++++++++- 10 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 gateway/conf/server.crt create mode 100644 gateway/conf/server.key create mode 100644 t/fixtures/policies/https/builtin/init.lua diff --git a/gateway/conf/nginx.conf.liquid b/gateway/conf/nginx.conf.liquid index 1d5ad18fe..5f85881b4 100644 --- a/gateway/conf/nginx.conf.liquid +++ b/gateway/conf/nginx.conf.liquid @@ -77,6 +77,9 @@ http { {% include tracer_conf %} {% endif %} + ssl_session_fetch_by_lua_block { require('apicast.executor'):ssl_session_fetch() } + ssl_session_store_by_lua_block { require('apicast.executor'):ssl_session_store() } + server { listen {{ port.management | default: 8090 }}; server_name {{ server_name.management | default: 'management _' }}; @@ -131,8 +134,10 @@ http { {% if https_port -%} listen {{ https_port }} ssl; - ssl_certificate {{ env.APICAST_HTTPS_CERTIFICATE }}; - ssl_certificate_key {{ env.APICAST_HTTPS_CERTIFICATE_KEY }}; + ssl_certificate {{ env.APICAST_HTTPS_CERTIFICATE | default: "conf/server.crt" | filesystem | first }}; + ssl_certificate_key {{ env.APICAST_HTTPS_CERTIFICATE_KEY | default: "conf/server.key" | filesystem | first }}; + + ssl_certificate_by_lua_block { require('apicast.executor'):ssl_certificate() } {%- endif %} server_name _; diff --git a/gateway/conf/server.crt b/gateway/conf/server.crt new file mode 100644 index 000000000..ea5cd4c44 --- /dev/null +++ b/gateway/conf/server.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBTzCB9gIJAJ/Hbl7Rg8UpMAoGCCqGSM49BAMCMDAxEDAOBgNVBAoMB0FQSWNh +c3QxHDAaBgNVBAsME0RlZmF1bHQgY2VydGlmaWNhdGUwHhcNMTgwNjA1MTAxNjAx +WhcNMjgwNjAyMTAxNjAxWjAwMRAwDgYDVQQKDAdBUEljYXN0MRwwGgYDVQQLDBNE +ZWZhdWx0IGNlcnRpZmljYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVUiV +WRQcAve1ssYN0qaFWP33pYRLSV4SM6G0BB3SLiYnQKan8K0I7DtvOAoT8HOm0UyM ++6vNyedReg5PXHOuPjAKBggqhkjOPQQDAgNIADBFAiEAoSKLhFHcwFGSu1N4NxSq +p0bGI5J8WYfrdvWVZgWsV9MCIBeJzCEsegLdVBf/mn+4m7GNitMNzLj4CxTCnpqq +S1m1 +-----END CERTIFICATE----- diff --git a/gateway/conf/server.key b/gateway/conf/server.key new file mode 100644 index 000000000..1caff8f1a --- /dev/null +++ b/gateway/conf/server.key @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIK9vyuYjAXWiI6QwoBwMs2BPKyY/46Qdd8ZFgTCA2YNRoAoGCCqGSM49 +AwEHoUQDQgAEVUiVWRQcAve1ssYN0qaFWP33pYRLSV4SM6G0BB3SLiYnQKan8K0I +7DtvOAoT8HOm0UyM+6vNyedReg5PXHOuPg== +-----END EC PRIVATE KEY----- diff --git a/gateway/src/apicast/executor.lua b/gateway/src/apicast/executor.lua index be7b11fd0..236b5a4a7 100644 --- a/gateway/src/apicast/executor.lua +++ b/gateway/src/apicast/executor.lua @@ -52,7 +52,7 @@ end -- @tparam string phase Nginx phase -- @treturn linked_list The context. Note: The list returned is 'read-write'. function _M:context(phase) - if phase == 'init' then + if phase == 'init' or phase == 'ssl_certificate' then return build_context(self) end diff --git a/gateway/src/apicast/policy.lua b/gateway/src/apicast/policy.lua index 0e0bfbf4d..5dca42e82 100644 --- a/gateway/src/apicast/policy.lua +++ b/gateway/src/apicast/policy.lua @@ -14,6 +14,8 @@ local PHASES = { 'content', 'balancer', 'header_filter', 'body_filter', 'post_action', 'log', 'metrics', + + 'ssl_certificate', } local setmetatable = setmetatable diff --git a/gateway/src/apicast/policy/find_service/find_service.lua b/gateway/src/apicast/policy/find_service/find_service.lua index a55ebe0e3..b8fedeb4d 100644 --- a/gateway/src/apicast/policy/find_service/find_service.lua +++ b/gateway/src/apicast/policy/find_service/find_service.lua @@ -70,8 +70,11 @@ function _M.new(...) return self end -function _M:rewrite(context) - context.service = self.find_service(context.configuration, context.host) +local function find_service(policy, context) + context.service = policy.find_service(context.configuration, context.host) end +_M.rewrite = find_service +_M.ssl_certificate = find_service + return _M diff --git a/gateway/src/apicast/policy/load_configuration/load_configuration.lua b/gateway/src/apicast/policy/load_configuration/load_configuration.lua index 7b0073328..e68e15e1d 100644 --- a/gateway/src/apicast/policy/load_configuration/load_configuration.lua +++ b/gateway/src/apicast/policy/load_configuration/load_configuration.lua @@ -1,4 +1,5 @@ local _M = require('apicast.policy').new('Load Configuration') +local ssl = require('ngx.ssl') local configuration_loader = require('apicast.configuration_loader').new() local configuration_store = require('apicast.configuration_store') @@ -30,4 +31,8 @@ function _M:rewrite(context) context.configuration = configuration_loader.rewrite(self.configuration, context.host) end +function _M:ssl_certificate(context) + context.host = ssl.server_name() +end + return _M diff --git a/spec/policy_spec.lua b/spec/policy_spec.lua index 1a293b5f5..c1bb337e1 100644 --- a/spec/policy_spec.lua +++ b/spec/policy_spec.lua @@ -6,7 +6,8 @@ describe('policy', function() 'rewrite', 'access', 'content', 'balancer', 'header_filter', 'body_filter', - 'post_action', 'log', 'metrics' + 'post_action', 'log', 'metrics', + 'ssl_certificate', } describe('.new', function() diff --git a/t/fixtures/policies/https/builtin/init.lua b/t/fixtures/policies/https/builtin/init.lua new file mode 100644 index 000000000..2e3fbee0e --- /dev/null +++ b/t/fixtures/policies/https/builtin/init.lua @@ -0,0 +1,23 @@ +local _M = require('apicast.policy').new('HTTPS', '1.0.0') +local ssl = require('ngx.ssl') +local new = _M.new + +function _M.new(configuration) + local policy = new(configuration) + + if configuration then + policy.certificate_chain = assert(ssl.parse_pem_cert(configuration.certificate)) + policy.priv_key = assert(ssl.parse_pem_priv_key(configuration.key)) + end + + return policy +end + +function _M:ssl_certificate() + assert(ssl.clear_certs()) + + assert(ssl.set_cert(self.certificate_chain)) + assert(ssl.set_priv_key(self.priv_key)) +end + +return _M diff --git a/t/listen-https.t b/t/listen-https.t index ee03603d9..4c1bb41da 100644 --- a/t/listen-https.t +++ b/t/listen-https.t @@ -7,7 +7,6 @@ run_tests(); __DATA__ === TEST 1: Listen on HTTPS ---- ssl random_port --- env eval ( 'APICAST_HTTPS_PORT' => "$Test::Nginx::Util::ServerPortForClient", @@ -63,3 +62,66 @@ MHcCAQEEIH22v43xtXcHWJyH3BEB9N30ahrCOLripkoSWW/WujUxoAoGCCqGSM49 AwEHoUQDQgAEjchlS+kmxpBuIvL/J+2EkN3/4EhorMBuAwCKa2Hh486Ldr9ggLCw fKrqwHFuC8x5PDE0kgel7e13i0FVnlavXQ== -----END EC PRIVATE KEY----- + + + +=== TEST 2: Listen on HTTPS with policy serving the certificate +--- env eval +( + 'APICAST_HTTPS_PORT' => "$Test::Nginx::Util::ServerPortForClient", + 'APICAST_POLICY_LOAD_PATH' => 't/fixtures/policies', +) +--- configuration +{ + "services": [{ + "proxy": { + "policy_chain": [ + { "name": "https", "configuration": { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIBRzCB7gIJAPHi8uNGM8wDMAoGCCqGSM49BAMCMCwxFjAUBgNVBAoMDVRlc3Q6\nOkFQSWNhc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA2MDUwOTQ0MjRaFw0y\nODA2MDIwOTQ0MjRaMCwxFjAUBgNVBAoMDVRlc3Q6OkFQSWNhc3QxEjAQBgNVBAMM\nCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI3IZUvpJsaQbiLy\n/yfthJDd/+BIaKzAbgMAimth4ePOi3a/YICwsHyq6sBxbgvMeTwxNJIHpe3td4tB\nVZ5Wr10wCgYIKoZIzj0EAwIDSAAwRQIhAPRkfbxowt0H7p5xZYpwoMKanUXz9eKQ\n0sGkOw+TqqGXAiAMKJRqtjnCF2LIjGygHG6BlgjM4NgIMDHteZPEr4qEmw==\n-----END CERTIFICATE-----", + "key": "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIH22v43xtXcHWJyH3BEB9N30ahrCOLripkoSWW/WujUxoAoGCCqGSM49\nAwEHoUQDQgAEjchlS+kmxpBuIvL/J+2EkN3/4EhorMBuAwCKa2Hh486Ldr9ggLCw\nfKrqwHFuC8x5PDE0kgel7e13i0FVnlavXQ==\n-----END EC PRIVATE KEY-----" } }, + { "name": "apicast.policy.upstream", + "configuration": { "rules": [ { "regex": "/", "url": "http://echo" } ] } } + ] + } + }] +} +--- test env +lua_ssl_trusted_certificate $TEST_NGINX_HTML_DIR/server.crt; + +content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect(ngx.var.server_addr, ngx.var.apicast_port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "localhost", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) +} +--- response_body +connected: 1 +ssl handshake: userdata +--- error_code: 200 +--- no_error_log +[error] +--- user_files +>>> server.crt +-----BEGIN CERTIFICATE----- +MIIBRzCB7gIJAPHi8uNGM8wDMAoGCCqGSM49BAMCMCwxFjAUBgNVBAoMDVRlc3Q6 +OkFQSWNhc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA2MDUwOTQ0MjRaFw0y +ODA2MDIwOTQ0MjRaMCwxFjAUBgNVBAoMDVRlc3Q6OkFQSWNhc3QxEjAQBgNVBAMM +CWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI3IZUvpJsaQbiLy +/yfthJDd/+BIaKzAbgMAimth4ePOi3a/YICwsHyq6sBxbgvMeTwxNJIHpe3td4tB +VZ5Wr10wCgYIKoZIzj0EAwIDSAAwRQIhAPRkfbxowt0H7p5xZYpwoMKanUXz9eKQ +0sGkOw+TqqGXAiAMKJRqtjnCF2LIjGygHG6BlgjM4NgIMDHteZPEr4qEmw== +-----END CERTIFICATE-----