-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
By default, at the moment if a user wants to use MTLS with the upstream API, the only way is using `APICAST_PROXY_SSL_CERTIFICATE`, and this certificate will be used in all services. With this policy, that includes an Nginx patch, and a new Nginx-module, different client certificates can be used by service, so one APICast instance can host more than MTLS connections to different upstreams. Signed-off-by: Eloy Coto <[email protected]>
- Loading branch information
Showing
4 changed files
with
262 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
return require("mtls") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
-- This policy enables MTLS with the upstream API endpoint | ||
|
||
local ssl = require('ngx.ssl') | ||
local open = io.open | ||
local ffi = require "ffi" | ||
local C = ffi.C | ||
local base = require "resty.core.base" | ||
local get_request = base.get_request | ||
|
||
|
||
ffi.cdef([[ | ||
int ngx_http_apicast_ffi_set_proxy_cert_key( | ||
ngx_http_request_t *r, void *cdata_chain, void *cdata_key); | ||
]]) | ||
|
||
|
||
local policy = require('apicast.policy') | ||
local _M = policy.new('mtls', "builtin") | ||
|
||
local path_type = "path" | ||
|
||
local new = _M.new | ||
|
||
local function set_certs(cert, key) | ||
local r = get_request() | ||
if not r then | ||
ngx.log(ngx.ERR, "No valid request") | ||
return | ||
end | ||
-- @TODO fix here the integer result | ||
local val = C.ngx_http_apicast_ffi_set_proxy_cert_key(r, cert, key) | ||
if val ~= ngx.OK then | ||
ngx.log(ngx.ERR, "Certificate cannot be set correctly") | ||
end | ||
end | ||
|
||
local function read_file(path) | ||
ngx.log(ngx.DEBUG, "reading path:", path) | ||
|
||
local file = open(path, "rb") | ||
if not file then | ||
ngx.log(ngx.ERR, "Cannot read path: ", path) | ||
return nil | ||
end | ||
|
||
local content = file:read("*a") | ||
file:close() | ||
return content | ||
end | ||
|
||
local function get_cert(value, value_type) | ||
local data = value | ||
if value_type == path_type then | ||
data = read_file(value) | ||
end | ||
return data | ||
end | ||
|
||
local function read_certificate(value, value_type) | ||
local data = get_cert(value, value_type) | ||
if data == nil then | ||
ngx.log(ngx.ERR, "Certificate value is invalid") | ||
return | ||
end | ||
|
||
return ssl.parse_pem_cert(data) | ||
end | ||
|
||
local function read_certificate_key(value, value_type) | ||
|
||
local data = get_cert(value, value_type) | ||
|
||
if data == nil then | ||
ngx.log(ngx.ERR, "Certificate value is invalid") | ||
return | ||
end | ||
|
||
if data == nil then | ||
ngx.log(ngx.ERR, "Certificate key value is invalid") | ||
return | ||
end | ||
|
||
return ssl.parse_pem_priv_key(data) | ||
|
||
end | ||
|
||
function _M.new(config) | ||
local self = new(config) | ||
if config == nil then | ||
config = {} | ||
end | ||
|
||
self.cert = read_certificate( | ||
config.certificate, | ||
config.certificate_type or path_type) | ||
self.cert_key = read_certificate_key( | ||
config.certificate_key, | ||
config.certificate_key_type or path_type) | ||
return self | ||
end | ||
|
||
|
||
function _M:balancer(context) | ||
set_certs(self.cert, self.cert_key) | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
use lib 't'; | ||
use Test::APIcast::Blackbox 'no_plan'; | ||
|
||
repeat_each(1); | ||
run_tests(); | ||
|
||
|
||
__DATA__ | ||
=== TEST 1: MTLS policy with correct certificate | ||
--- init eval | ||
$Test::Nginx::Util::ENDPOINT_SSL_PORT = Test::APIcast::get_random_port(); | ||
--- user_files fixture=mutual_ssl.pl eval | ||
--- backend | ||
location /transactions/authrep.xml { | ||
content_by_lua_block { | ||
local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=value" | ||
require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) | ||
} | ||
} | ||
--- configuration eval | ||
<<EOF | ||
{ | ||
"services": [ | ||
{ | ||
"id": 42, | ||
"backend_version": 1, | ||
"backend_authentication_type": "service_token", | ||
"backend_authentication_value": "token-value", | ||
"proxy": { | ||
"api_backend": "https://test:$Test::Nginx::Util::ENDPOINT_SSL_PORT/", | ||
"proxy_rules": [ | ||
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, | ||
{ | ||
"name": "apicast.policy.mtls", | ||
"configuration": { | ||
"certificate": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.crt", | ||
"certificate_type": "path", | ||
"certificate_key": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.key", | ||
"certificate_key_type": "path" | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
EOF | ||
--- upstream eval | ||
<<EOF | ||
listen $Test::Nginx::Util::ENDPOINT_SSL_PORT ssl; | ||
ssl_certificate $ENV{TEST_NGINX_SERVER_ROOT}/html/server.crt; | ||
ssl_certificate_key $ENV{TEST_NGINX_SERVER_ROOT}/html/server.key; | ||
ssl_client_certificate $ENV{TEST_NGINX_SERVER_ROOT}/html/client.crt; | ||
ssl_verify_client on; | ||
location / { | ||
echo 'ssl_client_s_dn: \$ssl_client_s_dn'; | ||
echo 'ssl_client_i_dn: \$ssl_client_i_dn'; | ||
} | ||
EOF | ||
--- request | ||
GET /?user_key=value | ||
--- response_body | ||
ssl_client_s_dn: CN=localhost,OU=APIcast,O=3scale | ||
ssl_client_i_dn: CN=localhost,OU=APIcast,O=3scale | ||
--- error_code: 200 | ||
--- no_error_log | ||
[error] | ||
=== TEST 2: MTLS policy takes precedence over env variables | ||
In this test we set the env variables to an invalid keys, to make sure that the | ||
correct ones are used. | ||
--- init eval | ||
$Test::Nginx::Util::ENDPOINT_SSL_PORT = Test::APIcast::get_random_port(); | ||
--- user_files fixture=mutual_ssl.pl eval | ||
--- env random_port eval | ||
( | ||
'APICAST_PROXY_HTTPS_CERTIFICATE' => "$Test::Nginx::Util::ServRoot/html/server.crt", | ||
'APICAST_PROXY_HTTPS_CERTIFICATE_KEY' => "$Test::Nginx::Util::ServRoot/html/server.key", | ||
) | ||
--- backend | ||
location /transactions/authrep.xml { | ||
content_by_lua_block { | ||
local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=value" | ||
require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0)) | ||
} | ||
} | ||
--- configuration eval | ||
<<EOF | ||
{ | ||
"services": [ | ||
{ | ||
"id": 42, | ||
"backend_version": 1, | ||
"backend_authentication_type": "service_token", | ||
"backend_authentication_value": "token-value", | ||
"proxy": { | ||
"api_backend": "https://test:$Test::Nginx::Util::ENDPOINT_SSL_PORT/", | ||
"proxy_rules": [ | ||
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, | ||
{ | ||
"name": "apicast.policy.mtls", | ||
"configuration": { | ||
"certificate": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.crt", | ||
"certificate_type": "path", | ||
"certificate_key": "$ENV{TEST_NGINX_SERVER_ROOT}/html/client.key", | ||
"certificate_key_type": "path" | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
EOF | ||
--- upstream eval | ||
<<EOF | ||
listen $Test::Nginx::Util::ENDPOINT_SSL_PORT ssl; | ||
ssl_certificate $ENV{TEST_NGINX_SERVER_ROOT}/html/server.crt; | ||
ssl_certificate_key $ENV{TEST_NGINX_SERVER_ROOT}/html/server.key; | ||
ssl_client_certificate $ENV{TEST_NGINX_SERVER_ROOT}/html/client.crt; | ||
ssl_verify_client on; | ||
location / { | ||
echo 'ssl_client_s_dn: \$ssl_client_s_dn'; | ||
echo 'ssl_client_i_dn: \$ssl_client_i_dn'; | ||
} | ||
EOF | ||
--- request | ||
GET /?user_key=value | ||
--- response_body | ||
ssl_client_s_dn: CN=localhost,OU=APIcast,O=3scale | ||
ssl_client_i_dn: CN=localhost,OU=APIcast,O=3scale | ||
--- error_code: 200 | ||
--- no_error_log | ||
[error] | ||
# @TODO | ||
# 2 Services, one with MTLS another one with normal TLS that is not affected. (To validate CTX in http request |