Skip to content

Commit 88a11a2

Browse files
Merge pull request #64 from UKHomeOffice/verify-upstream
Add support for verifying the upstream server's cert
2 parents df02af2 + 12c552e commit 88a11a2

File tree

6 files changed

+93
-5
lines changed

6 files changed

+93
-5
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ rules to be specified without downloading or mounting in a rule file.
3838
for easy tracking in down stream logs e.g. `nginxId=50c91049-667f-4286-c2f0-86b04b27d3f0`.
3939
If set to `HEADER` it will add `nginxId` to the headers, not append to the get params.
4040
* `CLIENT_CERT_REQUIRED` - if set to `TRUE`, will deny access at this location, see [Client Certs](#client-certs).
41+
* `VERIFY_SERVER_CERT` - if set to `TRUE`, will verify the upstream server's TLS certificate is valid and signed by the CA, see [Verifying Upstream Server](#verifying-upstream-server).
4142
* `USE_UPSTREAM_CLIENT_CERT` - if set to `TRUE`, will use the set of upstream client certs when connecting upstream, see [Upstream Client Certs](#upstream-client-certs).
4243
* `ERROR_REDIRECT_CODES` - Can override when Nginx will redirect requests to its own error page. Defaults to
4344
"`500 501 502 503 504`". To support a new code, say `505`, an error page must be provided at
@@ -102,6 +103,12 @@ N.B. see HTTP(S)_LISTEN_PORT above
102103
signed one is provided if they have not been mounted.
103104
* `/etc/keys/client-ca` If a client CA is mounted here, it will be loaded and configured.
104105
See `CLIENT_CERT_REQUIRED` above in [Environment Variables](#environment-variables).
106+
* `/etc/keys/upstream-server-ca` A CA public cert must be mounted here when verifying the upstream server's certificate is required.
107+
See `VERIFY_SERVER_CERT` above in [Environment Variables](#environment-variables).
108+
* `/etc/keys/upstream-client-crt` A public client cert must be mounted here when when the upstream server requires client cert authentication.
109+
See `USE_UPSTREAM_CLIENT_CERT` above in [Environment Variables](#environment-variables).
110+
* `/etc/keys/upstream-client-key` A private client key must be mounted here when when the upstream server requires client cert authentication.
111+
See `USE_UPSTREAM_CLIENT_CERT` above in [Environment Variables](#environment-variables).
105112
* `/usr/local/openresty/naxsi/*.conf` - [Naxsi](https://github.com/nbs-system/naxsi) rules location in default
106113
nginx.conf.
107114
* `/usr/local/openresty/nginx/html/$CODE.shtml` - HTML (with SSI support) displayed when a the status code $CODE
@@ -243,7 +250,22 @@ docker run -e 'PROXY_SERVICE_HOST=https://stackexchange.com' \
243250
-v "/path/to/client-public.crt:/etc/keys/upstream-client-crt" \
244251
-v "/path/to/client-private.key:/etc/keys/upstream-client-key" \
245252
-p 8443:443 \
246-
quay.io/ukhomeofficedigital/nginx-proxy:v1.8.0
253+
quay.io/ukhomeofficedigital/nginx-proxy:v2.1.0
254+
```
255+
256+
#### Verifying Upstream Server
257+
258+
If the environment variable `VERIFY_SERVER_CERT` is set to `TRUE` then
259+
the upstream server's certificate will be validated against the CA
260+
public cert at `/etc/keys/upstream-server-ca`.
261+
262+
```shell
263+
docker run -e 'PROXY_SERVICE_HOST=https://stackexchange.com' \
264+
-e 'PROXY_SERVICE_PORT=443' \
265+
-e 'VERIFY_SERVER_CERT=TRUE' \
266+
-v "/path/to/ca.crt:/etc/keys/upstream-server-ca" \
267+
-p 8443:443 \
268+
quay.io/ukhomeofficedigital/nginx-proxy:v2.1.0
247269
```
248270

249271
#### Arbitrary Config

ci-build.sh

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ TAG=ngx
66
PORT=8443
77
START_INSTANCE="docker run --privileged=true "
88
DOCKER_HOST_NAME=127.0.0.1
9+
MUTUAL_TLS="mutual-tls"
10+
STANDARD_TLS="standard-tls"
911

1012
function tear_down_container() {
1113
container=$1
1214
if docker ps -a | grep "${container}" &>/dev/null ; then
1315
if docker ps | grep "${container}" &>/dev/null ; then
14-
docker kill "${container}" &>/dev/null
16+
docker kill "${container}" &>/dev/null || true
1517
fi
1618
docker rm "${container}" &>/dev/null || true
1719
fi
@@ -25,6 +27,8 @@ function clean_up() {
2527
rm -f /tmp/file.txt
2628
tear_down_container mockserver
2729
tear_down_container slowmockserver
30+
tear_down_container ${MUTUAL_TLS}
31+
tear_down_container ${STANDARD_TLS}
2832
tear_down_container ${TAG}
2933
}
3034

@@ -253,7 +257,6 @@ wget -O /dev/null --quiet --no-check-certificate https://${DOCKER_HOST_NAME}:${P
253257
--private-key=./client_certs/client.key
254258

255259
echo "Test upstream client certs..."
256-
MUTUAL_TLS="mutual-tls"
257260
${STD_CMD} -d \
258261
-e "PROXY_SERVICE_HOST=http://www.w3.org" \
259262
-e "PROXY_SERVICE_PORT=80" \
@@ -273,6 +276,51 @@ echo "Test it's up and working..."
273276
wget -O /dev/null --quiet --no-check-certificate https://${DOCKER_HOST_NAME}:${PORT}/
274277
tear_down_container "${MUTUAL_TLS}"
275278

279+
echo "Test failure to verify upstream server cert..."
280+
${STD_CMD} -d \
281+
-e "PROXY_SERVICE_HOST=http://www.w3.org" \
282+
-e "PROXY_SERVICE_PORT=80" \
283+
--name="${STANDARD_TLS}" ${TAG}
284+
docker run --link "${STANDARD_TLS}:${STANDARD_TLS}" --rm martin/wait
285+
start_test "Start with failing upstream server verification" "${STD_CMD} \
286+
-e \"PROXY_SERVICE_HOST=https://${STANDARD_TLS}\" \
287+
-e \"PROXY_SERVICE_PORT=443\" \
288+
-e \"DNSMASK=TRUE\" \
289+
-e \"VERIFY_SERVER_CERT=TRUE\" \
290+
-v \"${PWD}/client_certs/ca.crt:/etc/keys/upstream-server-ca\" \
291+
--link \"${STANDARD_TLS}:${STANDARD_TLS}\" "
292+
echo "Test it blocks the request, returning a 502..."
293+
if curl -ki https://${DOCKER_HOST_NAME}:${PORT}/ | grep "502 Bad Gateway" ; then
294+
echo "Passed failure to verify upstream server cert"
295+
else
296+
echo "Failed failure to verify upstream server cert"
297+
exit 1
298+
fi
299+
tear_down_container "${STANDARD_TLS}"
300+
301+
echo "Test successfully verifying upstream server cert..."
302+
cd ./client_certs/
303+
./create_server_csr_and_key.sh
304+
./sign_server_key_with_ca.sh
305+
cd ..
306+
${STD_CMD} -d \
307+
-e "PROXY_SERVICE_HOST=http://www.w3.org" \
308+
-e "PROXY_SERVICE_PORT=80" \
309+
-v "${PWD}/client_certs/server.crt:/etc/keys/crt" \
310+
-v "${PWD}/client_certs/server.key:/etc/keys/key" \
311+
--name="${STANDARD_TLS}" ${TAG}
312+
docker run --link "${STANDARD_TLS}:${STANDARD_TLS}" --rm martin/wait
313+
start_test "Start with succeeding upstream server verification" "${STD_CMD} \
314+
-e \"PROXY_SERVICE_HOST=https://${STANDARD_TLS}\" \
315+
-e \"PROXY_SERVICE_PORT=443\" \
316+
-e \"DNSMASK=TRUE\" \
317+
-e \"VERIFY_SERVER_CERT=TRUE\" \
318+
-v \"${PWD}/client_certs/ca.crt:/etc/keys/upstream-server-ca\" \
319+
--link \"${STANDARD_TLS}:${STANDARD_TLS}\" "
320+
echo "Test it allows the request through..."
321+
wget -O /dev/null --quiet --no-check-certificate https://${DOCKER_HOST_NAME}:${PORT}/
322+
tear_down_container "${STANDARD_TLS}"
323+
276324
start_test "Start with Custom error pages redirect off" "${STD_CMD} \
277325
-e \"PROXY_SERVICE_HOST=http://mockserver\" \
278326
-e \"PROXY_SERVICE_PORT=8080\" \

client_certs/.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
ca.*
2-
client.*
1+
*.crt
2+
*.csr
3+
*.key
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
. ./settings.cfg
3+
openssl genrsa -out server.key 4096
4+
openssl req -new -key server.key -subj "${DN}/CN=standard-tls" -out server.csr
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env bash
2+
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

enable_location.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ NAXSI_USE_DEFAULT_RULES=$(get_id_var ${LOCATION_ID} NAXSI_USE_DEFAULT_RULES)
2020
EXTRA_NAXSI_RULES=$(get_id_var ${LOCATION_ID} EXTRA_NAXSI_RULES)
2121
CLIENT_CERT_REQUIRED=$(get_id_var ${LOCATION_ID} CLIENT_CERT_REQUIRED)
2222
USE_UPSTREAM_CLIENT_CERT=$(get_id_var ${LOCATION_ID} USE_UPSTREAM_CLIENT_CERT)
23+
VERIFY_SERVER_CERT=$(get_id_var ${LOCATION_ID} VERIFY_SERVER_CERT)
2324
PORT_IN_HOST_HEADER=$(get_id_var ${LOCATION_ID} PORT_IN_HOST_HEADER)
2425
ENABLE_UUID_PARAM=$(get_id_var ${LOCATION_ID} ENABLE_UUID_PARAM)
2526
ERROR_REDIRECT_CODES=$(get_id_var ${LOCATION_ID} ERROR_REDIRECT_CODES)
@@ -125,6 +126,15 @@ if [ "${USE_UPSTREAM_CLIENT_CERT}" == "TRUE" ]; then
125126
else
126127
SSL_CERTIFICATE=""
127128
fi
129+
if [ "${VERIFY_SERVER_CERT}" == "TRUE" ]; then
130+
if [ ! -f /etc/keys/upstream-server-ca ]; then
131+
exit_error_msg "Missing server CA cert at location:/etc/keys/upstream-server-ca"
132+
fi
133+
msg "Will require '${LOCATION}'s certificate to be verified."
134+
SSL_VERIFY="proxy_ssl_trusted_certificate /etc/keys/upstream-server-ca; proxy_ssl_verify on;"
135+
else
136+
SSL_VERIFY=""
137+
fi
128138

129139
if [ "${PORT_IN_HOST_HEADER}" == "FALSE" ]; then
130140
msg "Setting host only proxy header"
@@ -212,6 +222,7 @@ location ${LOCATION} {
212222
${WEB_SOCKETS}
213223
$(cat /location_template.conf)
214224
${SSL_CERTIFICATE}
225+
${SSL_VERIFY}
215226
proxy_set_header Host ${PROXY_HOST_SETTING};
216227
proxy_set_header X-Username "$ssl_client_s_dn_cn";
217228
proxy_set_header X-Real-IP \$${REMOTE_IP_VAR};

0 commit comments

Comments
 (0)