Skip to content

Commit

Permalink
APIcast TLS policy
Browse files Browse the repository at this point in the history
Extracted from 3scale/APIcast#966
  • Loading branch information
mikz committed Jan 22, 2019
1 parent 8c87f83 commit 9e93adf
Show file tree
Hide file tree
Showing 21 changed files with 668 additions and 139 deletions.
2 changes: 1 addition & 1 deletion .s2i/bin/assemble
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh

echo "---> Copying policy source..."
cp -Rf /tmp/src/. ./
cp -Rfv /tmp/src/. ./
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
[![CircleCI](https://circleci.com/gh/3scale/apicast-example-policy.svg?style=svg)](https://circleci.com/gh/3scale/apicast-example-policy)

# APIcast Example Policy

This policy is an example how to make custom policies for APIcast.
# APIcast TLS Validation Policy

This policy validates TLS Client Certificates.

## OpenShift

To install this on OpenShift you can use provided template:

```shell
oc new-app -f openshift.yml --param AMP_RELEASE=2.2.0
oc new-app -f openshift.yml --param AMP_RELEASE=2.4.0
```

The template creates new ImageStream for images containing this policy.
Then it creates two BuildConfigs: one for building an image to apicast-policy ImageStream
and second one for creating new APIcast image copying just necessary code from that previous image.

Run them both to create a new image:

```shell
oc start-build apicast-tls-policy --follow
oc start-build apicast-custom-policies --follow
```

# License

Expand Down
9 changes: 0 additions & 9 deletions Roverfile

This file was deleted.

16 changes: 0 additions & 16 deletions Roverfile.lock

This file was deleted.

1 change: 1 addition & 0 deletions apicast.d/ssl_verify_client.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssl_verify_client optional_no_ca;
1 change: 0 additions & 1 deletion cpanfile

This file was deleted.

19 changes: 11 additions & 8 deletions openshift.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v1
kind: Template
metadata:
name: "apicast-example-policy"
name: "apicast-tls-policy"
message: "APIcast Example Policy"
objects:

Expand All @@ -19,15 +19,15 @@ objects:
annotations:
labels:
app: apicast
name: apicast-example-policy
name: apicast-tls-policy
spec:
output:
to:
kind: ImageStreamTag
name: apicast-policy:example
name: apicast-policy:tls
source:
git:
uri: https://github.com/3scale/apicast-example-policy.git
uri: https://github.com/mikz/apicast-tls-validation-policy.git
ref: 'master'
type: Git
strategy:
Expand Down Expand Up @@ -62,14 +62,17 @@ objects:
images:
- from:
kind: ImageStreamTag
name: 'apicast-policy:example'
name: 'apicast-policy:tls'
paths:
# copy policy source code into the new image
- destinationDir: policies
sourcePath: /opt/app-root/policies/example
sourcePath: /opt/app-root/policies/tls_validation
# copy also installed dependencies to the policy folder, so they are vendored
# - destinationDir: policies/example/0.1/resty/
# sourcePath: /opt/app-root/src/lua_modules/share/lua/5.1/resty/iputils.lua
- destinationDir: policies/tls_validation/0.1/
sourcePath: /opt/app-root/resty/
# copy nginx configuration customization
- destinationDir: apicast.d
sourcePath: /opt/app-root/apicast.d
type: Dockerfile
dockerfile: |
FROM scratch
Expand Down
11 changes: 0 additions & 11 deletions policies/example/0.1/apicast-policy.json

This file was deleted.

51 changes: 0 additions & 51 deletions policies/example/0.1/example.lua

This file was deleted.

1 change: 0 additions & 1 deletion policies/example/0.1/init.lua

This file was deleted.

39 changes: 39 additions & 0 deletions policies/tls_validation/0.1/apicast-policy.json
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": "0.1",
"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."
}
}
}
}
1 change: 1 addition & 0 deletions policies/tls_validation/0.1/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('tls_validation')
61 changes: 61 additions & 0 deletions policies/tls_validation/0.1/tls_validation.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
-- 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.var.cached_key = nil
ngx.status = self.error_status
ngx.say(err)
return ngx.exit(ngx.status)
end
end

return _M
85 changes: 85 additions & 0 deletions resty/openssl/base.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
local ffi = require('ffi')

ffi.cdef([[
// 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
Loading

0 comments on commit 9e93adf

Please sign in to comment.