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

WIP - [THREESCALE-11404] Adding support for CRL and OCSP #1503

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added the `APICAST_PROXY_BUFFER_SIZE` variable to allow configuration of the buffer size for handling response from the proxied servers. [PR #1473](https://github.com/3scale/APIcast/pull/1473), [THREESCALE-8410](https://issues.redhat.com/browse/THREESCALE-8410)

- Added the `APICAST_HTTPS_VERIFY_CLIENT` variable to allow configuration of the `ssl_verify_client` directive. [PR #1491](https://github.com/3scale/APIcast/pull/1491) [THREESCALE-10156](https://issues.redhat.com/browse/THREESCALE-10156)
- Replace internal OPENSSL module with lua-resty-openssl [PR #1502](https://github.com/3scale/APIcast/pull/1502) [THREESCALE-11412](https://issues.redhat.com/browse/THREESCALE-11412)

## [3.15.0] 2024-04-04

Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/man
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/hamish/lua-resty-iputils-0.3.0-1.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/golgote/net-url-0.9-1.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/membphis/lua-resty-ipmatcher-0.6.1-0.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/fffonion/lua-resty-openssl-1.5.1-1.src.rock

RUN yum -y remove libyaml-devel m4 openssl-devel git gcc luarocks && \
rm -rf /var/cache/yum && yum clean all -y && \
Expand Down Expand Up @@ -93,7 +94,7 @@ WORKDIR /opt/app-root/app
USER 1001

ENV LUA_CPATH "./?.so;/usr/lib64/lua/5.1/?.so;/usr/lib64/lua/5.1/loadall.so;/usr/local/lib64/lua/5.1/?.so"
ENV LUA_PATH "/usr/lib64/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/*/?.lua;"
ENV LUA_PATH "/usr/lib64/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/*/?.lua;;"

WORKDIR /opt/app-root
ENTRYPOINT ["container-entrypoint"]
Expand Down
35 changes: 18 additions & 17 deletions gateway/Roverfile.lock
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
argparse 0.7.1-1||production
busted 2.2.0-1||testing
date 2.2-2||production
busted 2.2.0-1|02f31a9c103a44e166617cfdb6ba1b8994a9c912|testing
date 2.2-2|8d74567cf979c1eab2c6b6ca2e3b978fa40569a2|production
dkjson 2.8-1||testing
fifo 0.2-0||development
inspect 3.1.3-0||production
jsonschema 0.8-0|c1d72d86bb3dc5b33da57d47febc47657d29ea74|testing
ldoc 1.5.0-1||development
ldoc 1.5.0-1|09f82c959c50d8c3d5a968c379b1c75de66b002d|development
liquid 0.2.0-2||production
lua-resty-env 0.4.0-1||production
lua-resty-execvp 0.1.1-1||production
lua-resty-http 0.17.1-0||production
lua-resty-ipmatcher 0.6.1-0||production
lua-resty-iputils 0.3.0-2||production
lua-resty-jit-uuid 0.0.7-2||production
lua-resty-jwt 0.2.0-0||production
lua-resty-http 0.17.1-0|4ab4269cf442ba52507aa2c718f606054452fcad|production
lua-resty-ipmatcher 0.6.1-0|62d4c44d67227e8f3fe02331c2f8b90fe0d7ccd1|production
lua-resty-iputils 0.3.0-2|6110b41eaa52efd25e56f89e34412ab95f700d57|production
lua-resty-jit-uuid 0.0.7-2|64ae38de75c9d58f330d89e140ac872771c19223|production
lua-resty-jwt 0.2.0-0|2a62ff95eae91df6bd8655080a4b9b04c61bec6b|production
lua-resty-openssl 1.5.1-1|a900c5f5897448c181dd58073e51cdeeb3fd0029|production
lua-resty-repl 0.0.6-0|3878f41b7e8f97b1c96919db19dbee9496569dda|development
lua-resty-url 0.3.5-1||production
lua-term 0.8-1||testing
lua_cliargs 3.0-2||testing
luacov 0.15.0-1||testing
luafilesystem 1.8.0-1||production,development,testing
luassert 1.9.0-1||testing
luasystem 0.4.1-1||testing
luacov 0.15.0-1|19b52ca0298c8942df82dd441d7a4a588db4c413|testing
luafilesystem 1.8.0-1|7c6e1b013caec0602ca4796df3b1d7253a2dd258|production,development,testing
luassert 1.9.0-1|8d8dc8a54cc468048a128a867f6449a6c3fdd11a|testing
luasystem 0.4.1-1|c832d2b857d4174d17247de837426d4cfc95ec2f|testing
lyaml 6.2.8-1||production
markdown 0.33-1||development
markdown 0.33-1|8c09109924b218aaecbfd4d4b1de538269c4d765|development
mediator_lua 1.1.2-0||testing
net-url 1.1-1||testing
nginx-lua-prometheus 0.20181120-3||production
penlight 1.13.1-1||production,development,testing
net-url 1.1-1|32acd84d06e16ddffc975adafce9cea26f3b2dd1|testing
nginx-lua-prometheus 0.20181120-3|379c0a4d4d6f3c5b0eb93691fc7e14fff498e1ca|production
penlight 1.13.1-1|3b42fd05d8e60998b0fc5830a397b2354a81170b|production,development,testing
router 2.1-0||production
say 1.4.1-3||testing
say 1.4.1-3|45a3057e68c52b34ab59ef167efeb2340e356661|testing
1 change: 1 addition & 0 deletions gateway/apicast-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies = {
'nginx-lua-prometheus == 0.20181120',
'lua-resty-jit-uuid',
'lua-resty-ipmatcher',
'lua-resty-openssl'
}
build = {
type = "make",
Expand Down
2 changes: 1 addition & 1 deletion gateway/src/apicast/policy/fapi/fapi.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _M:access(context)
if self.validate_oauth2_certificate_bound_access_token then
if not context.jwt then return error(context.service or {}) end

local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert)
local cert = X509.new(ngx.var.ssl_client_raw_cert)

if not check_certificate(cert, context.jwt.cnf) then
ngx.log(ngx.WARN, 'fapi oauth_mtls failed for service ', context.service and context.service.id)
Expand Down
2 changes: 1 addition & 1 deletion gateway/src/apicast/policy/oauth_mtls/oauth_mtls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ end
function _M.access(_, context)
if not context.jwt then return error(context.service or {}) end

local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert)
local cert = X509.new(ngx.var.ssl_client_raw_cert)

if not check_certificate(cert, context.jwt.cnf) then
return error(context.service or {})
Expand Down
20 changes: 19 additions & 1 deletion gateway/src/apicast/policy/tls_validation/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# TLS Validation policy

This policy can validate TLS Client Certificate against a whitelist.
This policy can validate TLS Client Certificate against a whitelist and crl.

Whitelist expects PEM formatted CA or Client certificates.
Revocation List expects PEM formatted 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.

Expand All @@ -28,3 +29,20 @@ NOTE: This policy is not compatible with `APICAST_PATH_ROUTING` or `APICAST_PATH
}
}
```

With Certificate Revocation List (CRL)

```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"revocation_check_type": "crl",
"revoke_list": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
]
}
}
```
61 changes: 61 additions & 0 deletions gateway/src/apicast/policy/tls_validation/apicast-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,74 @@
"items": {
"$ref": "#/definitions/certificate"
}
},
"revoke": {
"$id": "#/definitions/revoke",
"type": "array",
"items": {
"$ref": "#/definitions/certificate"
}
}
},
"properties": {
"whitelist": {
"$ref": "#/definitions/store",
"title": "Certificate Whitelist",
"description": "Individual certificates and CA certificates to be whitelisted."
},
"allow_partial_chain": {
"description": "Allow certificate verification with only an intermediate certificate",
"type": "boolean",
"default": true
},
"revocation_check_type": {
"title": "Certificate Revocation Check type",
"type": "string",
"oneOf": [
{
"enum": [
"crl"
],
"title": "Use revoked certificates (CRL) in the PEM format to verify client certificates."
},
{
"enum": [
"none"
],
"title": "Do not check for certificate recovation status"
}
],
"default": "none"
}
},
"dependencies": {
"revocation_check_type": {
"oneOf": [
{
"properties": {
"revocation_check_type": {
"describe": "Use the Client credentials and the Token Introspection Endpoint from the OpenID Connect Issuer setting.",
"enum": [
"none"
]
}
}
},
{
"properties": {
"revocation_check_type": {
"enum": [
"crl"
]
},
"revoke_list": {
"title": "Certificate Whitelist",
"description": "Individual certificates and CA certificates to be revoked.",
"$ref": "#/definitions/store"
}
}
}
]
}
}
}
Expand Down
57 changes: 50 additions & 7 deletions gateway/src/apicast/policy/tls_validation/tls_validation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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 X509_CRL = require('resty.openssl.x509.crl')
local ngx_ssl = require "ngx.ssl"

local ipairs = ipairs
Expand All @@ -13,10 +14,10 @@ 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
local cert, err = X509.new(certificate.pem_certificate) -- TODO: handle errors

if cert then
store:add_cert(cert)
store:add(cert)

if debug then
ngx.log(ngx.DEBUG, 'adding certificate to the tls validation ', tostring(cert:subject_name()), ' SHA1: ', cert:hexdigest('SHA1'))
Expand All @@ -33,6 +34,29 @@ local function init_trusted_store(store, certificates)
return store
end

local function init_crl_list(store, crl_certificates)
local ok, err
local crl
for _, certificate in ipairs(crl_certificates) do
-- add crl to store, but skip setting the flag
crl, err = X509_CRL.new(certificate.pem_certificate)
if crl then
ok, err = store:add(crl)

if debug then
ngx.log(ngx.DEBUG, 'adding crl certificate to the tls validation ', tostring(crl:subject_name()), ' SHA1: ', crl:hexdigest('SHA1'))
end
else
ngx.log(ngx.WARN, 'failed to add crl 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.
Expand All @@ -42,6 +66,11 @@ function _M.new(config)

self.x509_store = init_trusted_store(store, config and config.whitelist or {})
self.error_status = config and config.error_status or 400
self.allow_partial_chain = config and config.allow_partial_chain or true
self.revocation_type = config and config.revocation_check_type or "none"
if self.revocation_type == "crl" then
init_crl_list(store, config and config.revoke_list or {})
end

return self
end
Expand All @@ -60,26 +89,40 @@ function _M:ssl_certificate()
end

function _M:access()
local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert)
if not cert then
local client_cert = ngx.var.ssl_client_raw_cert
if not client_cert then
ngx.status = self.error_status
ngx.say("No required TLS certificate was sent")
return ngx.exit(ngx.status)
end

local cert, err = X509.new(client_cert)
if not cert then
ngx.status = self.error_status
ngx.log(ngx.WARN, "Invalid TLS certificate, err: ", err)
ngx.say("Invalid TLS certificate")
return ngx.exit(ngx.status)
end

local store = self.x509_store

if self.allow_partial_chain then
store:set_flags(store.verify_flags.X509_V_FLAG_PARTIAL_CHAIN)
end

-- err is printed inside validate_cert method
-- so no need capture the err here
local ok, _ = store:validate_cert(cert)
local chain, ok
chain, err = store:verify(cert, nil, true)

if not ok then
if not chain then
ngx.status = self.error_status
ngx.log(ngx.WARN, "TLS certificate validation failed, err: ", err)
ngx.say("TLS certificate validation failed")
return ngx.exit(ngx.status)
end

return ok, nil
return true, nil
end

return _M
6 changes: 3 additions & 3 deletions gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ local function read_ca_certificates(ca_certificates)
local valid = false
local store = X509_STORE.new()
for _,certificate in pairs(ca_certificates) do
local cert, err = X509.parse_pem_cert(certificate)
local cert, err = X509.new(certificate)
if cert then
valid = true
store:add_cert(cert)
store:add(cert)
else
ngx.log(ngx.INFO, "cannot load certificate, err: ", err)
end
end

if valid then
return store.store
return store.ctx
end
end

Expand Down
Loading