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 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 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 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"). 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