How we chose the VPN protocol and set up the server

Why all this and what for?



We had: 10 simplest server configurations on DigitalOcean, iOS mobile devices, a server for collecting statistics, no experience in setting up VPN servers, and an indomitable desire to make a fast, reliable and easy-to-use VPN service that will enjoy enjoy. Not that all this was absolutely necessary, but if we have already begun, then we must approach the matter seriously.



In this article, I will briefly describe how we chose the VPN protocol, how we set up the server and how we organized the collection of statistics from each VPN server. Don't rely on detailed step-by-step instructions. In the article, I will provide excerpts from the configuration files with brief comments.



I think that in the IT sphere you will no longer find a person who would not know what a VPN is and why you need it.



But if you explain briefly why a modern person needs a VPN, then you get something like the following:



  • If you have any internal (private) resources, access to which should be limited from the global Internet.
  • If you need to establish a secure connection between two networks.
  • If you need to access resources that, for one reason or another, are not available from your country (your location changes relative to your IP address).


And the not entirely obvious point: the speed of the Internet connection can be higher through vpn, because your ISP can send your traffic along a shorter route, which means a more optimal route, because of this, you can get an increase in speed. But it can work in the opposite direction if you choose a not very good server location relative to you (more on that later).



VPN-



VPN- .



.



, :



  • .
  • .


+ , .



PPTP (Point-To-Point Tunneling Protocol)



VPN , Microsoft. - , . Microsoft L2TP SSTP PPTP.



, .



L2TP/IPSec



PPTP, . , -, .. .

L2TP/IPsec , . : , , VPN-.



.. , , , .



IKEv2/IPSec



Microsoft Cisco, (, OpenIKEv2, Openswan strongSwan).



Mobility and Multi-homing Protocol (MOBIKE), .



IKEv2 , WiFi .



IKEv2 .



, .. Mobility and Multi-homing Protocol .



OpenVPN



OpenVPN Technologies.



, .

OpenVPN . , TCP UPD, . VPN .



OpenVPN, , .



, , , - , .



Wireguard



VPN. IPSec OpenVPN, , , .



Unix , .. Unix. .



, , .



.



IKEv2/IPSe, :



  • Mobility and Multi-homing Protocol (MOBIKE).
  • .
  • .
  • .


.



VPN-



, , () ().



— , .. , ( ), .., , - .



- , digitalocean , 1 Gb 25 Gb .



, - VPN- .



:



  • Docker + docker-compose.
  • strongswan — IPSec .
  • Let's Encrypt — .
  • Radius — .


Docker , vpn-.



Docker.file ( )



FROM alpine:latest #      alpine-linux

...
# strongSwan Version
ARG SS_VERSION="https://download.strongswan.org/strongswan-5.8.2.tar.gz" #   ,        .

...

COPY ./run.sh /run.sh
COPY ./adduser.sh /adduser.sh
COPY ./rmuser.sh /rmuser.sh

RUN chmod 755 /run.sh /adduser.sh /rmuser.sh

VOLUME ["/usr/local/etc/ipsec.secrets"]

EXPOSE 500:500/udp 4500:4500/udp

CMD ["/run.sh"]


adduser.sh, rmuser.sh .



adduser.sh



#!/bin/sh

VPN_USER="$1"

if [ -z "$VPN_USER" ]; then
  echo "Usage: $0 username" >&2
  echo "Example: $0 jordi" >&2
  exit 1
fi

case "$VPN_USER" in
  *[\\\"\']*)
    echo "VPN credentials must not contain any of these characters: \\ \" '" >&2
    exit 1
    ;;
esac

VPN_PASSWORD="$(openssl rand -base64 9)" #   
HOST="$(printenv VPNHOST)"

echo "Password for user is: $VPN_PASSWORD"
echo $VPN_USER : EAP \"$VPN_PASSWORD\">> /usr/local/etc/ipsec.secrets #        /usr/local/etc/ipsec.secrets

ipsec rereadsecrets


rmuser.sh



#!/bin/sh

VPN_USER="$1"

if [ -z "$VPN_USER" ]; then
  echo "Usage: $0 username" >&2
  echo "Example: $0 jordi" >&2
  exit 1
fi

cp /usr/local/etc/ipsec.secrets /usr/local/etc/ipsec.secrets.bak
sed "/$VPN_USER :/d" /usr/local/etc/ipsec.secrets.bak > /usr/local/etc/ipsec.secrets #        /usr/local/etc/ipsec.secrets

ipsec rereadsecrets


ipsec.secrets.



:



run.sh



#!/bin/bash

VPNIPPOOL="10.15.1.0/24" #       IP  ,     VPN-.
LEFT_ID=${VPNHOST}       #   vpn-

sysctl net.ipv4.ip_forward=1
sysctl net.ipv6.conf.all.forwarding=1
sysctl net.ipv6.conf.eth0.proxy_ndp=1

if [ ! -z "$DNS_SERVERS" ] ; then #    DNS ,     vpn .
DNS=$DNS_SERVERS
else
DNS="1.1.1.1,8.8.8.8"
fi

if [ ! -z "$SPEED_LIMIT" ] ; then #  ,     ""  ,       .
tc qdisc add dev eth0 handle 1: ingress
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip src 0.0.0.0/0 police rate ${SPEED_LIMIT}mbit burst 10k drop flowid :1
tc qdisc add dev eth0 root tbf rate ${SPEED_LIMIT}mbit latency 25ms burst 10k
fi

iptables -t nat -A POSTROUTING -s ${VPNIPPOOL} -o eth0 -m policy --dir out --pol ipsec -j ACCEPT
iptables -t nat -A POSTROUTING -s ${VPNIPPOOL} -o eth0 -j MASQUERADE

iptables -L

#     
if [[ ! -f "/usr/local/etc/ipsec.d/certs/fullchain.pem" && ! -f "/usr/local/etc/ipsec.d/private/privkey.pem" ]] ; then
    certbot certonly --standalone --preferred-challenges http --agree-tos --no-eff-email --email ${LEEMAIL} -d ${VPNHOST}
    cp /etc/letsencrypt/live/${VPNHOST}/fullchain.pem /usr/local/etc/ipsec.d/certs
    cp /etc/letsencrypt/live/${VPNHOST}/privkey.pem /usr/local/etc/ipsec.d/private
    cp /etc/letsencrypt/live/${VPNHOST}/chain.pem /usr/local/etc/ipsec.d/cacerts
fi

rm -f /var/run/starter.charon.pid

#   ipsec 
if [ -f "/usr/local/etc/ipsec.conf" ]; then
rm /usr/local/etc/ipsec.conf
cat >> /usr/local/etc/ipsec.conf <<EOF
config setup
    charondebug="ike 1, knl 1, cfg 1"
    uniqueids=never
    conn ikev2-vpn
    ...

    eap_identity=%identity
EOF
fi

if [ ! -f "/usr/local/etc/ipsec.secrets" ]; then
cat > /usr/local/etc/ipsec.secrets <<EOF
: RSA privkey.pem
EOF
fi

.....
EOF
fi
sysctl -p

ipsec start --nofork


, docker-compose:



version: '3'

services:
  vpn:
    build: .
    container_name: ikev2-vpn-server
    privileged: true
    volumes:
      - './data/certs/certs:/usr/local/etc/ipsec.d/certs'
      - './data/certs/private:/usr/local/etc/ipsec.d/private'
      - './data/certs/cacerts:/usr/local/etc/ipsec.d/cacerts'
      - './data/etc/ipsec.d/ipsec.secrets:/usr/local/etc/ipsec.secrets'
    env_file:
      - .env
    ports:
      - '500:500/udp'
      - '4500:4500/udp'
      - '80:80'
    depends_on:
      - radius
    links:
      - radius
    networks:
      - backend

  radius:
    image: 'freeradius/freeradius-server:latest'
    container_name: freeradius-server
    volumes:
      - './freeradius/clients.conf:/etc/raddb/clients.conf'
      - './freeradius/mods-enabled/rest:/etc/raddb/mods-enabled/rest'
      - './freeradius/sites-enabled/default:/etc/raddb/sites-enabled/default'
    env_file:
      - .env
    command: radiusd -X
    networks:
      - backend
networks:
  backend:
    ipam:
      config:
        - subnet: 10.0.0.0/24


volume , .



, , Let's Encrypt.



.env , :



VPNHOST=vpn.vpn.com #   vpn-
LEEMAIL=admin@admin.com #  ,       Let's Encrypt
SPEED_LIMIT=20 #  ,       mbit
DNS_SERVERS= #      DNS 
RADIUS_SERVER= #  radius ,      radius
RADIUS_SERVER_SECRET= #  ,       radius 
REMOTE_SERVER= #     endpoint,      radius ,    .


docker-compose up -d, vpn-, radius ( ).





VPN-



, , . ipsec, , - , .



, , FreeRadius , ipsec .



FreeRadius , .

radius ipsec:



FreeRADIUS



if [[ ! -z "$RADIUS_SERVER" && ! -z "$RADIUS_SERVER_SECRET" ]]; then
rm /usr/local/etc/strongswan.d/charon/eap-radius.conf
cat >> /usr/local/etc/strongswan.d/charon/eap-radius.conf <<EOF
eap-radius {
    accounting = yes #  , ipsec        /,        
    accounting_close_on_timeout = no
    accounting_interval = 300 #    radius     .
    close_all_on_timeout = no
    load = yes
    nas_identifier = $VPNHOST

    # Section to specify multiple RADIUS servers.
    servers {
        primary {
            address = $RADIUS_SERVER
            secret = $RADIUS_SERVER_SECRET
            auth_port = 1812   # default
            acct_port = 1813   # default
        }
    }
}


endpoint, rest. /etc/raddb/mods-enabled/rest accounting, - :



accounting {
    uri = "${..connect_uri}/vpn_sessions/%{Acct-Session-Id}-%{Acct-Unique-Session-ID}"
method = 'post'
tls = ${..tls}
body = json
data = '{ "username": "%{User-Name}", "nas_port": "%{NAS-Port}", "nas_ip_address": "%{NAS-IP-Address}", "framed_ip_address": "%{Framed-IP-Address}", "framed_ipv6_prefix": "%{Framed-IPv6-Prefix}", "nas_identifier": "%{NAS-Identifier}", "airespace_wlan_id": "%{Airespace-Wlan-Id}", "acct_session_id": "%{Acct-Session-Id}", "nas_port_type": "%{NAS-Port-Type}", "cisco_avpair": "%{Cisco-AVPair}", "acct_authentic": "%{Acct-Authentic}", "tunnel_type": "%{Tunnel-Type}", "tunnel_medium_type": "%{Tunnel-Medium-Type}", "tunnel_private_group_id": "%{Tunnel-Private-Group-Id}", "event_timestamp": "%{Event-Timestamp}", "acct_status_type": "%{Acct-Status-Type}", "acct_input_octets": "%{Acct-Input-Octets}", "acct_input_gigawords": "%{Acct-Input-Gigawords}", "acct_output_octets": "%{Acct-Output-Octets}", "acct_output_gigawords": "%{Acct-Output-Gigawords}", "acct_input_packets": "%{Acct-Input-Packets}", "acct_output_packets": "%{Acct-Output-Packets}", "acct_terminate_cause": "%{Acct-Terminate-Cause}", "acct_session_time": "%{Acct-Session-Time}", "acct_delay_time": "%{Acct-Delay-Time}", "calling_station_id": "%{Calling-Station-Id}", "called_station_id": "%{Called-Station-Id}"}'

 }


.



VPN , , Apple , , , Let's Encrypt.



?



  • radius , VPN.
  • , VPN-.


VPN-?



  • radius.
  • .
  • , .
  • health-check .


?



Through trial and error, we came up with the option described in the article, first of all, we adhered to the principle of "make it simpler", did not reinvent our own bicycles and used ready-made tools like Docker, FreeRadius. Yes, most likely there is a place for optimization, tightening security policies, automation. But our option is perfect for personal use and for use in small companies if you need to organize access to private (closed) information.




All Articles