Skip to content
Merged
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 @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add `enable_extended_context` to allow JWT Claim Check access full request context [PR #1535](https://github.com/3scale/APIcast/pull/1535) [THREESCALE-9510](https://issues.redhat.com/browse/THREESCALE-9510)
- JWT signature verification, support for ES256/ES512 [PR #1533](https://github.com/3scale/APIcast/pull/1533) [THREESCALE-11474](https://issues.redhat.com/browse/THREESCALE-11474)
- JWT Parser policy [PR #1536](https://github.com/3scale/APIcast/pull/1536) [THREESCALE-10708](https://issues.redhat.com/browse/THREESCALE-10708)
- TLS Validation Policy - add support to validate client certificate with CRL and OCSP [PR #1503](https://github.com/3scale/APIcast/pull/1503) [THREESCALE-11404](https://issues.redhat.com/browse/THREESCALE-11404)

## [3.15.0] 2024-04-04

Expand Down
1 change: 1 addition & 0 deletions gateway/http.d/shdict.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ lua_shared_dict limiter 1m;
lua_shared_dict cached_auths 20m;
lua_shared_dict batched_reports {{env.APICAST_POLICY_BATCHER_SHARED_MEMORY_SIZE | default: "20m"}};
lua_shared_dict batched_reports_locks 1m;
lua_shared_dict ocsp_cache 10m;
73 changes: 69 additions & 4 deletions gateway/src/apicast/policy/tls_validation/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# TLS Validation policy

This policy can validate TLS Client Certificate against a whitelist.
This policy can validate TLS Client Certificate against a whitelist and Certificate Revocation List (CRL)

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.
* 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. However, you can change this behaviour with `allow_partial_chain`

## Configuration

Expand All @@ -18,13 +19,77 @@ NOTE: This policy is not compatible with `APICAST_PATH_ROUTING` or `APICAST_PATH

## Example

* Allow certificate verification with only an intermediate certificate.
```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
]
}
}
```

* Use full certificate chain to verify client certificate
```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"allow_partial_chain": false
}
}
```

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 X509 CRL ----- XXXXXX -----END X509 CRL-----"}
]
}
}
```

Checking certificate status with Online Certificate Status Protocol (OCSP). The responder url is
extracted from the certificate.

NOTE: When validating a client certificate with OCSP, APIcast requires the client to send the certificate chain
(i.e. if the certificate is signed with an intermediate certificate, the client needs to send both the client certificate + the intermediate certificate)

```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"revocation_check_type": "ocsp",
}
}
```

Overwrite OCSP responder URL

```
{
"name": "apicast.policy.tls_validation",
"configuration": {
"whitelist": [
{ "pem_certificate": ""-----BEGIN CERTIFICATE----- XXXXXX -----END CERTIFICATE-----"}
],
"revocation_check_type": "ocsp",
"ocsp_responder_url": "http://<ocsp-server>:<port>"
}
}
```
79 changes: 79 additions & 0 deletions gateway/src/apicast/policy/tls_validation/apicast-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,85 @@
"$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": [
"ocsp"
],
"title": "Enables OCSP validation of the client certificate."
},
{
"enum": [
"crl"
],
"title": "Use certificates revocation list (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": {
"enum": [
"none"
]
}
}
},
{
"properties": {
"revocation_check_type": {
"enum": [
"crl"
]
},
"revoke_list": {
"title": "Certificate RevokeList",
"description": "Individual certificates and CA certificates to be revoked.",
"$ref": "#/definitions/store"
}
}
},
{
"properties": {
"revocation_check_type": {
"enum": [
"ocsp"
]
},
"ocsp_responder_url": {
"title": "OCSP Responder URL ",
"description": "Overrides the URL of the OCSP responder specified in the “Authority Information Access” certificate extension for validation of client certificates. ",
"type": "string"
},
"cache_ttl": {
"title": "Max TTL for cached OCSP response",
"type": "integer",
"minimum": 1,
"maximum": 3600
}
}
}
]
}
}
}
Expand Down
116 changes: 116 additions & 0 deletions gateway/src/apicast/policy/tls_validation/ocsp_validation.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
local user_agent = require "apicast.user_agent"
local http_ng = require "resty.http_ng"
local resty_env = require "resty.env"
local tls = require "resty.tls"
local ngx_ssl = require "ngx.ssl"
local ocsp = require "ngx.ocsp"

local _M = {}
local ocsp_shm = ngx.shared.ocsp_cache

local function do_ocsp_request(ocsp_url, ocsp_request)
-- TODO: set default timeout
local http_client = http_ng.new{
options = {
headers = {
['User-Agent'] = user_agent()

Check warning on line 16 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L13-L16

Added lines #L13 - L16 were not covered by tests
},
ssl = { verify = resty_env.enabled('OPENSSL_VERIFY') }

Check warning on line 18 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L18

Added line #L18 was not covered by tests
}
}
local res, err = http_client.post{

Check warning on line 21 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L21

Added line #L21 was not covered by tests
ocsp_url,
ocsp_request,
headers= {
["Content-Type"] = "application/ocsp-request"
}}
if err then
return nil, err

Check warning on line 28 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L24-L28

Added lines #L24 - L28 were not covered by tests
end

ngx.log(ngx.INFO, "fetching OCSP response from ", ocsp_url)

Check warning on line 31 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L31

Added line #L31 was not covered by tests

if not res then
return nil, "failed to send request to OCSP responder: " .. tostring(err)

Check warning on line 34 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L33-L34

Added lines #L33 - L34 were not covered by tests
end

if res.status ~= 200 then
return nil, "unexpected OCSP responder status code: " .. res.status

Check warning on line 38 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L37-L38

Added lines #L37 - L38 were not covered by tests
end

return res.body

Check warning on line 41 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L41

Added line #L41 was not covered by tests
end

function _M.check_revocation_status(ocsp_responder_url, digest, ttl)
-- Nginx supports leaf mode, that is only verify the client ceritificate, however
-- until we have a way to detect which CA certificate is being used to verify the
-- client certificate we need to get the full certificate chain here to construct
-- the OCSP request.
local cert_chain, err = tls.get_full_client_certificate_chain()
if not cert_chain then
return nil, err or "no client certificate"

Check warning on line 51 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L49-L51

Added lines #L49 - L51 were not covered by tests
end

local der_cert
der_cert, err = ngx_ssl.cert_pem_to_der(cert_chain)
if not der_cert then
return nil, "failed to convert certificate chain from PEM to DER " .. err

Check warning on line 57 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L55-L57

Added lines #L55 - L57 were not covered by tests
end

local ocsp_resp
ocsp_resp = ocsp_shm:get(digest)

Check warning on line 61 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L61

Added line #L61 was not covered by tests

if ocsp_resp == nil then
ngx.log(ngx.INFO, "no ocsp resp cache found, fetch from ocsp responder")

Check warning on line 64 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L63-L64

Added lines #L63 - L64 were not covered by tests


-- TODO: check response cache
local ocsp_url
if ocsp_responder_url and ocsp_responder_url ~= "" then
ocsp_url = ocsp_responder_url

Check warning on line 70 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L69-L70

Added lines #L69 - L70 were not covered by tests
else
ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert)
if not ocsp_url then
return nil, err or ("could not extract OCSP responder URL, the client " ..
"certificate may be missing the required extensions")

Check warning on line 75 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L72-L75

Added lines #L72 - L75 were not covered by tests
end
end

if not ocsp_url or ocsp_url == "" then
return nil, " invalid OCSP responder URL"

Check warning on line 80 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L79-L80

Added lines #L79 - L80 were not covered by tests
end

local ocsp_req
ocsp_req, err = ocsp.create_ocsp_request(der_cert)
if not ocsp_req then
return nil, "failed to create OCSP request: " .. err

Check warning on line 86 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L84-L86

Added lines #L84 - L86 were not covered by tests
end

ocsp_resp, err = do_ocsp_request(ocsp_url, ocsp_req)
if not ocsp_resp or #ocsp_resp == 0 then
return nil, "unexpected response from OCSP responder: empty body"

Check warning on line 91 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L89-L91

Added lines #L89 - L91 were not covered by tests
end

-- Use ttl, normally this should be (nextUpdate - thisUpdate), but current version
-- of openresty API does not expose those attributes. Support for this was added
-- in openrest-core v0.1.31, we either need to backport or upgrade the openresty
-- version.
local ok
ok, err = ocsp_shm:set(digest, ocsp_resp, ttl)
if not ok then
ngx.log(ngx.ERR, "could not save ocsp response to cache: ", err)

Check warning on line 101 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L99-L101

Added lines #L99 - L101 were not covered by tests
end
else
ngx.log(ngx.INFO, "using ocsp from cache")

Check warning on line 104 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L104

Added line #L104 was not covered by tests
end

local ok
ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert)
if not ok then
return false, "failed to validate OCSP response: " .. err

Check warning on line 110 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L108-L110

Added lines #L108 - L110 were not covered by tests
end

return true

Check warning on line 113 in gateway/src/apicast/policy/tls_validation/ocsp_validation.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/apicast/policy/tls_validation/ocsp_validation.lua#L113

Added line #L113 was not covered by tests
end

return _M
Loading