diff --git a/docker/l2tp/.dockerignore b/docker/l2tp/.dockerignore new file mode 100644 index 0000000..12cf489 --- /dev/null +++ b/docker/l2tp/.dockerignore @@ -0,0 +1,3 @@ +.dockerignore +README.md +l2tp.env \ No newline at end of file diff --git a/docker/l2tp/Dockerfile b/docker/l2tp/Dockerfile new file mode 100644 index 0000000..767ae8e --- /dev/null +++ b/docker/l2tp/Dockerfile @@ -0,0 +1,25 @@ +# Dockerfile for L2TP/IPSec VPN Server +# Copyright (C) 2018 Teddysun + +FROM debian:stretch +MAINTAINER Teddysun + +RUN set -ex \ + && printf "deb http://deb.debian.org/debian sid main" > /etc/apt/sources.list.d/sid.list \ + && apt-get update \ + && apt-get -t sid install -y --no-install-recommends libreswan xl2tpd \ + && apt-get install -y --no-install-recommends wget iproute2 openssl ca-certificates kmod net-tools iptables \ + && apt-get -y autoremove \ + && apt-get -y clean \ + && rm -rf /var/lib/apt/lists/* + +COPY ./ipsec /etc/init.d/ipsec +COPY ./l2tp.sh /usr/bin/l2tp +COPY ./l2tpctl.sh /usr/bin/l2tpctl +RUN chmod 755 /etc/init.d/ipsec /usr/bin/l2tp /usr/bin/l2tpctl + +VOLUME /lib/modules + +EXPOSE 500/udp 4500/udp + +CMD [ "/usr/bin/l2tp" ] diff --git a/docker/l2tp/README.md b/docker/l2tp/README.md new file mode 100644 index 0000000..75b42de --- /dev/null +++ b/docker/l2tp/README.md @@ -0,0 +1,128 @@ +## L2TP/IPsec VPN Server Docker Image by Teddysun + +Docker image to run a L2TP/IPsec VPN Server, with both `L2TP/IPsec PSK` and `IPSec Xauth PSK`. +Based on Debian 9 (Stretch) with latest libreswan (IPsec VPN software) and xl2tpd (L2TP daemon). + +Docker images are built for quick deployment in various computing cloud providers. +For more information on docker and containerization technologies, refer to [official document][1]. + +## Prepare the host + +If you need to install docker by yourself, follow the [official installation guide][2]. + +## Pull the image + +```bash +$ docker pull teddysun/l2tp +``` + +This pulls the latest release of shadowsocks-libev. +It can be found at [Docker Hub][3]. + +## Start a container + +You **must create a environment file** `/etc/l2tp.env` in host at first, and sample value is below: + +``` +VPN_IPSEC_PSK=teddysun.com +VPN_USER=vpnuser +VPN_PASSWORD=vpnpassword +VPN_PUBLIC_IP= +VPN_L2TP_NET= +VPN_L2TP_LOCAL= +VPN_L2TP_REMOTE= +VPN_XAUTH_NET= +VPN_XAUTH_REMOTE= +VPN_DNS1= +VPN_DNS2= +``` + +This will create a default user account for L2TP/IPsec VPN login, which can be used by your **multiple devices**. +The IPSec PSK (pre-shared key) is specified by the `VPN_IPSEC_PSK` environment variable. +The username is specified in `VPN_USER` environment variable. +and password is specified in `VPN_PASSWORD` environment variable. +If your VPS has multiple public IP addresses, maybe public IP need to specified in `VPN_PUBLIC_IP` environment variable. + +There is an example to start a container: + +```bash +$ docker run -d --privileged -p 500:500/udp -p 4500:4500/udp --name l2tp --env-file /etc/l2tp.env -v /lib/modules:/lib/modules teddysun/l2tp +``` + +**Note**: The UDP port number `500` and `4500` must be opened in firewall. + +## Check container details + +If you want to view the container logs: + +```bash +$ docker logs l2tp +``` + +Output log like below: + +``` +L2TP/IPsec VPN Server with the Username and Password is below: + +Server IP: Your Server public IP +IPSec PSK: IPSec PSK (pre-shared key) +Username : VPN username +Password : VPN password + +Redirecting to: /etc/init.d/ipsec start +Starting pluto IKE daemon for IPsec: Initializing NSS database + +xl2tpd[1]: Not looking for kernel SAref support. +xl2tpd[1]: Using l2tp kernel support. +xl2tpd[1]: xl2tpd version xl2tpd-1.3.12 started on 1d20eaecd9f2 PID:1 +xl2tpd[1]: Written by Mark Spencer, Copyright (C) 1998, Adtran, Inc. +xl2tpd[1]: Forked by Scott Balmos and David Stipp, (C) 2001 +xl2tpd[1]: Inherited by Jeff McAdams, (C) 2002 +xl2tpd[1]: Forked again by Xelerance (www.xelerance.com) (C) 2006-2016 +xl2tpd[1]: Listening on IP address 0.0.0.0, port 1701 +``` + +To check the status of your L2TP/IPSec VPN server, you can confirm `ipsec status` to your container like below: + +```bash +$ docker exec -it l2tp ipsec status +``` + +## Manage VPN Users + +If you want to add, modify or remove user accounts, please do it simple like below: + +### List all users + +```bash +$ docker exec -it l2tp l2tpctl -l +``` + +### Add a user + +```bash +$ docker exec -it l2tp l2tpctl -a +``` + +### Delete a user + +```bash +$ docker exec -it l2tp l2tpctl -d +``` + +### Modify a user password + +```bash +$ docker exec -it l2tp l2tpctl -m +``` + +### Print help information + +```bash +$ docker exec -it l2tp l2tpctl -h +``` + + +[1]: https://docs.docker.com/ +[2]: https://docs.docker.com/install/ +[3]: https://hub.docker.com/r/teddysun/l2tp/ \ No newline at end of file diff --git a/docker/l2tp/ipsec b/docker/l2tp/ipsec new file mode 100644 index 0000000..833ea14 --- /dev/null +++ b/docker/l2tp/ipsec @@ -0,0 +1,304 @@ +#!/bin/sh +# IPsec startup and shutdown script +# +### BEGIN INIT INFO +# Provides: ipsec +# Required-Start: $network $remote_fs $syslog $named +# Required-Stop: $syslog $remote_fs +# Default-Start: +# Default-Stop: 0 1 6 +# Short-Description: Start Libreswan IPsec at boot time +# Description: Enable automatic key management for IPsec (KLIPS and NETKEY) +### END INIT INFO +# +### see https://bugzilla.redhat.com/show_bug.cgi?id=636572 +### Debian and Fedora interpret the LSB differently for Default-Start: + +# Copyright (C) 1998, 1999, 2001 Henry Spencer. +# Copyright (C) 2002 Michael Richardson +# Copyright (C) 2006 Michael Richardson +# Copyright (C) 2008 Michael Richardson +# Copyright (C) 2008-2015 Tuomo Soini +# Copyright (C) 2012 Paul Wouters +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. See . +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# ipsec sysv style init.d script for starting and stopping +# the IPsec security subsystem (KLIPS and Pluto). +# +# This script becomes /etc/init.d/ipsec +# and is also accessible as "ipsec setup" +# +# The startup and shutdown times are a difficult compromise (in particular, +# it is almost impossible to reconcile them with the insanely early/late +# times of NFS filesystem startup/shutdown). Startup is after startup of +# syslog and pcmcia support; shutdown is just before shutdown of syslog. +# +# chkconfig: - 47 76 +# description: IPsec provides encrypted and authenticated communications; \ +# NETKEY/KLIPS is the kernel half of it, Pluto is the user-level management daemon. + +test ${IPSEC_INIT_SCRIPT_DEBUG} && set -v -x + +# Source function library. +if [ -f /etc/init.d/functions ]; then + . /etc/init.d/functions +elif [ -f /lib/lsb/init-functions ]; then + . /lib/lsb/init-functions +fi + +# Check that networking is up. +[ "${NETWORKING}" = "no" ] && exit 6 + +if [ $(id -u) -ne 0 ]; then + echo "permission denied (must be superuser)" | \ + logger -s -p daemon.error -t ipsec_setup 2>&1 + exit 4 +fi + +if [ $(ip addr list | grep -c cipsec) -ne 0 ]; then + echo "Cisco IPsec client is already loaded, aborting! (cipsec# device found)" + exit 1 +fi + +# where the private directory and the config files are +IPSEC_CONF="${IPSEC_CONF:-/etc/ipsec.conf}" +IPSEC_EXECDIR="${IPSEC_EXECDIR:-/usr/lib/ipsec}" +IPSEC_SBINDIR="${IPSEC_SBINDIR:-/usr/sbin}" +unset PLUTO_OPTIONS + +rundir=/var/run/pluto +plutopid=${rundir}/pluto.pid +plutoctl=${rundir}/pluto.ctl +lockdir=/var/lock/subsys +lockfile=${lockdir}/ipsec +ipsecversion=/proc/net/ipsec_version +kamepfkey=/proc/net/pfkey + +# /etc/resolv.conf related paths +LIBRESWAN_RESOLV_CONF=${rundir}/libreswan-resolv-conf-backup +ORIG_RESOLV_CONF=/etc/resolv.conf + +# there is some confusion over the name - just do both +[ -f /etc/sysconfig/ipsec ] && . /etc/sysconfig/ipsec +[ -f /etc/sysconfig/pluto ] && . /etc/sysconfig/pluto + +# misc setup +umask 022 + +# standardize PATH, and export it for everything else's benefit +PATH="${IPSEC_SBINDIR}:/sbin:/usr/sbin:/usr/local/bin:/bin:/usr/bin" +export PATH + +mkdir -p ${rundir} +chmod 700 ${rundir} + +verify_config() { + [ -f ${IPSEC_CONF} ] || exit 6 + + config_error=$(ipsec addconn --config ${IPSEC_CONF} --checkconfig 2>&1) + RETVAL=$? + if [ ${RETVAL} -gt 0 ]; then + echo "Configuration error - the following error occurred:" + echo ${config_error} + echo "IKE daemon status was not modified" + exit ${RETVAL} + fi +} + +start() { + echo -n "Starting pluto IKE daemon for IPsec: " + ipsec _stackmanager start + + # pluto searches the current directory, so this is required for making it selinux compliant + cd / + # Create nss db or convert from old format to new sql format + ipsec --checknss + # Enable nflog if configured + ipsec --checknflog > /dev/null + # This script will enter an endless loop to ensure pluto restarts on crash + ipsec _plutorun --config ${IPSEC_CONF} --nofork ${PLUTO_OPTIONS} & + [ -d ${lockdir} ] || mkdir -p ${lockdir} + touch ${lockfile} + # Because _plutorun starts pluto at background we need to make sure pluto is started + # before we know if start was successful or not + for waitsec in 1 2 3 4 5; do + if status >/dev/null; then + RETVAL=0 + break + else + echo -n "." + sleep 1 + RETVAL=1 + fi + done + if [ ${RETVAL} -ge 1 ]; then + rm -f ${lockfile} + fi + echo + return ${RETVAL} +} + + +stop() { + if [ -e ${plutoctl} ]; then + echo "Shutting down pluto IKE daemon" + ipsec whack --shutdown 2>/dev/null + # don't use seq, might not exist on embedded + for waitsec in 1 2 3 4 5 6 7 8 9 10; do + if [ -s ${plutopid} ]; then + echo -n "." + sleep 1 + else + break + fi + done + echo + rm -f ${plutoctl} # we won't be using this anymore + fi + if [ -s ${plutopid} ]; then + # pluto did not die peacefully + pid=$(cat ${plutopid}) + if [ -d /proc/${pid} ]; then + kill -TERM ${pid} + RETVAL=$? + sleep 5; + if [ -d /proc/${pid} ]; then + kill -KILL ${pid} + RETVAL=$? + fi + if [ ${RETVAL} -ne 0 ]; then + echo "Kill failed - removing orphaned ${plutopid}" + fi + else + echo "Removing orphaned ${plutopid}" + fi + rm -f ${plutopid} + fi + + ipsec _stackmanager stop + ipsec --stopnflog > /dev/null + + # cleaning up backup resolv.conf + if [ -e ${LIBRESWAN_RESOLV_CONF} ]; then + if grep 'Libreswan' ${ORIG_RESOLV_CONF} > /dev/null 2>&1; then + cp ${LIBRESWAN_RESOLV_CONF} ${ORIG_RESOLV_CONF} + fi + rm -f ${LIBRESWAN_RESOLV_CONF} + fi + + rm -f ${lockfile} + return ${RETVAL} +} + +restart() { + verify_config + stop + start + return $? +} + +status() { + local RC + if [ -f ${plutopid} ]; then + if [ -r ${plutopid} ]; then + pid=$(cat ${plutopid}) + if [ -n "$pid" -a -d /proc/${pid} ]; then + RC=0 # running + else + RC=1 # not running but pid exists + fi + else + RC=4 # insufficient privileges + fi + fi + if [ -z "${RC}" ]; then + if [ -f ${lockfile} ]; then + RC=2 + else + RC=3 + fi + fi + case "${RC}" in + 0) + echo "ipsec: pluto (pid ${pid}) is running..." + return 0 + ;; + 1) + echo "ipsec: pluto dead but pid file exits" + return 1 + ;; + 2) + echo "ipsec: pluto dead but subsys locked" + return 2 + ;; + 4) + echo "ipsec: pluto status unknown due to insufficient privileges." + return 4 + ;; + esac + echo "ipsec: pluto is stopped" + return 3 +} + +condrestart() { + verify_config + RETVAL=$? + if [ -f ${lockfile} ]; then + restart + RETVAL=$? + fi + return ${RETVAL} +} + +version() { + ipsec version + return $? +} + + +# do it +case "$1" in + start) + start + RETVAL=$? + ;; + stop) + stop + RETVAL=$? + ;; + restart) + restart + RETVAL=$? + ;; + reload|force-reload) + restart + RETVAL=$? + ;; + condrestart|try-restart) + condrestart + RETVAL=$? + ;; + status) + status + RETVAL=$? + ${IPSEC_EXECDIR}/whack --status 2>/dev/null | grep Total | sed 's/^000\ Total\ //' + ;; + version) + version + RETVAL=$? + ;; + *) + echo "Usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status|version}" + RETVAL=2 +esac + +exit ${RETVAL} \ No newline at end of file diff --git a/docker/l2tp/l2tp.env b/docker/l2tp/l2tp.env new file mode 100644 index 0000000..bc3e0cc --- /dev/null +++ b/docker/l2tp/l2tp.env @@ -0,0 +1,16 @@ +# This file is part of L2TP/IPSec VPN Server Docker image. +# Define your own values for these environment variables. +# DO NOT put "" or '' around values, or add space around = +# DO NOT use these special characters within values: \ " ' + +VPN_IPSEC_PSK=teddysun.com +VPN_USER=vpnuser +VPN_PASSWORD=vpnpassword +VPN_PUBLIC_IP= +VPN_L2TP_NET= +VPN_L2TP_LOCAL= +VPN_L2TP_REMOTE= +VPN_XAUTH_NET= +VPN_XAUTH_REMOTE= +VPN_DNS1= +VPN_DNS2= \ No newline at end of file diff --git a/docker/l2tp/l2tp.sh b/docker/l2tp/l2tp.sh new file mode 100644 index 0000000..1ad0609 --- /dev/null +++ b/docker/l2tp/l2tp.sh @@ -0,0 +1,264 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +# +# This is a Shell script for configure and start L2TP/IPSec VPN server with Docker image +# +# Copyright (C) 2018 Teddysun +# +# Reference URL: +# https://github.com/libreswan/libreswan +# https://github.com/xelerance/xl2tpd + +if [ ! -f "/.dockerenv" ]; then + echo "Error: This script must be run in a Docker container." >&2 + exit 1 +fi + +if ip link add dummy0 type dummy 2>&1 | grep -q "not permitted"; then + echo "Error: This Docker image must be run in privileged mode." >&2 + exit 1 +fi + +ip link delete dummy0 >/dev/null 2>&1 + +rand(){ + index=0 + str="" + for i in {a..z}; do arr[index]=${i}; index=$(expr ${index} + 1); done + for i in {A..Z}; do arr[index]=${i}; index=$(expr ${index} + 1); done + for i in {0..9}; do arr[index]=${i}; index=$(expr ${index} + 1); done + for i in {1..10}; do str="$str${arr[$RANDOM%$index]}"; done + echo ${str} +} + +is_64bit(){ + if [ "$(getconf WORD_BIT)" == "32" ] && [ "$(getconf LONG_BIT)" == "64" ]; then + return 0 + else + return 1 + fi +} + +# Environment file name +l2tp_env_file="/etc/l2tp.env" +# Auto generated +if [ -z "${VPN_IPSEC_PSK}" ] && [ -z "${VPN_USER}" ] && [ -z "${VPN_PASSWORD}" ]; then + if [ -f "${l2tp_env_file}" ]; then + echo "Loading previously generated environment variables for L2TP/IPSec VPN Server..." + . "${l2tp_env_file}" + else + echo "L2TP/IPSec VPN Server environment variables is not set. Use default environment variables..." + VPN_IPSEC_PSK="teddysun.com" + VPN_USER="vpnuser" + VPN_PASSWORD="$(rand)" + echo "VPN_IPSEC_PSK=${VPN_IPSEC_PSK}" > ${l2tp_env_file} + echo "VPN_USER=${VPN_USER}" >> ${l2tp_env_file} + echo "VPN_PASSWORD=${VPN_PASSWORD}" >> ${l2tp_env_file} + chmod 600 ${l2tp_env_file} + fi +fi + +# Environment variables: +# VPN_IPSEC_PSK +# VPN_USER +# VPN_PASSWORD +if [ -z "${VPN_IPSEC_PSK}" ] || [ -z "${VPN_USER}" ] || [ -z "${VPN_PASSWORD}" ]; then + echo "Error: Environment variables must be specified. please edit your environment file and retry again." >&2 + exit 1 +fi + +if printf '%s' "${VPN_IPSEC_PSK} ${VPN_USER} ${VPN_PASSWORD}" | LC_ALL=C grep -q '[^ -~]\+'; then + echo "Error: Environment variables must not contain non-ASCII characters." >&2 + exit 1 +fi + +case "${VPN_IPSEC_PSK} ${VPN_USER} ${VPN_PASSWORD}" in + *[\\\"\']*) + echo "Error: Environment variables must not contain these special characters like: \\ \" '" + exit 1 + ;; +esac + +# Environment variables: +# VPN_PUBLIC_IP +PUBLIC_IP=${VPN_PUBLIC_IP:-''} + +[ -z "${PUBLIC_IP}" ] && PUBLIC_IP=$( wget -qO- -t1 -T2 ipv4.icanhazip.com ) +[ -z "${PUBLIC_IP}" ] && PUBLIC_IP=$( wget -qO- -t1 -T2 ipinfo.io/ip ) + +# Environment variables: +# VPN_L2TP_NET +# VPN_L2TP_LOCAL +# VPN_L2TP_REMOTE +# VPN_XAUTH_NET +# VPN_XAUTH_REMOTE +# VPN_DNS1 +# VPN_DNS2 +L2TP_NET=${VPN_L2TP_NET:-'192.168.18.0/24'} +L2TP_LOCAL=${VPN_L2TP_LOCAL:-'192.168.18.1'} +L2TP_REMOTE=${VPN_L2TP_REMOTE:-'192.168.18.10-192.168.18.250'} +XAUTH_NET=${VPN_XAUTH_NET:-'192.168.20.0/24'} +XAUTH_REMOTE=${VPN_XAUTH_REMOTE:-'192.168.20.10-192.168.20.250'} +DNS1=${VPN_DNS1:-'8.8.8.8'} +DNS2=${VPN_DNS2:-'8.8.4.4'} + +# Create IPSec config +cat > /etc/ipsec.conf < /etc/xl2tpd/xl2tpd.conf < /etc/ppp/options.xl2tpd < /etc/ipsec.secrets < /etc/ppp/chap-secrets < /etc/ipsec.d/passwd < +# +# Reference URL: +# https://github.com/libreswan/libreswan +# https://github.com/xelerance/xl2tpd + +rand(){ + index=0 + str="" + for i in {a..z}; do arr[index]=${i}; index=$(expr ${index} + 1); done + for i in {A..Z}; do arr[index]=${i}; index=$(expr ${index} + 1); done + for i in {0..9}; do arr[index]=${i}; index=$(expr ${index} + 1); done + for i in {1..10}; do str="$str${arr[$RANDOM%$index]}"; done + echo ${str} +} + +list_users(){ + if [ ! -f /etc/ppp/chap-secrets ];then + echo "Error: /etc/ppp/chap-secrets file not found." + exit 1 + fi + local line="+-------------------------------------------+\n" + local string=%20s + printf "${line}|${string} |${string} |\n${line}" Username Password + grep -v "^#" /etc/ppp/chap-secrets | awk '{printf "|'${string}' |'${string}' |\n", $1,$3}' + printf ${line} +} + +add_user(){ + while : + do + read -p "Please enter Username:" user + if [ -z ${user} ]; then + echo "Username can not be empty" + else + grep -w "${user}" /etc/ppp/chap-secrets > /dev/null 2>&1 + if [ $? -eq 0 ];then + echo "Username (${user}) already exists. Please re-enter your username." + else + break + fi + fi + done + pass="$(rand)" + echo "Please enter ${user}'s password:" + read -p "(Default Password: ${pass}):" tmppass + [ ! -z ${tmppass} ] && pass=${tmppass} + pass_enc=$(openssl passwd -1 "${pass}") + echo "${user} l2tpd ${pass} *" >> /etc/ppp/chap-secrets + echo "${user}:${pass_enc}:xauth-psk" >> /etc/ipsec.d/passwd + echo "Username (${user}) add completed." +} + +del_user(){ + while : + do + read -p "Please enter Username you want to delete it:" user + if [ -z ${user} ]; then + echo "Username can not be empty" + else + grep -w "${user}" /etc/ppp/chap-secrets >/dev/null 2>&1 + if [ $? -eq 0 ];then + break + else + echo "Username (${user}) is not exists. Please re-enter your username." + fi + fi + done + sed -i "/^\<${user}\>/d" /etc/ppp/chap-secrets + sed -i "/^\<${user}\>/d" /etc/ipsec.d/passwd + echo "Username (${user}) delete completed." +} + +mod_user(){ + while : + do + read -p "Please enter Username you want to change password:" user + if [ -z ${user} ]; then + echo "Username can not be empty" + else + grep -w "${user}" /etc/ppp/chap-secrets >/dev/null 2>&1 + if [ $? -eq 0 ];then + break + else + echo "Username (${user}) is not exists. Please re-enter your username." + fi + fi + done + pass="$(rand)" + echo "Please enter ${user}'s new password:" + read -p "(Default Password: ${pass}):" tmppass + [ ! -z ${tmppass} ] && pass=${tmppass} + pass_enc=$(openssl passwd -1 "${pass}") + sed -i "/^\<${user}\>/d" /etc/ppp/chap-secrets + sed -i "/^\<${user}\>/d" /etc/ipsec.d/passwd + echo "${user} l2tpd ${pass} *" >> /etc/ppp/chap-secrets + echo "${user}:${pass_enc}:xauth-psk" >> /etc/ipsec.d/passwd + echo "Username ${user}'s password has been changed." +} + +action=$1 +case ${action} in + -l|--list) + list_users + ;; + -a|--add) + add_user + ;; + -d|--del) + del_user + ;; + -m|--mod) + mod_user + ;; + -h|--help) + echo "Usage: `basename $0` -l,--list List all users" + echo " `basename $0` -a,--add Add a user" + echo " `basename $0` -d,--del Delete a user" + echo " `basename $0` -m,--mod Modify a user password" + echo " `basename $0` -h,--help Print this help information" + ;; + *) + echo "Usage: `basename $0` [-l,--list|-a,--add|-d,--del|-m,--mod|-h,--help]" && exit + ;; +esac