-
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.
Merge pull request #966 from 3scale/tls-validation-poc
TLS Client Certificate validation policy
- Loading branch information
Showing
27 changed files
with
1,285 additions
and
37 deletions.
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
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,7 @@ | ||
# TLS Validation policy | ||
|
||
This policy can validate TLS Client Certificate against a whitelist. | ||
|
||
Whitelist expects PEM formatted CA or Client certificates. | ||
It is not necessary to have the full certificate chain, just partial matches are allowed. | ||
For example you can add to the whitelist just leaf client certificates without the whole bundle with a CA certificate. |
39 changes: 39 additions & 0 deletions
39
gateway/src/apicast/policy/tls_validation/apicast-policy.json
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,39 @@ | ||
{ | ||
"$schema": "http://apicast.io/policy-v1/schema#manifest#", | ||
"name": "TLS validation", | ||
"summary": "Validate client TLS certificates", | ||
"description": [ | ||
"Validate client certificates against individual certificates and CA certificates." | ||
], | ||
"version": "builtin", | ||
"configuration": { | ||
"type": "object", | ||
"definitions": { | ||
"certificate": { | ||
"$id": "#/definitions/certificate", | ||
"type": "object", | ||
"properties": { | ||
"pem_certificate": { | ||
"type": "string", | ||
"title": "PEM formatted certificate", | ||
"description": "Certificate including the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----" | ||
} | ||
} | ||
}, | ||
"store": { | ||
"$id": "#/definitions/store", | ||
"type": "array", | ||
"items": { | ||
"$ref": "#/definitions/certificate" | ||
} | ||
} | ||
}, | ||
"properties": { | ||
"whitelist": { | ||
"$ref": "#/definitions/store", | ||
"title": "Certificate Whitelist", | ||
"description": "Individual certificates and CA certificates to be whitelisted." | ||
} | ||
} | ||
} | ||
} |
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('tls_validation') |
63 changes: 63 additions & 0 deletions
63
gateway/src/apicast/policy/tls_validation/tls_validation.lua
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,63 @@ | ||
-- This is a tls_validation description. | ||
|
||
local policy = require('apicast.policy') | ||
local _M = policy.new('tls_validation') | ||
local X509_STORE = require('resty.openssl.x509.store') | ||
local X509 = require('resty.openssl.x509') | ||
|
||
local ipairs = ipairs | ||
local tostring = tostring | ||
|
||
local debug = ngx.config.debug | ||
|
||
local function init_trusted_store(store, certificates) | ||
for _,certificate in ipairs(certificates) do | ||
local cert, err = X509.parse_pem_cert(certificate.pem_certificate) -- TODO: handle errors | ||
|
||
if cert then | ||
store:add_cert(cert) | ||
|
||
if debug then | ||
ngx.log(ngx.DEBUG, 'adding certificate to the tls validation ', tostring(cert:subject_name()), ' SHA1: ', cert:hexdigest('SHA1')) | ||
end | ||
else | ||
ngx.log(ngx.WARN, 'error whitelisting certificate, err: ', err) | ||
|
||
if debug then | ||
ngx.log(ngx.DEBUG, 'certificate: ', certificate.pem_certificate) | ||
end | ||
end | ||
end | ||
|
||
return store | ||
end | ||
|
||
local new = _M.new | ||
--- Initialize a tls_validation | ||
-- @tparam[opt] table config Policy configuration. | ||
function _M.new(config) | ||
local self = new(config) | ||
local store = X509_STORE.new() | ||
|
||
self.x509_store = init_trusted_store(store, config and config.whitelist or {}) | ||
self.error_status = config and config.error_status or 400 | ||
|
||
return self | ||
end | ||
|
||
function _M:access() | ||
local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert) | ||
local store = self.x509_store | ||
|
||
local ok, err = store:validate_cert(cert) | ||
|
||
if not ok then | ||
ngx.status = self.error_status | ||
ngx.say(err) | ||
return ngx.exit(ngx.status) | ||
end | ||
|
||
return ok, err | ||
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
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,87 @@ | ||
local ffi = require('ffi') | ||
|
||
ffi.cdef([[ | ||
typedef long time_t; | ||
// https://github.com/openssl/openssl/blob/4ace4ccda2934d2628c3d63d41e79abe041621a7/include/openssl/ossl_typ.h | ||
typedef struct x509_store_st X509_STORE; | ||
typedef struct x509_st X509; | ||
typedef struct X509_crl_st X509_CRL; | ||
typedef struct X509_name_st X509_NAME; | ||
typedef struct bio_st BIO; | ||
typedef struct bio_method_st BIO_METHOD; | ||
typedef struct X509_VERIFY_PARAM_st X509_VERIFY_PARAM; | ||
typedef struct stack_st OPENSSL_STACK; | ||
typedef struct evp_md_st { | ||
int type; | ||
int pkey_type; | ||
int md_size; | ||
} EVP_MD; | ||
unsigned long ERR_get_error(void); | ||
const char *ERR_reason_error_string(unsigned long e); | ||
void ERR_clear_error(void); | ||
]]) | ||
|
||
local C = ffi.C | ||
local _M = { } | ||
|
||
local error = error | ||
|
||
local function openssl_error() | ||
local code, reason | ||
|
||
while true do | ||
--[[ | ||
https://www.openssl.org/docs/man1.1.0/crypto/ERR_get_error.html | ||
ERR_get_error() returns the earliest error code | ||
from the thread's error queue and removes the entry. | ||
This function can be called repeatedly | ||
until there are no more error codes to return. | ||
]]-- | ||
code = C.ERR_get_error() | ||
|
||
if code == 0 then | ||
break | ||
else | ||
reason = C.ERR_reason_error_string(code) | ||
end | ||
end | ||
|
||
C.ERR_clear_error() | ||
|
||
if reason then | ||
return ffi.string(reason) | ||
end | ||
end | ||
|
||
local function ffi_value(ret, expected) | ||
if ret == nil or ret == -1 or (expected and ret ~= expected) then | ||
return nil, openssl_error() or 'expected value, got nil' | ||
end | ||
|
||
return ret | ||
end | ||
|
||
local function ffi_assert(ret, expected) | ||
local value, err = ffi_value(ret, expected) | ||
|
||
if not value then | ||
error(err, 2) | ||
end | ||
|
||
return value | ||
end | ||
|
||
local function tocdata(obj) | ||
return obj and obj.cdata or obj | ||
end | ||
|
||
_M.ffi_assert = ffi_assert | ||
_M.ffi_value = ffi_value | ||
_M.openssl_error = openssl_error | ||
_M.tocdata = tocdata | ||
|
||
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,59 @@ | ||
local base = require('resty.openssl.base') | ||
local ffi = require('ffi') | ||
|
||
ffi.cdef([[ | ||
// https://www.openssl.org/docs/manmaster/man3/BIO_write.html | ||
BIO_METHOD *BIO_s_mem(void); | ||
BIO * BIO_new(BIO_METHOD *type); | ||
void BIO_vfree(BIO *a); | ||
int BIO_read(BIO *b, void *data, int len); | ||
int BIO_write(BIO *b, const void *data, int dlen); | ||
size_t BIO_ctrl_pending(BIO *b); | ||
]]) | ||
local C = ffi.C | ||
local ffi_assert = base.ffi_assert | ||
local str_len = string.len | ||
local assert = assert | ||
|
||
local _M = { | ||
|
||
} | ||
|
||
local mt = { | ||
__index = _M, | ||
__new = function(ct, bio_method) | ||
local bio = ffi_assert(C.BIO_new(bio_method)) | ||
|
||
return ffi.new(ct, bio) | ||
end, | ||
__gc = function(self) | ||
C.BIO_vfree(self.cdata) | ||
end, | ||
} | ||
|
||
-- no changes to the metamethods possible from this point | ||
local BIO = ffi.metatype('struct { BIO *cdata; }', mt) | ||
|
||
local bio_mem = C.BIO_s_mem() | ||
|
||
function _M:read() | ||
local bio = self.cdata | ||
-- BIO_ctrl_pending() return the amount of pending data. | ||
local len = C.BIO_ctrl_pending(bio) | ||
local buf = ffi.new("char[?]", len) | ||
ffi_assert(C.BIO_read(bio, buf, len) >= 0) | ||
return ffi.string(buf, len) | ||
end | ||
|
||
function _M:write(str) | ||
local len = str_len(assert(str, 'expected string')) | ||
|
||
return ffi_assert(C.BIO_write(self.cdata, str, len)) | ||
end | ||
|
||
function _M.new() | ||
return BIO(bio_mem) | ||
end | ||
|
||
return _M |
Oops, something went wrong.