Création d'un serveur VPN

Par ghislain , 25 septembre, 2024

Objectifs

Le but est de d'obtenir un serveur avec un proxy Squid qui utilise le VPN. Ainsi la navigation apparaît sortir au bout du tunnel VPN, peut-être dans un autre pays.

Un évolution serait de mettre en place un proxy SOCKS v5 pour augmenter les possibilités offertes par ce serveur.

Prérequis

Avant de commencer il vous faut bien entendu un Raspberry PI, un type 1 B est utilisé dans mon cas. Celui-ci doit être installé avec une distribution Raspberry Pi OS ou Ubuntu sans interface graphique ( cela réduit énormément les mise à jour ) avec le réseau de configurer et le SSH actif.

Il vous faut choisir un fournisseur de VPN pour pouvoir monter le tunnel vers un des serveurs de ce fournisseur. Le paramétrage est disponible dans la console de votre compte client du fournisseur de VPN choisit.

Iptables

Afin de paramétrer IPTables, nous allons utiliser les services initd pour configurer le mur de feu. N'ayant qu'une seule interface réseau cela convient tout à fait, il y a d'autres solutions avec les scripts if-up.d et if-down.d. Pour cela nous ajoutons un fichier iptables dans la liste des services et nous l'activons avec la commande update-rc.d :

#!/bin/bash
### BEGIN INIT INFO
# Provides:          iptables
# Required-Start:
# Required-Stop:
# X-Start-Before:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Enables and disables firewall rules
# Description:       Enables and disables the firewall rules
#                    using iptables(8)
### END INIT INFO
LOCKFILE=/var/lock/firewall.lock
IPTABLES=/sbin/iptables
IPV4_NETWORK=192.168.0.0/24
IP6TABLES=/sbin/ip6tables
IPV6_NETWORK=2001:861:e040:e2b0::/64
PATH=/sbin:/bin:/usr/sbin:/usr/bin
. /lib/lsb/init-functions
start_firewall() {
    if [ -f "${LOCKFILE}" ]; then
        log_failure_msg "Lock file exists, firewall is already enabled?"
        exit 1
    fi
    if ! touch ${LOCKFILE} ; then
        log_failure_msg "Cannot create a lock file!"
        exit 1
    fi
    log_success_msg "Enabling firewall rules using iptables(8)."
########## Iptables rules ##########
## Remove any existing rules from all chains
  ${IPTABLES} -F
  ${IPTABLES} -F -t nat
  ${IPTABLES} -F -t mangle
  ${IP6TABLES} -F
  ${IP6TABLES} -F -t nat
  ${IP6TABLES} -F -t mangle
## Remove any pre-existing user-defined rules
  ${IPTABLES} -X
  ${IPTABLES} -X -t nat
  ${IPTABLES} -X -t mangle
  ${IP6TABLES} -X
  ${IP6TABLES} -X -t nat
  ${IP6TABLES} -X -t mangle
# Zero the counters
  ${IPTABLES} -Z
  ${IP6TABLES} -Z
#  ${IPTABLES} -N LOG_DROP
#  ${IPTABLES} -A LOG_DROP -j LOG --log-prefix '[IPTABLES DROP] : '
#  ${IPTABLES} -A LOG_DROP -j DROP
#  ${IP6TABLES} -N LOG_DROP
#  ${IP6TABLES} -A LOG_DROP -j LOG --log-prefix '[IPTABLES DROP] : '
#  ${IP6TABLES} -A LOG_DROP -j DROP
########## DEFAULT RULES ##########
  ${IPTABLES} -P INPUT DROP
  ${IP6TABLES} -P INPUT DROP
  ${IPTABLES} -P OUTPUT DROP
  ${IP6TABLES} -P OUTPUT DROP
  ${IPTABLES} -P FORWARD DROP
  ${IP6TABLES} -P FORWARD DROP
## Accept looplocal in input.
  ${IPTABLES} -I INPUT -i lo -j ACCEPT
  ${IP6TABLES} -I INPUT -i lo -j ACCEPT
  ${IPTABLES} -I OUTPUT -o lo -j ACCEPT
  ${IP6TABLES} -I OUTPUT -o lo -j ACCEPT
########## COMMON SECURITY RULES ##########
## Drop XMAS and NULL scans.
  ${IPTABLES} -A INPUT -p tcp --tcp-flags FIN,URG,PSH FIN,URG,PSH -j DROP
  ${IP6TABLES} -A INPUT -p tcp --tcp-flags FIN,URG,PSH FIN,URG,PSH -j DROP
  ${IPTABLES} -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
  ${IP6TABLES} -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
  ${IPTABLES} -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
  ${IP6TABLES} -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
  ${IPTABLES} -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
  ${IP6TABLES} -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
## Drop silently broadcasts.
  ${IPTABLES} -A INPUT -m pkttype --pkt-type broadcast -j DROP
  ${IP6TABLES} -A INPUT -m pkttype --pkt-type broadcast -j DROP
########## NORMAL RULES ##########
## Permit open connexion to send packets.
  ${IPTABLES} -A OUTPUT -m state ! --state INVALID -j ACCEPT
  ${IP6TABLES} -A OUTPUT -m state ! --state INVALID -j ACCEPT
## Permit open connection to receive packets.
  ${IPTABLES} -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
  ${IP6TABLES} -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
## Allow SSH
  ${IPTABLES} -A INPUT  -p tcp -s $IPV4_NETWORK --dport 22 -j ACCEPT
  ${IP6TABLES} -A INPUT  -p tcp -s $IPV6_NETWORK --dport 22 -j ACCEPT
## Allow FTP as client
#  ${IPTABLES} -A INPUT -p tcp -s $IPV4_NETWORK --sport 20 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
#  ${IP6TABLES} -A INPUT -p tcp -s $IPV6_NETWORK --sport 20 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
## Allow Transmission web interface
  ${IPTABLES} -A INPUT  -p tcp -s $IPV4_NETWORK --dport 9091 -j ACCEPT
  ${IP6TABLES} -A INPUT  -p tcp -s $IPV6_NETWORK --dport 9091 -j ACCEPT
## Allow specified BitTorrrent client port
  ${IPTABLES} -A INPUT  -p tcp --dport 9999 -j ACCEPT
  ${IP6TABLES} -A INPUT  -p tcp --dport 9999 -j ACCEPT
## Allow incoming HTTP traffic to Squid Proxy 
  ${IPTABLES} -A INPUT  -p tcp -s $IPV4_NETWORK --dport 8080 -j ACCEPT
  ${IP6TABLES} -A INPUT  -p tcp -s $IPV6_NETWORK --dport 8080 -j ACCEPT
## Log packets in forward.
#  ${IPTABLES} -A FORWARD -j LOG
#  ${IP6TABLES} -A FORWARD -j LOG
  log_success_msg "Firewall rules loaded successfully."
}
reset_firewall() {
    log_success_msg "Disabling iptables(8) firewall rules."
    # Remove any existing rules from all chains
    ${IPTABLES} -F
    ${IPTABLES} -F -t nat
    ${IPTABLES} -F -t mangle
    ${IP6TABLES} -F
    ${IP6TABLES} -F -t nat
    ${IP6TABLES} -F -t mangle
    # Remove any pre-existing user-defined rules
    ${IPTABLES} -X
    ${IPTABLES} -X -t nat
    ${IPTABLES} -X -t mangle
    ${IP6TABLES} -X
    ${IP6TABLES} -X -t nat
    ${IP6TABLES} -X -t mangle
 
    # Zero the counters
    ${IPTABLES} -Z
    ${IP6TABLES} -Z
    ${IPTABLES} -P INPUT ACCEPT
    ${IPTABLES} -P OUTPUT ACCEPT
    ${IPTABLES} -P FORWARD ACCEPT
    ${IP6TABLES} -P INPUT ACCEPT
    ${IP6TABLES} -P OUTPUT ACCEPT
    ${IP6TABLES} -P FORWARD ACCEPT
    log_success_msg "Firewall shutdown successful."
}
status_firewall() {
    if [ -f "${LOCKFILE}" ]; then
        log_success_msg "Firewall is enabled."
    else
        log_success_msg "Firewall is disabled."
    fi
}
case "${1}" in
    start)
        start_firewall
        ;;
    reset)
        reset_firewall
        ;;
    stop)
        reset_firewall
        rm -f "${LOCKFILE}"
        ;;
    status)
        status_firewall
        ;;
    reload|restart|force-reload)
        reset_firewall
        rm -f "${LOCKFILE}"
        start_firewall
        ;;
    *)
        echo "usage: ${0} {start|stop|reload|restart|force-reload|reset" >&2
        ;;
esac

OpenVPN

On utilisera la version OpenVPN fournie avec la distribution. Cependant il faut désactiver le service de démarrage du serveur et créer un fichier dans /etc/init.d pour démarrer OpenVPN en tant que client. Les identifiants de connexion doivent être écrits dans le fichier /root/openvpn.auth ( première ligne avec le login et second ligne avec le mot de passe en clair ) dans l'exemple ci-dessous :

#! /bin/sh -e
### BEGIN INIT INFO
# Provides:          openvpn-client
# Required-Start:    $local_fs $network
# Required-Stop:     $local_fs $network
# Default-Start:     
# Default-Stop:      0 1 2 3 4 5 6
# Short-Description: Openvpn client
# Description:       Connect a openvpn tunnel
#
### END INIT INFO
. /lib/lsb/init-functions
OPENVPN_DIR=/etc/openvpn
OPENVPN_CLIENT=/usr/sbin/openvpn
OPENVPN_AUTHFILE=/root/openvpn.auth
# Source defaults file. Edit that file to configure this script.
if [ -e /etc/default/openvpn-client ]; then
  . /etc/default/openvpn-client
fi
if [ "$OPENVPN_CLIENT" = "/usr/sbin/openvpn" ]; then
  test -x "$OPENVPN_CLIENT" || exit 0
elif [ ! -x "$OPENVPN_CLIENT" ]; then
  log_failure_msg "Openvpn client '$OPENVPN_CLIENT' does not exist or is not" \
    "executable."
  exit 5
fi
if [ ! -f "$OPENVPN_AUTHFILE" -o  ! -r "$OPENVPN_AUTHFILE" ]; then
  log_failure_msg "Openvpn authentication '$OPENVPN_AUTHFILE' does not exist or is not" \
    "executable."
  exit 5
fi
if [ ! -d "$OPENVPN_DIR" ]; then
  log_failure_msg "OPENVPN configuration directory '$OPENVPN_DIR' does not exist."
  exit 6
fi
if [ ! -z "$OPENVPN_OPTS" ]; then
        (echo $OPENVPN_OPTS | grep -- '--daemon' 1>/dev/null) &&
                log_warning_msg "\`--daemon' option detected \
                        on /etc/default/openvpn, this \
                        can cause problems on openvpn. The option \
                        will be suppressed"
                OPENVPN_OPTS=`echo "$OPENVPN_OPTS" | sed 's/--daemon//g'`
fi
PIDFILE=/var/run/openvpn-client.pid
CONFIGFILE=openvpn.conf
DESC="Openvpn client"
NAME=`basename $OPENVPN_CLIENT`
OPENVPN_OPTS=" --config $OPENVPN_DIR/$CONFIGFILE --writepid $PIDFILE --daemon $NAME --auth-user-pass $OPENVPN_AUTHFILE --script-security 2"
is_running()
{
  retval=1
  if [ -r $PIDFILE ]; then
    pid=`cat $PIDFILE`
    if [ -e /proc/$pid ]; then
      procname=`/bin/ps h -p $pid`
      [ -n "$procname" ] && retval=0
    fi
  fi
  return $retval
}
start()
{
  log_begin_msg "Starting $DESC: $NAME"
  if is_running; then
    log_progress_msg "already running"
  else
    start-stop-daemon --start --quiet --background --pidfile $PIDFILE \
      --make-pidfile \
      --exec $OPENVPN_CLIENT -- $OPENVPN_OPTS
  fi
  log_end_msg 0
  if [ "$SCHEDULE" = "1" ]; then
    schedule
  fi
}
stop()
{
  log_begin_msg "Stopping $DESC: $NAME"
  if ! is_running; then
    log_progress_msg "not running"
  else
    start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE \
   --exec $OPENVPN_CLIENT
                # Wait until really stopped - $pid is set from is_running
                # (waiting for max 60s (600 cycles at 100ms))
                i=0
                while kill -0 "$pid" 2> /dev/null;  do
                        if [ $i = '600' ]; then
                                break;
                        else
                                if [ $i = '0' ]; then
                                        echo -n " ... waiting "
                                elif [ $(($i%10)) = 0 ]; then
                                        echo -n "."
                                fi
                                i=$(($i+1))
                                sleep .1
                        fi
                done
  fi
  rm -f $PIDFILE
  log_end_msg 0
}
status()
{
  STATUS="Status of $DESC:"
  if is_running; then
    log_success_msg "$STATUS running"
  else
    log_success_msg "$STATUS stopped"
  fi
}
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart|force-reload)
    stop
    sleep 1
    start
    ;;
  status)
    status
    ;;
  schedule)
    schedule
    ;;
  *)
    log_success_msg "Usage: $0 {start|stop|restart|force-reload|status}" >&2
    exit 1
    ;;
esac

 

Squid

Nous utilisons la version Squid de la distribution utilisée. Le fichier de configure squid.conf doit rester assez simple pour l'usage voulu :

acl localnet src fc00::/7               # RFC 4193 local private network range
acl localnet src XX.XX.XX.XX/??         # Local LAN
acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
include /etc/squid/conf.d/*
http_access allow localnet
http_access allow localhost
http_access deny all
http_port 8080
coredump_dir /var/spool/squid
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     u0%      0
refresh_pattern .               0       20%     4320

Celui-ci doit cependant se lancer après le VPN afin de la route par défaut ait déjà été changée. Aussi le service sera lancé depuis un autre script.

Du coup dans le fichier /etc/init.d/squid on prendra soin au début du fichier de modifier les lignes pour obtenir :

# Default-Start:
# Default-Stop:      0 1 2 3 4 5 6

pour ensuite lancer la commande de mise à jour les liens symboliques :

update-rc.d squid defaults

Transmission

Pour transmission la version de la distribution est largement suffisante. Comme pour Squid, celui-ci doit se lancer après le VPN afin que la route par défaut soit celle du VPN. De la même manière in modifiera le fichier /etc/init.d/transmission-daemon

# Default-Start:
# Default-Stop:      0 1 2 3 4 5 6

pour ensuite lancer la commande de mise à jour les liens symboliques :

update-rc.d squid defaults

Pensez à monter un disque externe ou avoir une carte SD suffisamment importante en fonction de ce que voulez faire.

Démarrage des services

Pour lancer tout cela j'ai créé un fichier de lancement des services nommé /etc/init.d/vpnbox : 

#!/bin/sh
### BEGIN INIT INFO
# Provides:          vpnbox
# Required-Start:    $local_fs $network
# Required-Stop:     $local_fs $network
# Default-Start:     3 4 5
# Default-Stop:      0 1 2 6
# Short-Description: VPN box services
# Description:       Connect an open vpn tunnel and start squid and transmission daemon
#
### END INIT INFO

. /lib/lsb/init-functions

VPN_SERVICE="openvpn-client"
VPN_SERVICE_STATUS_RESPONSE='Openvpn client'
BITTORRENT_SERVICE="transmission-daemon"
BITTORRENT_DESC='Transmission BitTorrent Client'
SQUID_SERVICE="squid"
SQUID_DESC="Squid HTTP Proxy 3.x"

test_vpn_actif(){
        content=$(wget https://wtfismyip.com/text -q -O-)
        isValid=$(echo $content | sed 's/^\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}$/1/g')
        if [ "$(echo $isValid | grep "^[[:digit:]]*$")" ] && [ 1 -eq $isValid ]; then
                return 1
        else
                return 0
        fi
}

test_vpn_connected(){
        content=$(ifconfig tun0 | egrep '[[:space:]]*netmask[[:space:]]*([0-9]{1,3}\.){3}([0-9]{1,3})[[:space:]]*destination[[:space:]]*([0-9]{1,3}\.){3}([0-9]{1,3})')
        if [ -n "$content" ]; then
                return 1
        else
                return 0
        fi
}

wait_vpn_connected(){
        test_vpn_connected
        isConnected=$?
        while [ $isConnected -ne 1 ]; do
                sleep 10
                test_vpn_connected
                isConnected=$?
        done
}

start(){
        log_daemon_msg "Starting $VPN_DESC" "$VPN_SERVICE"
        service $VPN_SERVICE start
        wait_vpn_connected
        test_vpn_actif
        isActif=$?
        if [ $isActif -ne 1 ]; then
                service $VPN_SERVICE stop
                return false
        else
                log_daemon_msg "Starting $SQUID_DESC" "$SQUID_SERVICE"
                service $SQUID_SERVICE start
                log_daemon_msg "Starting $BITTORRENT_DESC" "$BITTORRENT_SERVICE"
                service $BITTORRENT_SERVICE start
        fi
}

stop(){
        log_daemon_msg "Stopping $BITTORRENT_DESC" "$BITTORRENT_SERVICE"
        service $BITTORRENT_SERVICE stop
        log_daemon_msg "Stopping $SQUID_DESC" "$SQUID_SERVICE"
        service $SQUID_SERVICE stop
        log_daemon_msg "Stopping $VPN_DESC" "$VPN_SERVICE"
        service $VPN_SERVICE stop
        return 0
}

case "$1" in
    start)
        log_daemon_msg "Starting $DESC" "$NAME"
        if start ; then
                log_end_msg $?
        else
                log_end_msg $?
        fi
        ;;
    stop)
        log_daemon_msg "Stopping $DESC" "$NAME"
        if stop ; then
                log_end_msg $?
        else
                log_end_msg $?
        fi
        ;;
    restart)
        log_daemon_msg "Restarting $DESC" "$NAME"
        stop
        if start ; then
                log_end_msg $?
        else
                log_end_msg $?
        fi
        ;;
    status)
        service $VPN_SERVICE status
        val=$?
        service $SQUID_SERVICE status
        val=$(($?+$val))
        service $BITTORRENT_SERVICE status
        val=$(($?+$val))
        exit $val
        ;;
    check)
        test_vpn_connected
        isConnected=$?
        if [ $isConnected -eq 1 ]; then
                test_vpn_actif
                isActif=$?
                if [ $isActif -ne 1 ]; then
                        stop
                        start
                fi
        else
                stop
                start
        fi;
        ;;
    *)
        echo "Usage: /etc/init.d/$NAME {start|stop|restart|status|check}"
        exit 3
        ;; 
esac

J'ai conscience qu'il y a des choses à améliorer, un jour peut-être...

Étiquettes

Commentaires