From 352791a0111abd14d06dcc865df28edee921966c Mon Sep 17 00:00:00 2001 From: An Tran Date: Tue, 11 Mar 2025 14:07:58 +1000 Subject: [PATCH] [dev-environments] Introduce MTLS dev environment --- dev-environments/mtls/Makefile | 26 ++++++ dev-environments/mtls/README.md | 61 +++++++++++++ dev-environments/mtls/apicast-config.json | 93 ++++++++++++++++++++ dev-environments/mtls/cert/Makefile | 94 +++++++++++++++++++++ dev-environments/mtls/cert/intermediate.cnf | 82 ++++++++++++++++++ dev-environments/mtls/cert/root-ca.cnf | 74 ++++++++++++++++ dev-environments/mtls/docker-compose.yml | 49 +++++++++++ dev-environments/mtls/docker-entrypoint.sh | 4 + dev-environments/mtls/ocsp.Dockerfile | 9 ++ 9 files changed, 492 insertions(+) create mode 100644 dev-environments/mtls/Makefile create mode 100644 dev-environments/mtls/README.md create mode 100644 dev-environments/mtls/apicast-config.json create mode 100644 dev-environments/mtls/cert/Makefile create mode 100644 dev-environments/mtls/cert/intermediate.cnf create mode 100644 dev-environments/mtls/cert/root-ca.cnf create mode 100644 dev-environments/mtls/docker-compose.yml create mode 100644 dev-environments/mtls/docker-entrypoint.sh create mode 100644 dev-environments/mtls/ocsp.Dockerfile diff --git a/dev-environments/mtls/Makefile b/dev-environments/mtls/Makefile new file mode 100644 index 000000000..b36836472 --- /dev/null +++ b/dev-environments/mtls/Makefile @@ -0,0 +1,26 @@ +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec +.DEFAULT_GOAL := gateway +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +WORKDIR := $(patsubst %/,%,$(dir $(MKFILE_PATH))) +DOCKER ?= $(shell which docker 2> /dev/null || echo "docker") + +gateway: ## run gateway configured to access upstream powered with TLS + $(DOCKER) compose -f docker-compose.yml up --attach gateway + +clean: + $(DOCKER) compose down --volumes --remove-orphans + $(DOCKER) compose -f docker-compose.yml down --volumes --remove-orphans + $(MAKE) clean -C $(WORKDIR)/cert -f $(WORKDIR)/cert/Makefile + +certs: + $(MAKE) clean -C $(WORKDIR)/cert -f $(WORKDIR)/cert/Makefile + $(MAKE) all -C $(WORKDIR)/cert -f $(WORKDIR)/cert/Makefile + +template: export WHITELIST=$(shell cat cert/intermediate.cert.pem | tr '\n' ' ') +template: export CRL=$(shell cat cert/intermediate.crl.pem | tr '\n' ' ') +template: + yq -i '.services[0].proxy.policy_chain[0].configuration.whitelist[0].pem_certificate="$(WHITELIST)"' apicast-config.json + yq -i '.services[0].proxy.policy_chain[0].configuration.revoke_list[0].pem_certificate="$(CRL)"' apicast-config.json + yq -i '.services[1].proxy.policy_chain[0].configuration.whitelist[0].pem_certificate="$(WHITELIST)"' apicast-config.json + diff --git a/dev-environments/mtls/README.md b/dev-environments/mtls/README.md new file mode 100644 index 000000000..6d7b57b67 --- /dev/null +++ b/dev-environments/mtls/README.md @@ -0,0 +1,61 @@ +# Making APIcast listen on HTTPS + +## Create the SSL Certificates + +```sh +make certs +``` + +## Prepare apicast-config.json + +```sh +make template +``` + +## Run the gateway + +Running local `apicast-test` docker image + +```sh +make gateway +``` + +Running custom apicast image + +```sh +make gateway IMAGE_NAME=quay.io/3scale/apicast:latest +``` + +## Testing CRL + +Send request with valid cert + +``` +curl --resolve example.com:8443:127.0.0.1 -H "Host: crl.example.com" --cacert cert/rootCA.cert.pem --cert cert/client-chain.cert.pem --key cert/client.key.pem "https://example.com:8443/?user_key=123" --http1.1 -v +``` + +Request with revoked cert + +```sh +curl --resolve example.com:8443:127.0.0.1 -H "Host: crl.example.com" --cacert cert/rootCA.cert.pem --cert cert/revoked_client-chain.cert.pem --key cert/revoked_client.key.pem "https://example.com:8443/?user_key=123" --http1.1 -v +``` + +## Testing OCSP + +Send request with valid cert + +``` +curl --resolve example.com:8443:127.0.0.1 -H "Host: ocsp.example.com" --cacert cert/rootCA.cert.pem --cert cert/client-chain.cert.pem --key cert/client.key.pem "https://example.com:8443/?user_key=123" --http1.1 -v +``` + +Request with revoked cert + +```sh +curl --resolve example.com:8443:127.0.0.1 -H "Host: ocsp.example.com" --cacert cert/rootCA.cert.pem --cert cert/revoked_client-chain.cert.pem --key cert/revoked_client.key.pem "https://example.com:8443/?user_key=123" --http1.1 -v +``` + +## Clean env + +```sh +make clean +``` diff --git a/dev-environments/mtls/apicast-config.json b/dev-environments/mtls/apicast-config.json new file mode 100644 index 000000000..890f8bc3a --- /dev/null +++ b/dev-environments/mtls/apicast-config.json @@ -0,0 +1,93 @@ +{ + "services": [ + { + "id": "1", + "backend_version": "1", + "proxy": { + "hosts": [ + "crl.example.com" + ], + "api_backend": "http://one.upstream/get", + "backend": { + "endpoint": "http://127.0.0.1:8081", + "host": "backend" + }, + "policy_chain": [ + { + "name": "tls_validation", + "version": "builtin", + "configuration": { + "whitelist": [ + { + "pem_certificate": "" + } + ], + "allow_partial_chain": true, + "revocation_check_type": "crl", + "revoke_list": [ + { + "pem_certificate": "" + } + ] + } + }, + { + "name": "apicast.policy.apicast" + } + ], + "proxy_rules": [ + { + "http_method": "GET", + "pattern": "/", + "metric_system_name": "hits", + "delta": 1, + "parameters": [], + "querystring_parameters": {} + } + ] + } + }, + { + "id": "2", + "backend_version": "1", + "proxy": { + "hosts": [ + "ocsp.example.com" + ], + "api_backend": "http://one.upstream/get", + "backend": { + "endpoint": "http://127.0.0.1:8081", + "host": "backend" + }, + "policy_chain": [ + { + "name": "tls_validation", + "version": "builtin", + "configuration": { + "whitelist": [ + { + "pem_certificate": "" + } + ], + "allow_partial_chain": true, + "revocation_check_type": "ocsp" + } + }, + { + "name": "apicast.policy.apicast" + } + ], + "proxy_rules": [ + { + "http_method": "GET", + "pattern": "/", + "metric_system_name": "hits", + "delta": 1, + "parameters": [], + "querystring_parameters": {} + } + ] + } + } + ] +} diff --git a/dev-environments/mtls/cert/Makefile b/dev-environments/mtls/cert/Makefile new file mode 100644 index 000000000..c515cb3e6 --- /dev/null +++ b/dev-environments/mtls/cert/Makefile @@ -0,0 +1,94 @@ +DOMAIN=example.com + +clean: + - rm *.crt *.key *.pem *.csr index.* crlnumber crlnumber.* serial serial.* *.srl + +all: index ca intermediate ca-chain ocsp server client revoked_client crl client-chain revoked-client-chain + +index: + touch index.txt + echo 1000 > serial + echo 1000 > crlnumber + +ca: + openssl genrsa -out rootCA.key.pem 2048 + openssl req -config root-ca.cnf \ + -key rootCA.key.pem \ + -new -x509 -days 3650 -sha256 -extensions v3_ca \ + -out rootCA.cert.pem \ + -subj "/C=US/CN=ca.$(DOMAIN)" + +intermediate: + openssl genrsa -out intermediate.key.pem 2048 + openssl req -config intermediate.cnf \ + -key intermediate.key.pem \ + -new -sha256 \ + -out intermediate.csr.pem \ + -subj "/CN=intermediate-cert.$(DOMAIN)" + openssl ca -config root-ca.cnf \ + -extensions v3_intermediate_ca -days 2650 -notext -batch \ + -in intermediate.csr.pem \ + -out intermediate.cert.pem + +ca-chain: + cat intermediate.cert.pem rootCA.cert.pem > ca-chain.cert.pem + +crl: + openssl ca -config intermediate.cnf \ + -gencrl -out intermediate.crl.pem + +ocsp: + openssl genrsa -out ocsp.$(DOMAIN).key.pem 2048 + openssl req -config intermediate.cnf -new -sha256 \ + -key ocsp.$(DOMAIN).key.pem \ + -out ocsp.$(DOMAIN).csr.pem \ + -nodes \ + -subj "/CN=ocsp.$(DOMAIN)" + openssl ca -config intermediate.cnf \ + -extensions v3_ocsp -days 2650 -notext -batch \ + -in ocsp.$(DOMAIN).csr.pem \ + -out ocsp.$(DOMAIN).cert.pem + +server: + openssl req -subj '/CN=$(DOMAIN)' -newkey rsa:4096 -nodes \ + -sha256 \ + -days 3650 \ + -keyout $(DOMAIN).key \ + -out $(DOMAIN).csr + chmod +r $(DOMAIN).key + openssl x509 -req -in $(DOMAIN).csr -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial -out $(DOMAIN).crt -days 500 -sha256 + openssl ca -config root-ca.cnf \ + -extensions v3_intermediate_ca -days 2650 -notext -batch \ + -in $(DOMAIN).csr \ + -out $(DOMAIN).pem + +client: + openssl genrsa -out client.key.pem 2048 + openssl req -config intermediate.cnf -new -sha256 \ + -key client.key.pem \ + -out client.csr.pem \ + -nodes \ + -subj "/CN=client-cert.$(DOMAIN)" + echo -e "y\ny\n" | openssl ca -config intermediate.cnf \ + -extensions v3_leaf -days 375 -notext -md sha256 \ + -in client.csr.pem \ + -out client.cert.pem + +revoked_client: + openssl genrsa -out revoked_client.key.pem 2048 + openssl req -config intermediate.cnf -new -sha256 \ + -key revoked_client.key.pem \ + -out revoked_client.csr.pem \ + -nodes \ + -subj "/CN=revoked_client-cert.$(DOMAIN)" + echo -e "y\ny\n" | openssl ca -config intermediate.cnf \ + -extensions v3_leaf -days 375 -notext -md sha256 \ + -in revoked_client.csr.pem \ + -out revoked_client.cert.pem + openssl ca -config intermediate.cnf -revoke revoked_client.cert.pem + +client-chain: + cat client.cert.pem intermediate.cert.pem > client-chain.cert.pem + +revoked-client-chain: + cat revoked_client.cert.pem intermediate.cert.pem > revoked_client-chain.cert.pem diff --git a/dev-environments/mtls/cert/intermediate.cnf b/dev-environments/mtls/cert/intermediate.cnf new file mode 100644 index 000000000..e7a657ecd --- /dev/null +++ b/dev-environments/mtls/cert/intermediate.cnf @@ -0,0 +1,82 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = . +certificate = $dir/intermediate.cert.pem +database = $dir/index.txt +new_certs_dir = $dir +serial = $dir/serial +crlnumber = $dir/crlnumber + +private_key = $dir/intermediate.key.pem + +name_opt = ca_default +cert_opt = ca_default + +default_days = 365 +default_crl_days= 30 +default_md = sha256 +preserve = no +policy = policy_anything + +[ policy_match ] +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 4096 +default_md = sha256 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +string_mask = nombstr + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = CA +localityName = Locality Name (eg, city) +localityName_default = Testland +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Testers +organizationalUnitName = Organizational Unit Name (eg, section) +commonName = Common Name (eg, your name or your server\'s hostname) +commonName_max = 64 +emailAddress = Email Address +emailAddress_max = 64 +emailAddress_default = sec@test.testing + +[ v3_ca ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = critical,CA:true + +[ v3_intermediate_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign +extendedKeyUsage = OCSPSigning + +[ v3_ocsp ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning + +[ v3_leaf ] +extendedKeyUsage = serverAuth, clientAuth +authorityInfoAccess = OCSP;URI:http://ocsp:2560 diff --git a/dev-environments/mtls/cert/root-ca.cnf b/dev-environments/mtls/cert/root-ca.cnf new file mode 100644 index 000000000..1a31bf9f4 --- /dev/null +++ b/dev-environments/mtls/cert/root-ca.cnf @@ -0,0 +1,74 @@ +[ ca ] +default_ca = CA_default # The default ca section + +[ CA_default ] +dir = . # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +database = $dir/index.txt # database index file. + # several certs with same subject. +new_certs_dir = $dir # default place for new certs. +certificate = $dir/rootCA.cert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +private_key = $dir/rootCA.key.pem # The private key + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = sha256 # use SHA-256 by default +preserve = no # keep passed DN ordering +policy = policy_match + +[ policy_match ] +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 4096 +default_md = sha256 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +string_mask = nombstr + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = CA +localityName = Locality Name (eg, city) +localityName_default = Testland +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Testers +organizationalUnitName = Organizational Unit Name (eg, section) +commonName = Common Name (eg, your name or your server\'s hostname) +commonName_max = 64 +emailAddress = Email Address +emailAddress_max = 64 +emailAddress_default = sec@test.testing + +[ v3_ca ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = critical,CA:true + +[ v3_intermediate_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, digitalSignature, cRLSign, keyCertSign diff --git a/dev-environments/mtls/docker-compose.yml b/dev-environments/mtls/docker-compose.yml new file mode 100644 index 000000000..594bf1619 --- /dev/null +++ b/dev-environments/mtls/docker-compose.yml @@ -0,0 +1,49 @@ +--- +services: + gateway: + image: ${IMAGE_NAME:-apicast-test} + build: + context: ../.. + dockerfile: Dockerfile + depends_on: + - one.upstream + - two.upstream + environment: + APICAST_HTTPS_PORT: 8443 + APICAST_HTTPS_CERTIFICATE: /var/run/secrets/apicast/example.com.pem + APICAST_HTTPS_CERTIFICATE_KEY: /var/run/secrets/apicast/example.com.key + THREESCALE_CONFIG_FILE: /tmp/config.json + THREESCALE_DEPLOYMENT_ENV: staging + APICAST_CONFIGURATION_LOADER: lazy + APICAST_WORKERS: 1 + APICAST_LOG_LEVEL: debug + APICAST_CONFIGURATION_CACHE: "0" + expose: + - "8443" + - "8090" + ports: + - "8443:8443" + - "8090:8090" + volumes: + - ./apicast-config.json:/tmp/config.json + - ./cert:/var/run/secrets/apicast + one.upstream: + image: quay.io/openshift-logging/alpine-socat:1.8.0.0 + container_name: one.upstream + command: "-d -v -d TCP-LISTEN:80,reuseaddr,fork TCP:two.upstream:8080" + expose: + - "80" + restart: unless-stopped + two.upstream: + image: quay.io/kuadrant/authorino-examples:talker-api + expose: + - "8080" + ocsp: + build: + dockerfile: ./ocsp.Dockerfile + ports: + - "2560:2560" + expose: + - "2560" + volumes: + - ./cert:/cert diff --git a/dev-environments/mtls/docker-entrypoint.sh b/dev-environments/mtls/docker-entrypoint.sh new file mode 100644 index 000000000..7a6f7ef2b --- /dev/null +++ b/dev-environments/mtls/docker-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#This entrypoint is responsible for leaving the OSCP running to accept requests +openssl ocsp -url http://0.0.0.0:2560 -text -index /cert/index.txt -CA /cert/ca-chain.cert.pem -rkey /cert/ocsp.example.com.key.pem -rsigner /cert/ocsp.example.com.cert.pem diff --git a/dev-environments/mtls/ocsp.Dockerfile b/dev-environments/mtls/ocsp.Dockerfile new file mode 100644 index 000000000..390b94cc6 --- /dev/null +++ b/dev-environments/mtls/ocsp.Dockerfile @@ -0,0 +1,9 @@ +FROM mirror.gcr.io/library/alpine:3 + +RUN apk --no-cache add openssl + +COPY cert /cert + +COPY docker-entrypoint.sh docker-entrypoint.sh +EXPOSE 2560 +ENTRYPOINT [ "/bin/sh", "docker-entrypoint.sh" ]