diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d500e03f..ed1e1a5cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Default credentials policy [PR #741](https://github.com/3scale/apicast/pull/741), [THREESCALE-586](https://issues.jboss.org/browse/THREESCALE-586) - Configurable caching for the token introspection policy [PR #656](https://github.com/3scale/apicast/pull/656) - `APICAST_ACCESS_LOG_FILE` env to make the access log location configurable [THREESCALE-743](https://github.com/3scale/apicast/pull/743) +- ENV variables to make APIcast listen on HTTPS port [PR #622](https://github.com/3scale/apicast/pull/622) +- New `ssl_certificate` phase allows policies to provide certificate to terminate HTTPS connection [PR #622](https://github.com/3scale/apicast/pull/622). ### Changed diff --git a/doc/parameters.md b/doc/parameters.md index a3d7f84fa..95dc3b43b 100644 --- a/doc/parameters.md +++ b/doc/parameters.md @@ -285,3 +285,21 @@ You can choose to mount a different configuration than the provided by default b This environment variable controls the HTTP header used for forwarding opentracing information, this HTTP header will be forwarded to upstream servers. + +### `APICAST_HTTPS_PORT` + +**Default:** no value + +Controls on which port APIcast should start listening for HTTPS connections. If this clashes with HTTP port it will be used only for HTTPS. + +### `APICAST_HTTPS_CERTIFICATE` + +**Default:** no value + +Path to a file with X.509 certificate in the PEM format for HTTPS. + +### `APICAST_HTTPS_CERTIFICATE_KEY` + +**Default:** no value + +Path to a file with the X.509 certificate secret key in the PEM format. diff --git a/doc/policies.md b/doc/policies.md index 26ca2b521..36f706066 100644 --- a/doc/policies.md +++ b/doc/policies.md @@ -15,7 +15,7 @@ APIcast. ## Policies A policy tells APIcast what it should do in each of the nginx phases: `init`, -`init_worker`, `rewrite`, `access`,`content`, `balancer`, `header_filter`, `body_filter`, +`init_worker`, `ssl_certificate`, `rewrite`, `access`,`content`, `balancer`, `header_filter`, `body_filter`, `post_action`, `log`, and `metrics`. Policies can share data between them. They do that through what we call the @@ -35,7 +35,7 @@ The way policy chains work is as follows: suppose that we have a policy A that describes what to do in the `rewrite` and `header_filter` phases and a policy B that describes what to run in `access` and `header_filter`. Assume also that when describing the chain, we indicate that policy A should be run before -policy B. When APIcast receives a request, it will check the policy chain +policy B. When APIcast receives an HTTP request, it will check the policy chain described to see what it should run on each phase: - rewrite: execute the function policy A provides for this phase. - access: execute the function policy B provides for this phase. @@ -53,6 +53,9 @@ Notice that we did not indicate what APIcast does in the `init` and the request. `init` is executed when APIcast boots, and `init_worker` when each of each of its workers start. +Another phase that is not executed for every request is `ssl_certificate` because +it is called only when APIcast terminates the HTTPS connection. + The order in which policies actions are applied depend on two factors: - Position of the policy within the policy chain. - The phase in which the policies act. diff --git a/examples/add-ssl/cert/server.crt b/examples/add-ssl/cert/server.crt index cd8ad353d..2840acb22 100644 --- a/examples/add-ssl/cert/server.crt +++ b/examples/add-ssl/cert/server.crt @@ -1,19 +1,18 @@ -----BEGIN CERTIFICATE----- -MIIDBjCCAe4CCQDF1mZcWyPgwTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB -VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 -cyBQdHkgTHRkMB4XDTE2MTExNzA4NDcxOVoXDTE3MTExNzA4NDcxOVowRTELMAkG -A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0 -IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AKvwk7TsVwW/0KSpZMU4LU7PE5rJGGyy0EJY/Y22uAuqzAYDyyuShW3IfUqS0hr5 -nOLZWSMjntcHbbLsYy6J6ZcmxfwlpS8FGOq2zhQvHZCmxMTRSTK+5EfgLe0al556 -k2RoEcsGgK4jTH09+Fig3dMBL/1ly+s/z7JBzFkCX03y6BA26CjrHl2aVJjyJWRs -Z1/9qndjCzZzVCsdJ7NJi/km3ugAJFRPggxucBeyCToHkQQacXYL4X8S8fS6oP7w -XNMttBdENl1iGT1uD4mxjy1i8L3v8OhK6qxE+dxGLcakECbuGDGBGi/Cyn4sPKfs -fghFfboSPm60OfeXcyS1n+sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAWcctiXNI -Je80bzdvDNgRegdnZHI5xg+TrrVMQ9DFeCPOCpcesqkypdV2HZKRhtKsosu6O7MR -g7o2C2RnMPuL+8J94FW4V+nDZoUcksOzNERZXMUHctSGkzGkoWBtdCx44dfCcLmQ -gAyQ6+I4pT1Ks6Ri3IJdaj+JFK9C9thibzXXsviW8S6CuqArAIp4jssIO39moFGQ -2dry9O0suQ23YaMAF6Uy0V5Ds9+FyZPNFWn/JPkdQKGuLjeOnOr4LH2JZAyYJxyk -k8M1Dl2TrS/QaqOrpHQ3a/6J6BVvYiLih2tjO6spqWc7rlum8L+qpd9T2m0kukzg -cPK994E90uBW8Q== +MIIC6jCCAdICCQDyqR3mvsRZyDANBgkqhkiG9w0BAQsFADA3MRAwDgYDVQQKDAdS +ZWQgSGF0MQ8wDQYDVQQLDAYzc2NhbGUxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x +ODAyMjMwNzQ3MDBaFw0yODAyMjEwNzQ3MDBaMDcxEDAOBgNVBAoMB1JlZCBIYXQx +DzANBgNVBAsMBjNzY2FsZTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvD63witlrnDVdHPTV6wrajNh9F3WXsiwFKuZ +OM/2uGrzdeKd2dsMNUfYD/X9sLN8OUe+IbLdVvwGy9cx6nuKR3XTWhzjm2yHUCO9 +eT1svPUWGkZNMkyftcG4RG6ArYzf8GUciZ8tm8sMtp6RJ3UhJOETsoo+HXaPbMr2 +YICDvPGV5QOJK3mdvHFb5Os19WHkB8zcm/iA8b+0dlTF8cOCuHaTx0ncGDf6Q1zx +A22hQyBFQUcW2MWQBIGkXe2sJMK5P9QS+0kVS8miSuud2qxohKWkuK9WtVsGaJj3 +XTdvvZxpu/H+FyMjvoQhd3iaFYRfHrVR91L7G2jY+CzYS4Q7KwIDAQABMA0GCSqG +SIb3DQEBCwUAA4IBAQBkyiygZOJKkxSuMQ9pQ5CldPIloCd+LV0MLmzMztH5NF0/ +Zr4hiUB4vMPIt5a2Ay5Og3C04wVoFBbNuMs/70nwBkxqQ0+7c8qmlgLk9OTvjkOC +MEN4bd4MK2WNRw6340bm13nOaBcqsQW//0jhJaWFKEIBddXNwawP1Ua8/B1hXJKz +vSmCWM7XZ2A/LoB69eSwyse1D75BpYOZ5WdBRBKnZO45cc0eRcbGYvK0KOeXXjNw +LCqdMZav74dfB4LM/b6CnHyrZC6FFtJbsBLLJ7UCcRNeR6UF0tTdUel95LdscA5j +sMqFt3bnE9U+IIy3BoxorviPdlbInLXBzynqqyl3 -----END CERTIFICATE----- diff --git a/examples/add-ssl/cert/server.key b/examples/add-ssl/cert/server.key index 6a323e778..b3c64c993 100644 --- a/examples/add-ssl/cert/server.key +++ b/examples/add-ssl/cert/server.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/gateway/conf/nginx.conf.liquid b/gateway/conf/nginx.conf.liquid index de2ba8f8d..6985db2f6 100644 --- a/gateway/conf/nginx.conf.liquid +++ b/gateway/conf/nginx.conf.liquid @@ -122,7 +122,32 @@ http { access_log {{ access_log_file | default: "/dev/stdout" }} time; - listen {{ port.apicast | default: 8080 }}; + {%- assign http_port = port.apicast | default: 8080 %} + {%- assign https_port = env.APICAST_HTTPS_PORT %} + + {% if http_port != https_port -%} + listen {{ http_port }}; + {% endif %} + + {% if https_port -%} + listen {{ https_port }} ssl; + + {%- assign https_certificate = env.APICAST_HTTPS_CERTIFICATE -%} + ssl_certificate {% if https_certificate -%} + {{ https_certificate }} + {%- else -%} + {{ "conf/server.crt" | filesystem | first }} + {%- endif %}; + + {%- assign https_certificate_key = env.APICAST_HTTPS_CERTIFICATE_KEY -%} + ssl_certificate_key {% if https_certificate_key -%} + {{ https_certificate_key }} + {%- else -%} + {{ "conf/server.key" | filesystem | first }} + {%- endif %}; + + 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/cpanfile b/gateway/cpanfile index cb9ced0dc..6d492c184 100644 --- a/gateway/cpanfile +++ b/gateway/cpanfile @@ -1,4 +1,4 @@ -requires 'Test::APIcast', '0.12'; +requires 'Test::APIcast', '0.14'; requires 'Crypt::JWT'; requires 'Test::Deep'; requires 'File::Slurp'; diff --git a/gateway/cpanfile.snapshot b/gateway/cpanfile.snapshot index a196e317e..93dbd58be 100644 --- a/gateway/cpanfile.snapshot +++ b/gateway/cpanfile.snapshot @@ -383,10 +383,10 @@ DISTRIBUTIONS Spiffy::mixin undef requirements: ExtUtils::MakeMaker 6.30 - Test-APIcast-0.12 - pathname: M/MC/MCICHRA/Test-APIcast-0.12.tar.gz + Test-APIcast-0.14 + pathname: M/MC/MCICHRA/Test-APIcast-0.14.tar.gz provides: - Test::APIcast 0.12 + Test::APIcast 0.14 Test::APIcast::Blackbox undef requirements: File::Slurp 0 diff --git a/gateway/src/apicast/cli/template.lua b/gateway/src/apicast/cli/template.lua index a010f3d42..387db3699 100644 --- a/gateway/src/apicast/cli/template.lua +++ b/gateway/src/apicast/cli/template.lua @@ -29,6 +29,7 @@ function _M:new(config, dir, strict) for name,value in pairs(resty_env.list()) do insert(env, { name = name, value = value }) + env[name] = value end local context = setmetatable({ 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/echo/echo.lua b/gateway/src/apicast/policy/echo/echo.lua index 1d7fef0d1..478461ae0 100644 --- a/gateway/src/apicast/policy/echo/echo.lua +++ b/gateway/src/apicast/policy/echo/echo.lua @@ -4,6 +4,7 @@ -- the whole processing of the request. local _M = require('apicast.policy').new('Echo Policy') +local cjson = require('cjson') local tonumber = tonumber local new = _M.new @@ -20,7 +21,13 @@ function _M.new(configuration) end function _M.content() - ngx.say(ngx.var.request) + local accept = ngx.var.http_accept + + if accept == 'application/json' then + ngx.say(cjson.encode({ request = ngx.var.request })) + else + ngx.say(ngx.var.request) + end end function _M:rewrite() diff --git a/gateway/src/apicast/policy/find_service/find_service.lua b/gateway/src/apicast/policy/find_service/find_service.lua index a55ebe0e3..8cfbe5928 100644 --- a/gateway/src/apicast/policy/find_service/find_service.lua +++ b/gateway/src/apicast/policy/find_service/find_service.lua @@ -1,5 +1,5 @@ -local policy = require('apicast.policy') -local _M = policy.new('Find Service Policy') +local Policy = require('apicast.policy') +local _M = Policy.new('Find Service Policy') local configuration_store = require 'apicast.configuration_store' local mapping_rules_matcher = require 'apicast.mapping_rules_matcher' local new = _M.new @@ -70,8 +70,14 @@ 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 = context.service or policy.find_service(context.configuration, context.host) end +_M.rewrite = find_service + +-- ssl_certiticate is the first phase executed when request arrives on HTTPS +-- therefore it needs to find a service to build a policy chain +_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..3afda5f9f 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,16 @@ function _M:rewrite(context) context.configuration = configuration_loader.rewrite(self.configuration, context.host) end +function _M.ssl_certificate(_, context) + if not context.host then + local server_name, err = ssl.server_name() + + if server_name then + context.host = server_name + elseif err then + ngx.log(ngx.DEBUG, 'could not get TLS SNI server name: ', err) + end + end +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 new file mode 100644 index 000000000..4c1bb41da --- /dev/null +++ b/t/listen-https.t @@ -0,0 +1,127 @@ +use lib 't'; +use Test::APIcast::Blackbox 'no_plan'; + +$ENV{TEST_NGINX_HTML_DIR} ||= "$Test::Nginx::Util::ServRoot/html"; + +run_tests(); + +__DATA__ +=== TEST 1: Listen on HTTPS +--- env eval +( + 'APICAST_HTTPS_PORT' => "$Test::Nginx::Util::ServerPortForClient", + 'APICAST_HTTPS_CERTIFICATE' => "$Test::Nginx::Util::ServRoot/html/server.crt", + 'APICAST_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server.key", +) +--- configuration fixture=echo.json +--- 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----- +>>> server.key +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +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-----