diff --git a/Dockerfile b/Dockerfile index 6a2ee12..0865797 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM ubuntu:precise -RUN echo deb http://archive.ubuntu.com/ubuntu/ precise main universe > /etc/apt/sources.list.d/precise.list +FROM ubuntu:trusty +MAINTAINER randall@mason.ch + RUN apt-get update -q -RUN apt-get install -qy openvpn iptables socat curl +RUN apt-get install -qy openvpn iptables socat zip easy-rsa curl gettext-base miniupnpc ADD ./bin /usr/local/sbin -VOLUME /etc/openvpn -EXPOSE 443/tcp 1194/udp 8080/tcp -CMD run + +CMD ["/usr/local/sbin/run"] diff --git a/README.md b/README.md index 89a1c48..bbaa512 100644 --- a/README.md +++ b/README.md @@ -2,96 +2,33 @@ Quick instructions: +Edit the environment variables half way down runDockVPN.sh and then run it: ```bash -CID=$(docker run -d --privileged -p 1194:1194/udp -p 443:443/tcp jpetazzo/openvpn) -docker run -t -i -p 8080:8080 --volumes-from $CID jpetazzo/openvpn serveconfig +vim runDockVPN.sh +./runDockVPN.sh +``` +it will print out the directory where your OpenVPN configuration files +are stored and you should get those (SECURELY!!!) to your local directory: +```bash +rsync -avH --rsync-path="sudo rsync" userWithSudo@dockerHost:/ OpenVPN-Config/ +``` +or +```bash +docker run --rm --volumes-from OpenVPN-Config busybox tar cvf - /etc/openvpn/ \ + | ssh mobile@jailBrokeniPhone "mkdir OpenVPN-Config; tar -xvf - -C OpenVPN-Config/" ``` -Now download the file located at the indicated URL. You will get a -certificate warning, since the connection is done over SSL, but we are -using a self-signed certificate. After downloading the configuration, -stop the `serveconfig` container. You can restart it later if you need -to re-download the configuration, or to download it to multiple devices. - -The file can be used immediately as an OpenVPN profile. It embeds all the -required configuration and credentials. It has been tested successfully on -Linux, Windows, and Android clients. If you can test it on OS X and iPhone, -let me know! - -**Note:** there is a [bug in the Android Download Manager]( -http://code.google.com/p/android/issues/detail?id=3492) which prevents -downloading files from untrusted SSL servers; and in that case, our -self-signed certificate means that our server is untrusted. If you -try to download with the default browser on your Android device, -it will show the download as "in progress" but it will remain stuck. -You can download it with Firefox; or you can transfer it with another -way: Dropbox, USB, micro-SD card... - -If you reboot the server (or stop the container) and you `docker run` -again, you will create a new service (with a new configuration) and -you will have to re-download the configuration file. However, you can -use `docker start` to restart the service without touching the configuration. - - -## How does it work? - -When the `jpetazzo/openvpn` image is started, it generates: - -- Diffie-Hellman parameters, -- a private key, -- a self-certificate matching the private key, -- two OpenVPN server configurations (for UDP and TCP), -- an OpenVPN client profile. - -Then, it starts two OpenVPN server processes (one on 1194/udp, another -on 443/tcp). - -The configuration is located in `/etc/openvpn`, and the Dockerfile -declares that directory as a volume. It means that you can start another -container with the `--volumes-from` flag, and access the configuration. -Conveniently, `jpetazzo/openvpn` comes with a script called `serveconfig`, -which starts a pseudo HTTPS server on `8080/tcp`. The pseudo server -does not even check the HTTP request; it just sends the HTTP status line, -headers, and body right away. +then you can directly import those files into Ubuntu's Network Manager, +TunnelBlick, iPhone or Android's OpenVPN Connect, or pretty much anywhere. +Since the configuration files are in a seperate volume only container named OpenVPN-Config, +any reboots or restarts will start you back off where you were. Just re-run the script. +If you need to add hosts to your server, just edit and re-run the script. It will restart +OpenVPN, but it will re-create the needed files. A new script to add hosts without a restart +is easy to create, and will hopefully be added later. ## OpenVPN details We use `tun` mode, because it works on the widest range of devices. `tap` mode, for instance, does not work on Android, except if the device is rooted. - -The topology used is `net30`, because it works on the widest range of OS. -`p2p`, for instance, does not work on Windows. - -The TCP server uses `192.168.255.0/25` and the UDP server uses -`192.168.255.128/25`. - -The client profile specifies `redirect-gateway def1`, meaning that after -establishing the VPN connection, all traffic will go through the VPN. -This might cause problems if you use local DNS recursors which are not -directly reachable, since you will try to reach them through the VPN -and they might not answer to you. If that happens, use public DNS -resolvers like those of Google (8.8.4.4 and 8.8.8.8) or OpenDNS -(208.67.222.222 and 208.67.220.220). - - -## Security discussion - -For simplicity, the client and the server use the same private key and -certificate. This is certainly a terrible idea. If someone can get their -hands on the configuration on one of your clients, they will be able to -connect to your VPN, and you will have to generate new keys. Which is, -by the way, extremely easy, since each time you `docker run` the OpenVPN -image, a new key is created. If someone steals your configuration file -(and key), they will also be able to impersonate the VPN server (if they -can also somehow hijack your connection). - -It would probably be a good idea to generate two sets of keys. - -It would probably be even better to generate the server key when -running the container for the first time (as it is done now), but -generate a new client key each time the `serveconfig` command is -called. The command could even take the client CN as argument, and -another `revoke` command could be used to revoke previously issued -keys. diff --git a/bin/client.combined.template b/bin/client.combined.template new file mode 100755 index 0000000..272be47 --- /dev/null +++ b/bin/client.combined.template @@ -0,0 +1,32 @@ +client +dev tun +nobind +resolv-retry infinite +persist-key +persist-tun + +cipher BF-CBC +comp-lzo +passtos +mssfix +verb 3 + +redirect-gateway def1 + + +$CLIENT_KEY + + +$CLIENT_CRT + + +$CA_CRT + + +$DH_PARAMS + +key-direction 1 + +$TA_KEY + + diff --git a/bin/client.template b/bin/client.template new file mode 100755 index 0000000..e853c05 --- /dev/null +++ b/bin/client.template @@ -0,0 +1,19 @@ +client +dev tun +nobind +resolv-retry infinite +persist-key +persist-tun + +cipher BF-CBC +comp-lzo +passtos +mssfix +verb 3 + +redirect-gateway def1 + +ca ca.crt +cert ${CLIENTNAME}.crt +key ${CLIENTNAME}.key +tls-auth ta.key 1 diff --git a/bin/printconfig.sh b/bin/printconfig.sh new file mode 100755 index 0000000..332c564 --- /dev/null +++ b/bin/printconfig.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +while read line +do + grep -q GET "$line" | sed -e 's#GET /\([a-z]\).ovpn HTTP.*#\1#g' | grep -q . && \ + cat client.$(grep -q GET "$line" | sed -e 's#GET /\([a-z]\).ovpn HTTP.*#\1#g').combined.ovpn +done diff --git a/bin/run b/bin/run index 908e28b..f03f890 100755 --- a/bin/run +++ b/bin/run @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -e [ -d /dev/net ] || @@ -6,101 +6,143 @@ set -e [ -c /dev/net/tun ] || mknod /dev/net/tun c 10 200 -cd /etc/openvpn -[ -f dh.pem ] || - openssl dhparam -out dh.pem 512 -[ -f key.pem ] || - openssl genrsa -out key.pem 2048 -chmod 600 key.pem -[ -f csr.pem ] || - openssl req -new -key key.pem -out csr.pem -subj /CN=OpenVPN/ -[ -f cert.pem ] || - openssl x509 -req -in csr.pem -out cert.pem -signkey key.pem -days 36525 - -[ -f tcp443.conf ] || cat >tcp443.conf <udp1194.conf < ${SERVER_NAME}-${SERVER_IP}-${SERVER_PROTO}-${SERVER_PORT}.servers + +for CLIENTNAME in $CLIENTNAMES +do + export CLIENTNAME + [ -f $KEY_DIR/${CLIENTNAME}.key ] || + "$EASY_RSA/pkitool" "${CLIENTNAME}" + export CLIENT_KEY=$(< $KEY_DIR/$CLIENTNAME.key) + export CLIENT_CRT=$(< $KEY_DIR/$CLIENTNAME.crt) + + [ -f $KEY_DIR/${CLIENTNAME}.p12 ] || + $OPENSSL pkcs12 -export -inkey $KEY_DIR/$CLIENTNAME.key -in $KEY_DIR/$CLIENTNAME.crt -certfile $KEY_DIR/ca.crt -out $KEY_DIR/$CLIENTNAME.p12 -password pass:"" + # if we wanted a new config only when we first run, but the port and IP may change, so regenerate anyhow + #[ -f client.${CLIENTNAME}.ovpn ] || { + { + + cat $SCRIPT_PATH/client.combined.template | envsubst > client.${CLIENTNAME}.combined.ovpn + cat $SCRIPT_PATH/client.template | envsubst > client.${CLIENTNAME}.ovpn + # Add in each server template to the bottom of the config, newest first for only the current server: + # for SERVER_STANZA in "$(ls -t ${SERVER_NAME}*servers)" + # Add in each server template to the bottom of the config, newest first for all servers using this same OpenVPN-Config: + for SERVER_STANZA in "$(ls -t *servers)" + do + cat $SERVER_STANZA >> client.${CLIENTNAME}.combined.ovpn + cat $SERVER_STANZA >> client.${CLIENTNAME}.ovpn + done + + } + + #[ -d ${CLIENTNAME}.tblk ] || { + { + mkdir -p ${CLIENTNAME}.tblk + mv client.${CLIENTNAME}.ovpn ${CLIENTNAME}.tblk + cp ${KEY_DIR}/$CLIENTNAME.key ${KEY_DIR}/$CLIENTNAME.crt ${KEY_DIR}/ca.crt ${KEY_DIR}/dh${KEY_SIZE}.pem ${KEY_DIR}/ta.key ${CLIENTNAME}.tblk + zip -r ${CLIENTNAME}.tblk.zip ${CLIENTNAME}.tblk + zip -jr ${CLIENTNAME}.zip ${CLIENTNAME}.tblk + } + +# #[ -f client.${CLIENTNAME}.http ] || cat >client.${CLIENTNAME}.http <client.${CLIENTNAME}.http <client.ovpn < -`cat key.pem` - - -`cat cert.pem` - - -`cat cert.pem` - - -`cat dh.pem` - - - -remote $MY_IP_ADDR 1194 udp - - - -remote $MY_IP_ADDR 443 tcp-client - -EOF - -[ -f client.http ] || cat >client.http <> tcp443.log & -while true ; do openvpn udp1194.conf ; done >> udp1194.log & -tail -F *.log +openvpn \ + --ca ${KEY_DIR}/ca.crt \ + --cert ${KEY_DIR}/${SERVER_NAME}.crt \ + --key ${KEY_DIR}/${SERVER_NAME}.key \ + --dh ${KEY_DIR}/dh${KEY_SIZE}.pem \ + --tls-auth ${KEY_DIR}/ta.key 0 \ + --port ${SERVER_PORT:-1194} \ + --proto ${SERVER_PROTO:-udp} \ + --dev ${SERVER_DEV:-tun} \ + --topology ${SERVER_TOPOLOGY:-subnet} \ + --server ${SERVER_NETWORK:-192.168.255.128} ${SERVER_NETMASK:-255.255.255.128} \ + --push "dhcp-option DNS ${DNS_SERVER:-8.8.8.8}" \ + --max-clients ${SERVER_MAX_CLIENTS:-20} \ + --ifconfig-pool-persist ipp.${SERVER_PROTO:-udp}.txt \ + --push "redirect-gateway def1 bypass-dhcp" \ + --persist-key \ + --persist-tun \ + --comp-lzo \ + --mode server \ + --tls-server \ + --user ${SERVER_USER:-nobody} \ + --group ${SERVER_GROUP:-nogroup} \ + --keepalive 10 120 \ + --status openvpn-status.udp.log \ + --verb 3 diff --git a/bin/serveconfig b/bin/serveconfig index 9ba7dba..213cc5b 100755 --- a/bin/serveconfig +++ b/bin/serveconfig @@ -1,14 +1,14 @@ #!/bin/sh cd /etc/openvpn -[ -f client.http ] || { +ls client*ovpn | cut -d \. -f 2 || { echo "Please run the OpenVPN container at least once in normal mode," echo "to generate the client configuration file. Thank you." exit 1 } -echo "https://$(curl -s http://myip.enix.org/REMOTE_ADDR):8080/" +echo "https://$(curl -s http://myip.enix.org/REMOTE_ADDR):8080/.ovpn" socat -d -d \ OPENSSL-LISTEN:8080,fork,reuseaddr,key=key.pem,certificate=cert.pem,verify=0 \ - EXEC:'cat client.http' \ + EXEC:'printCombined.config.sh' \ 2>> http8080.log diff --git a/bin/server_stanza.template b/bin/server_stanza.template new file mode 100644 index 0000000..eca8364 --- /dev/null +++ b/bin/server_stanza.template @@ -0,0 +1,7 @@ + + +remote $SERVER_IP ${SERVER_PORT} ${SERVER_PROTO} + + +remote $SERVER_NAME ${SERVER_PORT} ${SERVER_PROTO} + diff --git a/environmentVariables b/environmentVariables new file mode 100755 index 0000000..dcaa94a --- /dev/null +++ b/environmentVariables @@ -0,0 +1 @@ +grep -i "=\"\?\${" bin/run | sed -e 's/^\W\+//g' | cut -d = -f 1 | sed -s -e 's/^/-e "/g' -e 's/$/=\" \\/g' diff --git a/importDir.sh b/importDir.sh new file mode 100644 index 0000000..b5ca919 --- /dev/null +++ b/importDir.sh @@ -0,0 +1 @@ +cd etc_openvpn; tar -cf - ./ | docker run --rm -i --volumes-from OpenVPN-Config --name vpnImport ubuntu:trusty bash -c "mkdir -p /etc/openvpn/; tar -xvf - -C /etc/openvpn/" diff --git a/makePersistentStorage.md b/makePersistentStorage.md new file mode 100644 index 0000000..1ca83bc --- /dev/null +++ b/makePersistentStorage.md @@ -0,0 +1,2 @@ +docker run -v /etc/openvpn/ --name OpenVPN-Config busybox true +docker run -t -i --rm --volumes-from OpenVPN-Config --name dockvpn dockvpn bash diff --git a/runDockVPN.sh b/runDockVPN.sh new file mode 100755 index 0000000..28f71dc --- /dev/null +++ b/runDockVPN.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Bash inline comments per: http://stackoverflow.com/a/12797512/317670 + +TAG="clashthebunny/dockvpn:0.1-$(dpkg --print-architecture 2>/dev/null || echo amd64)" +docker build -t="$TAG" . + +# create a container that's just saving the state of /etc/openvpn/ +# this won't overwrite something that exists. If you set this to +# something other than /etc/openvpn, be sure to change $CONFIG_DIR. +CONFIG_DIR="/etc/openvpn" +docker ps -a | grep -q OpenVPN-Config || docker run -v $CONFIG_DIR --name OpenVPN-Config busybox true + +SERVER_PORT=${SERVER_PORT:-1194} +SERVER_PROTO=${SERVER_PROTO:-udp} + +UPNP_URL=$(upnpc -s | grep IGD | sed -e 's/.*http/http/g') +# TODO: add pagekite and/or ngrok support + +# delete any old builds or running vpn clients. +docker ps | grep -q " dockvpn-$SERVER_PROTO-$SERVER_PORT " && docker stop dockvpn-$SERVER_PROTO-$SERVER_PORT +docker ps -a | grep -q " dockvpn-$SERVER_PROTO-$SERVER_PORT " && docker rm dockvpn-$SERVER_PROTO-$SERVER_PORT + +# Run this new build. All the environment variables below are the default in the run script and therefore optional +# You can always override everything by adding a command on the end of the tag. This is because I don't use entrypoint +# This means that once you have a good stable config and openvpn command, just run the openvpn command at the end of the +# line and drop all of the environment variables. +docker run --privileged -t -i --volumes-from OpenVPN-Config \ + --name dockvpn-$SERVER_PROTO-$SERVER_PORT \ + -e "UPNP_URL=$UPNP_URL" `# url to control router` \ + -e "SERVER_PORT=$SERVER_PORT" `# some unused port` \ + -e "SERVER_PROTO=$SERVER_PROTO" `# udp or tcp` \ + -e "SERVER_USER=nobody" `# DROP ROOT PRIVELEGES` \ + -e "SERVER_GROUP=nogroup" `# DROP ROOT GROUP` \ + -e "SERVER_DEV=tun" `# tun will create the next tunX dev` \ + -e "SERVER_TOPOLOGY=subnet" `# subnet, p2p, or net30` \ + -e "SERVER_MAX_CLIENTS=20" `# You know, how many phones you got?` \ + -e "SERVER_NETWORK=192.168.255.0" `# A uniq address space among clients` \ + -e "SERVER_NETMASK=255.255.255.128" `# netmask for above network` \ + -e "DNS_SERVER=8.8.8.8" `# Google\'s last service: xkcd.com/1361/` \ + -e "CONFIG_DIR=$CONFIG_DIR" `# What does OpenVPN-Config share` \ + -e "KEY_SIZE=2048" `# 2048 ought to be enough for anybody...` \ + -e "CA_EXPIRE=3650" `# 10 years` \ + -e "KEY_EXPIRE=3650" `# still 10 years` \ + -e "KEY_COUNTRY=US" `# where are you` \ + -e "KEY_PROVINCE=Massachusetts" `# no, really, where are you` \ + -e "KEY_CITY=Boston" `# no, exactly, where are you` \ + -e "KEY_ORG=MasonFamily" `# and where do you work` \ + -e "KEY_EMAIL=randall@mason.ch" `# and how can you be reached?` \ + -e "KEY_OU=TheITGuy" `# IT, as disfunctional as that is` \ + -e "KEY_NAME=DockVpN" `# This is the name of this system` \ + -e "SERVER_NAME=${SERVER_NAME:-$(uname -n)}" `# the hostname of the server, external` \ + -e "CLIENTNAMES=randall android iphone" `# space seperated list of clients` \ + `# there is one optional argument without a default if all hell breaks loose with DNS` \ + `# -e "SERVER_IP=123.456.789.ABC"` `# great for situations without internet` \ + "$TAG" "$@" + +echo "now get your config files from here:" +#echo $(docker inspect OpenVPN-Config | python -c 'import json,fileinput; print json.loads("".join(fileinput.input()))[0]["Volumes"]["/etc/openvpn"]') +#echo $(docker inspect OpenVPN-Config | grep -A1 Volumes | cut -d \" -f 4 | grep "\/") +echo $(docker inspect -f "{{ .Volumes }}" OpenVPN-Config | cut -d : -f 2- | cut -d \] -f 1)