From 76546e182349b4a5022e900ebe3ac8b121c936c5 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Wed, 10 May 2017 17:31:43 +0200 Subject: [PATCH 1/5] Add client revocation test --- test/config.sh | 3 +- test/tests/revocation/run.sh | 87 ++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100755 test/tests/revocation/run.sh diff --git a/test/config.sh b/test/config.sh index 4beb51f..de1e6ba 100644 --- a/test/config.sh +++ b/test/config.sh @@ -7,11 +7,12 @@ testAlias+=( imageTests+=( [openvpn]=' - paranoid + paranoid conf_options basic dual-proto otp iptables + revocation ' ) diff --git a/test/tests/revocation/run.sh b/test/tests/revocation/run.sh new file mode 100755 index 0000000..290b69b --- /dev/null +++ b/test/tests/revocation/run.sh @@ -0,0 +1,87 @@ +#!/bin/bash +set -e + +[ -n "${DEBUG+x}" ] && set -x + +OVPN_DATA="basic-data" +CLIENT1="travis-client1" +CLIENT2="travis-client2" +IMG="kylemanna/openvpn" +NAME="ovpn-test" +CLIENT_DIR="$(readlink -f "$(dirname "$BASH_SOURCE")/../../client")" +SERV_IP="$(ip -4 -o addr show scope global | awk '{print $4}' | sed -e 's:/.*::' | head -n1)" + +# +# Initialize openvpn configuration and pki. +# +docker volume create --name $OVPN_DATA +docker run --rm -v $OVPN_DATA:/etc/openvpn $IMG ovpn_genconfig -u udp://$SERV_IP +docker run --rm -v $OVPN_DATA:/etc/openvpn -it -e "EASYRSA_BATCH=1" -e "EASYRSA_REQ_CN=Travis-CI Test CA" $IMG ovpn_initpki nopass + +# +# Fire up the server. +# +sudo iptables -N DOCKER || echo 'Firewall already configured' +sudo iptables -I FORWARD 1 -j DOCKER +docker run -d -v $OVPN_DATA:/etc/openvpn --cap-add=NET_ADMIN --privileged -p 1194:1194/udp --name $NAME $IMG + +# +# Generate a first client certificate and configuration using $CLIENT1 as CN then revoke it. +# +docker exec -it $NAME easyrsa build-client-full $CLIENT1 nopass +docker exec -it $NAME ovpn_getclient $CLIENT1 > $CLIENT_DIR/config.ovpn +docker exec -it $NAME bash -c "echo 'yes' | ovpn_revokeclient $CLIENT1 remove" + +# +# Test that openvpn client can't connect using $CLIENT1 config. +# +if docker run --rm -v $CLIENT_DIR:/client --cap-add=NET_ADMIN --privileged --net=host $IMG /client/wait-for-connect.sh; then + echo "Client was able to connect after revocation test #1." >&2 + exit 2 +fi + +# +# Generate and revoke a second client certificate using $CLIENT2 as CN, then test for failed client connection. +# +docker exec -it $NAME easyrsa build-client-full $CLIENT2 nopass +docker exec -it $NAME ovpn_getclient $CLIENT2 > $CLIENT_DIR/config.ovpn +docker exec -it $NAME bash -c "echo 'yes' | ovpn_revokeclient $CLIENT2 remove" + +if docker run --rm -v $CLIENT_DIR:/client --cap-add=NET_ADMIN --privileged --net=host $IMG /client/wait-for-connect.sh; then + echo "Client was able to connect after revocation test #2." >&2 + exit 2 +fi + +# +# Restart the server +# +docker stop $NAME && docker start $NAME + +# +# Test for failed connection using $CLIENT2 config again. +# +if docker run --rm -v $CLIENT_DIR:/client --cap-add=NET_ADMIN --privileged --net=host $IMG /client/wait-for-connect.sh; then + echo "Client was able to connect after revocation test #3." >&2 + exit 2 +fi + +# +# Stop the server and clean up +# +docker kill $NAME && docker rm $NAME +docker volume rm $OVPN_DATA +sudo iptables -D FORWARD 1 + +# +# Celebrate +# +cat < + ----------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\\ + ||----w | + || || +EOF From dcf3791d541220b38f1938ea9e484b2961686f2c Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 2 May 2017 16:42:39 +0200 Subject: [PATCH 2/5] Generate a CRL during PKI initialization --- bin/ovpn_initpki | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/ovpn_initpki b/bin/ovpn_initpki index c4c6f1d..14b8ec9 100755 --- a/bin/ovpn_initpki +++ b/bin/ovpn_initpki @@ -38,3 +38,6 @@ openvpn --genkey --secret $EASYRSA_PKI/ta.key # For a server key with a password, manually init; this is autopilot easyrsa build-server-full "$OVPN_CN" nopass + +# Generate the CRL for client/server certificates revocation. +easyrsa gen-crl From 59644d953de4ff3b7b746745e1473b04a528965a Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 2 May 2017 17:17:01 +0200 Subject: [PATCH 3/5] Replace hardlinking of crl.pem with a copy easyrsa gen-crl does not modify the crl.pem in place but rather remove the old file and create a new one, which means any hardlink to it will get broken again at each invocation of easyrsa gen-crl. If hardlink to this file is not going to work anyway and we still need it to be readable by OpenVPN, we're better off copying it and chmod-ing it every time a new one is detected on container start, using the conditional expression file1 -nt file2. --- bin/ovpn_run | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/ovpn_run b/bin/ovpn_run index 0b2996c..9e9f3d5 100755 --- a/bin/ovpn_run +++ b/bin/ovpn_run @@ -74,13 +74,14 @@ if [ "$OVPN_DEFROUTE" != "0" ] || [ "$OVPN_NAT" == "1" ] ; then setupIptablesAndRouting fi -# Use a hacky hardlink as the CRL Needs to be readable by the user/group +# Use a copy of crl.pem as the CRL Needs to be readable by the user/group # OpenVPN is running as. Only pass arguments to OpenVPN if it's found. -if [ -r "$EASYRSA_PKI/crl.pem" ]; then - if [ ! -r "$OPENVPN/crl.pem" ]; then - ln "$EASYRSA_PKI/crl.pem" "$OPENVPN/crl.pem" - chmod 644 "$OPENVPN/crl.pem" - fi +if [ "$EASYRSA_PKI/crl.pem" -nt "$OPENVPN/crl.pem" ]; then + cp -f "$EASYRSA_PKI/crl.pem" "$OPENVPN/crl.pem" + chmod 644 "$OPENVPN/crl.pem" +fi + +if [ -r "$OPENVPN/crl.pem" ]; then addArg "--crl-verify" "$OPENVPN/crl.pem" fi From a091bef13b97edd180341fade2afa9a900205cdb Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 2 May 2017 18:03:37 +0200 Subject: [PATCH 4/5] Create a script to handle client revocation This script revoke the certificate corresponding to the commonName passed as first parameter, generate a new CRL, copies it to /etc/openvpn, make it readable by OpenVPN and optionally remove the crt, key and req file corresponding to the revoked certificate using "remove" as second parameter (removal of those files are required to generate a new client certificate using the revoked certificate's CN). --- bin/ovpn_revokeclient | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 bin/ovpn_revokeclient diff --git a/bin/ovpn_revokeclient b/bin/ovpn_revokeclient new file mode 100755 index 0000000..c1c175f --- /dev/null +++ b/bin/ovpn_revokeclient @@ -0,0 +1,61 @@ +#!/bin/bash + +# +# Revoke a client certificate +# + +if [ "$DEBUG" == "1" ]; then + set -x +fi + +set -e + +if [ -z "$OPENVPN" ]; then + export OPENVPN="$PWD" +fi +if ! source "$OPENVPN/ovpn_env.sh"; then + echo "Could not source $OPENVPN/ovpn_env.sh." + exit 1 +fi +if [ -z "$EASYRSA_PKI" ]; then + export EASYRSA_PKI="$OPENVPN/pki" +fi + +cn="$1" +parm="$2" + +if [ ! -f "$EASYRSA_PKI/private/${cn}.key" ]; then + echo "Unable to find \"${cn}\", please try again or generate the key first" >&2 + exit 1 +fi + +revoke_client_certificate(){ + easyrsa revoke "$1" + echo "Generating the Certificate Revocation List :" + easyrsa gen-crl + cp -f "$EASYRSA_PKI/crl.pem" "$OPENVPN/crl.pem" + chmod 644 "$OPENVPN/crl.pem" +} + +remove_files(){ + rm -v "$EASYRSA_PKI/issued/${1}.crt" + rm -v "$EASYRSA_PKI/private/${1}.key" + rm -v "$EASYRSA_PKI/reqs/${1}.req" +} + +case "$parm" in + "remove") + revoke_client_certificate "$cn" + remove_files "$cn" + ;; + "" | "keep") + revoke_client_certificate "$cn" + ;; + *) + echo "When revoking a client certificate, this script let you choose if you want to remove the corresponding crt, key and req files." >&2 + echo "Pease note that the removal of those files is required if you want to generate a new client certificate using the revoked certificate's CN." >&2 + echo " 1. keep (default): Keep the files." >&2 + echo " 2. remove: Remove the files." >&2 + echo "Please specify one of those options as second parameter." >&2 + ;; +esac From 5aea8b914caca4319f10056525c9395a11524a39 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 2 May 2017 18:10:12 +0200 Subject: [PATCH 5/5] Update documentation Add ovpn_revokeclient usage to client.md and docker-compose.md --- docs/clients.md | 9 ++++++--- docs/docker-compose.md | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/clients.md b/docs/clients.md index d5dd073..ccbbecb 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -34,9 +34,12 @@ After doing so, you will find the following files in each of the `$cn` directori ## Revoking Client Certificates -Revoke `client1`'s certificate and generate the certificate revocation list (CRL): +Revoke `client1`'s certificate and generate the certificate revocation list (CRL) using [`ovpn_revokeclient`](/bin/ovpn_revokeclient) script : - docker run --rm -it -v $OVPN_DATA:/etc/openvpn kylemanna/openvpn easyrsa revoke client1 - docker run --rm -it -v $OVPN_DATA:/etc/openvpn kylemanna/openvpn easyrsa gen-crl + docker run --rm -it -v $OVPN_DATA:/etc/openvpn kylemanna/openvpn ovpn_revokeclient client1 The OpenVPN server will read this change every time a client connects (no need to restart server) and deny clients access using revoked certificates. + +You can optionally pass `remove` as second parameter to ovpn_revokeclient to remove the corresponding crt, key and req files : + + docker run --rm -it -v $OVPN_DATA:/etc/openvpn kylemanna/openvpn ovpn_revokeclient client1 remove diff --git a/docs/docker-compose.md b/docs/docker-compose.md index 827ee69..4f3ac2f 100644 --- a/docs/docker-compose.md +++ b/docs/docker-compose.md @@ -59,6 +59,15 @@ docker-compose run --rm openvpn easyrsa build-client-full $CLIENTNAME nopass docker-compose run --rm openvpn ovpn_getclient $CLIENTNAME > $CLIENTNAME.ovpn ``` +* Revoke a client certificate + +```bash +# Keep the corresponding crt, key and req files. +docker-compose run --rm openvpn ovpn_revokeclient $CLIENTNAME +# Remove the corresponding crt, key and req files. +docker-compose run --rm openvpn ovpn_revokeclient $CLIENTNAME remove +``` + ## Debugging Tips * Create an environment variable with the name DEBUG and value of 1 to enable debug output (using "docker -e").