webhosting by: WebSupport.sk                                             UnlimitedHosting | CustomHosting | FreeWeb.sk

OpenVPN vždy a všade – 2. časť

bwpow's picture

V tejto časti si ukážeme skripty, ktoré nám umožnia spútať obrovskú silu, ktorou OpenVPN disponuje. Oproti prvej časti v nej nájdete menej beletrie a viac konkrétnych technických informácií.

V prvej časti sme si ukázali, ako rozložiť záťaž medzi viac procesov a zabezpečiť, aby sa klient dokázal pripojiť aj z veľmi reštriktívnych podmienok. Druhá časť bude zameraná na samotné ovládanie správania OpenVPN servera pomocou jednoduchých skriptov napísaných v bashi. Keďže skripty sú pomerne rozsiahle a niektoré funkcie v nich obstarávajú rutinné záležitosti, prípadne sú zduplikované, v texte budú vynechané. Na konci článku nájdete odkaz na stiahnutie balíčka s plnými verziami skriptov a taktiež s ukážkovým konfiguračným súborom, ktorý budem používať na demonštráciu.

Minule sme zaviedli niekoľko podmienok, ktoré na systém kladieme a prvé dve sme vyriešili. Zvyšné už nebudem preberať osobitne, ale spojím ich do jednej konkrétnej situácie, kvôli ktorej som vlastne celé toto nasadenie riešil. Ide o telefónny server (SIP – Asterisk), ktorý navyše slúži aj na jednoduchú výmenu súborov (Samba a FTP) a streamovanie obrazu a zvuku. Používatelia sa chcú na neho pripájať šifrovane, pričom ide o nasledujúce zariadenia:

  • Notebooky a desktopy s MS Windows od XP po 7 (originálny OpenVPN klient)
  • Macbooky s Mac OS X (Tunnelblick)
  • Notebooky s GNU/Linuxom – Debian, CentOS a iné (softvér z repozitárov)
  • Celé siete skryté za rôznymi OpenWRT routrami (softvér z repozitára OpenWRT)
  • Rôzne iOS zariadenia – iPhone, iPad a aj iPod (softvér z cydie)

Pripájanie nových klientov ide skoro úplne mimo mňa, takže generovanie certifikátov neprichádza v úvahu. Taktiež nie je možné akokoľvek upravovať softvér, ktorý používajú. Celé to teda musí fungovať jednoducho, so softvérom, ktorý je bežne k dispozícii. Proces inštalácie spočíva len v nainštalovaní softvéru, nakopírovaní zopár súborov a možno vygenerovaní vhodného loginu a hesla, no aj to vždy priamo na klientskom zariadení. Na server nie je potrebné vôbec siahať a nič v ňom kvôli novým klientom nastavovať.

Tieto podmienky dosť sťažujú identifikáciu jednotlivých klientov. Všetci sa totiž pripájajú rovnakým certifikátom. Tu je možno dobré sa na chvíľu zastaviť a podotknúť, že situácia, kedy viac klientov má ten istý certifikát (alebo akékoľvek autentizačné údaje) neznižuje bezpečnosť jednotlivých pripojení. OpenVPN využíva SSL, ktoré si pre každé jedno spojenie vytvára úplne nezávislé kľúče a teda klienti nemajú možnosť navzájom si odpočúvať a dešifrovať komunikáciu keď je už raz nadviazaná. Jediný problém je len pri samotnej prvotnej autentizácii sa serveru, ak by sme chceli klientovi prideliť jedinečnú IP adresu alebo mu umožniť nejaký exkluzívny prístup k zdrojom. Našim cieľom je však zabezpečiť pripojenie proti odpočúvaniu, čo je splnené. Na SIP a iné služby sa klient autentizuje dodatočne, už cez šifrované pripojenie. Hlavné nebezpečenstvo vzniká len vtedy, ak sa jeden klient nečakane odpojí a druhý následne získa jeho IP adresu VPN zakončenia. Tomu ale dokážeme zabrániť.

OpenVPN okrem certifikátu podporuje ďalšie formy autentizácie. Pre nás zaujímavou bude autentizácia pomocou loginu a hesla. Túto formu podporuje každá verzia klienta, no mnohé odmietajú prečítať údaje zo súboru a vyžadujú ich zadanie používateľom. Záleží to od toho, ako bol softvér skompilovaný, keďže toto chovanie ovplyvňuje jediná direktíva v hlavičkovom súbore. Originálny softvér pre Windows načítanie loginu a hesla zo súboru neumožňuje, no napríklad ten v OpenWRT áno. Ako som spomínal, rekompilácia neprichádza v úvahu, musíme si teda vystačiť s tým, čo máme už hotové. Preto som pre naše nasadenie počítal s tým, že niektorí klienti sa budú hlásiť jedinečným loginom a heslo a iní nie. Mojim cieľom bolo to, že ak niekto login pošle, nech dostane stále tú istú IP adresu a ak nepošle, nech dostane IP adresu, ktorú už veľmi dávno nikto iný nepoužil a nemá ju rezervovanú ani jeden z loginových klientov. K tomuto správaniu sa ešte vrátim pri komentovaní samotných skriptov.

Ako som už spomínal, klientov môže byť veľmi veľa. Výber vhodnej topológie bol teda jednoduchý. OpenVPN ponúka momentálne tri:

  • net30 – kde každý klient má vyhradenú podsieť /30, obsadí teda až 4 IP adresy
  • p2p – dnes už zastaralá, navyše nefungujúca na Windows klientoch
  • subnet – každý klient má jednu IP adresu z podsiete, jediná vhodná voľba

Problém s topológiou subnet však nastáva pri starších klientoch. Žiaľ, niektorí ľudia majú z rôznych nepochopiteľných dôvodov stále OpenVPN verzie 2.0.X, ktorý túto topológiu nepodporoval. Konečná voľba preto bola použítie tap miesto tun, teda vytvorenie tunela na druhej vrstve OSI miesto tretej. Nevýhodou je, že po takomto tuneli behá príliš veľa bordelu (rôzne broadcasty), no pri našom použití si s tým nakoniec aj tak poradíme.

Poďme teda na praktickú ukážku. Keďže mojou filozofiou je robiť veci ľahko a rýchlo nasaditeľné, pripravil som si dva skripty, ktoré sa postarajú o všetko potrebné. Navyše, aby som nemusel do týchto skriptov zasahovať, všetky parametre čítajú z konfiguračného súboru, ktorý vyzerá nasledovne:

lockfile="/tmp/vpn-demo.lock"
datadir="/etc/openvpn/demo/data"
logdir="/var/log/vpn"
configdir="/etc/openvpn"
suffix="demo-vpn"
ip_start="172.16.1.0"
ip_end="172.16.255.255"
ip_mask="255.255.0.0"
port_begin="5131"
port_num="4"
protos="udp tcp"
fw_ports="53/udp 123/udp 161/udp 443/tcp 80/tcp 993/tcp 995/tcp 110/tcp 143/tcp 21/tcp"
fw_file="/tmp/demo-vpn.firewall"
if_name="br-demo"
if_ip="172.16.0.1"
local_if="eth0"
local_ip="169.254.0.162"
local_dns="demo-vpn.doma"

Poďme si prejsť jednotlivé parametre.

  • lockfile – určuje umiestnenie „uzamykacieho“ súboru, ktorý zabezpečuje to, aby skript nebežal súčasne viackrát
  • datadir – adresár, ktorý bude obsahovať súbory s aktuálnym stavom jednotlivých OpenVPN serverov a zároveň zoznam klientov, ktorí sa hlásia loginom a heslom
  • logdir – adresár, kam budú ukladané logy
  • configdir – adresár, kam sa majú vygenerovať konfiguračné súbory pre OpenVPN
  • suffix – identifikátor, ktorý bude pridaný ku konfiguračným súborom
  • ip_start, ip_end a ip_mask – rozsah IP adries, ktoré sa budú prideľovať jednotlivým klientom
  • port_begin, port_num, protos – port, od ktorého budú číslované počúvajúce OpenVPN serverové procesy, počet paralelne bežiacich serverov na jednom protokole a zoznam protokolov, na ktorých majú počúvať
  • fw_ports – zoznam jednotlivých portov, v tvare PORT/PROTOKOL
  • fw_file – súbor, do ktorého budú vygenerované pravidlá pre iptables
  • if_name, if_ip – názov bridge rozhrania, ktoré sa má v systéme vytvoriť a IP adresa, ktorú mu treba prideliť
  • local_if, local_ip, local_dns – lokálne rozhranie, na ktorom má byť OpenVPN server dostupný, jeho IP adresa a názov DNS

Toto sú všetky parametre, ktoré potrebujete nastaviť. O všetko ostatné sa postarajú dva bash skripty, ktoré sa u mňa nazývajú /usr/local/bin/rapidvpn-admin.sh a /usr/local/bin/rapidvpn-script.sh. Prvý skript je zodpovedný za generovanie konfiguračných súborov pre OpenVPN a vytváranie pravidiel pre iptables, ten druhý je volaný priamo zo serverových procesov OpenVPN a stará sa o vybavenie klientov.

Poďme teda na ten prvý:
Zadefinujeme si zoznam konfiguračných premenných a taktiež zoznam pomocných premenných používaných skriptom

CONFVARS="lockfile datadir logdir configdir suffix ip_start ip_end ip_mask port_begin port_num protos fw_ports fw_file if_name if_ip local_if local_ip local_dns"
GENVARS="proto protox port"
Ďalej nasleduje funkcia, ktorá generuje konfiguračný súbor pre OpenVPN pre konkrétny protokol a port
saveovpn()
{
cat > "$FNAME" << EOF
...
...
EOF
Samotný obsah konfiguračného súboru bude uvedený neskôr. Obsahoval premenné vo forme %názov%, ktoré nasledujúce riadky nahradia reálnymi hodnotami.
  for Vname in $CONFVARS $GENVARS; do
    eval "Vval=\$${Vname}"
    Vval=${Vval//\//\\/}
    sed -i "s/%${Vname}%/${Vval}/g" "$FNAME"
  done
  echo "" >> "$FNAME"
  for Vname in $CONFVARS $GENVARS; do
    eval "Vval=\$${Vname}"
    echo "setenv-safe $Vname \"$Vval\"" >> "$FNAME"
  done
}
myexit()
{
  if [ -z "$1" ]; then exit 1; fi
  if [ -n "$2" ]; then echo "ERROR: $2"; fi
  exit $1
}
Funkcia, ktorý overí, či prvý parameter je naozaj legitímna IPv4 adresa.
check_ip()
{
  if [ -z "$1" ] || [[ ! $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then myexit 1 "malformed IP '$1'"; fi
  read p1 p2 p3 p4 <<<$(IFS='.'; echo $1)
  if [ "$p1" -lt "0" ] || [ "$p1" -gt "255" ]; then myexit 1 "malformed IP '$1'"; fi
  if [ "$p2" -lt "0" ] || [ "$p2" -gt "255" ]; then myexit 1 "malformed IP '$1'"; fi
  if [ "$p3" -lt "0" ] || [ "$p3" -gt "255" ]; then myexit 1 "malformed IP '$1'"; fi
  if [ "$p4" -lt "0" ] || [ "$p4" -gt "255" ]; then myexit 1 "malformed IP '$1'"; fi
}
usage()
{
  echo "Usage: $0 COMMAND CONFIG"
  echo "COMMAND:"
  echo "  install - creates openvpn config files"
  echo "  firewall - generates firewall rules for load balancing"
  echo "CONFIG:"
  echo "  Path to .rapid config file"
  exit 1
}
Nasledujúca funkcia načíta obsah konfiguračného súboru, skontroluje, či sú v ňom naozaj všetky potrebné parametre. Ak sa názov niektorého parametru končí na dir, tak sa pokúsi tento adresár vytvoriť. Rovnako, ak sa niektorý parameter končí na _ip, tak skontroluje, či ide o legitímnu IPv4 adresu.
readconfig()
{
. "$1"
  for X in $CONFVARS; do
    eval "V=\$${X}"
    if [ -z "$V" ]; then myexit 1 "Missing variable '$X'"; fi
    if [ "${X:(-3)}" == "dir" ]; then
      if [ ! -d "$V" ]; then mkdir -p "$V" >/dev/null 2>/dev/null; fi
      if [ ! -d "$V" ]; then myexit 1 "Unable to create directory '$V'"; fi
    fi
    if [ "${X:(-3)}" == "_ip" ] || [ "${X:0:3}" == "ip_" ] ; then
      check_ip "$V"
    fi
  done
}
if [ -z "$1" ] || [ -z "$2" ] || [ ! -f "$2" ]; then usage; fi
CMD="$1"
readconfig "$2"
Ak bol skript zavolaný s parametrom install, vygeneruje konfiguračné súbory a skončí.
if [ "$CMD" == "install" ]; then
  port="$port_begin"
  for proto in $protos; do
    if [ "$proto" == "udp" ]; then protox="udp"
      else if [ "$proto" == "tcp" ]; then protox="tcp-server"
        else myexit 1 "Proto must be 'tcp' or 'udp'"; fi
    fi
    eval "port_$proto=\$port"
    for I in $(seq 1 $port_num); do
      FNAME="${configdir}/${port}-${proto}-${suffix}.conf"
      echo "$FNAME"
      saveovpn
      let "port=port+1"
    done
  done
  exit 0
fi
Ak bol skript zavolaný s parametrom firewall, vygeneruje pravidlá pre iptables, ktoré boli spomínané v prvej časti.
if [ "$CMD" == "firewall" ]; then
  FNAME="$fw_file"
  echo "#!/bin/bash" > "$FNAME"
  echo "# Generated on $(date)" >> "$FNAME"
  port="$port_begin"
  for proto in $protos; do
    if [ "$proto" != "udp" ] && [ "$proto" != "tcp" ]; then myexit 1 "Proto must be 'tcp' or 'udp'"; fi
    echo "" >> "$FNAME"
    echo "# INPUT $proto" >> "$FNAME"
    let "out_port=port+port_num-1"
    echo "\$IPTABLES -A INPUT -p $proto -i $local_if -d $local_ip --dport ${port}:${out_port} -m state --state NEW -j ACCEPT" >> "$FNAME"
    echo "" >> "$FNAME"
    echo "# DNAT $proto" >> "$FNAME"
    for X in $fw_ports; do
      read in_port in_proto <<<$(IFS='/'; echo $X)
      if [ "$in_proto" == "$proto" ] && [[ $in_port =~ ^[0-9]+$ ]]; then
        echo "# ${in_port}/${in_proto}" >> "$FNAME"
        for I in $(seq 1 $port_num); do
          let "out_port=port+I-1"
          if [ "$I" -gt "1" ]; then
            PROB=$(awk "BEGIN{print 1.0 / $I}")
            echo "\$IPTABLES -t nat -I PREROUTING -i $local_if -d $local_ip -p $proto --dport $in_port -m state --state NEW -m statistic --mode random --probability $PROB -j DNAT --to ${local_ip}:${out_port}" >> "$FNAME"
          else
            echo "\$IPTABLES -t nat -I PREROUTING -i $local_if -d $local_ip -p $proto --dport $in_port -m state --state NEW -j DNAT --to ${local_ip}:${out_port}" >> "$FNAME"
          fi
        done
      fi
    done
    let "port=port+port_num"
  done
  exit 0
fi
usage
Toto bol celý skript. Po jeho zavolaní s parametrom install máme v adresári configdir vygenerované konfiguračné súbory. Pozrime si jeden, hneď prvý:
local 169.254.0.162
port 5131
proto udp
dev tap5131
persist-tun
persist-key
ca /etc/openvpn/demo-vpn/ca.crt
cert /etc/openvpn/demo-vpn/cert.crt
key /etc/openvpn/demo-vpn/cert.key
dh /etc/openvpn/demo-vpn/dh2048.pem
Niektorým sa môže zdať, že v tomto konfiguračnom súbore chýba direktíva server, po ktorej väčšinou nasleduje lokálna IP adresa rozhrania a maska. Táto direktíva je ale iba alias pre celú štrúdľu príkazov, ktoré si môžete pozrieť v manuáli. My z týchto príkazov využijeme len dva nasledujúce. Chýbať bude hlavne ifconfig, ktorý definuje adresu lokálneho rozhrania. Dosiahneme tak, že rozhranie vytvorené serverom (v tomto prípade tap5131) zostane bez adresy a bude v stave down.
mode server
push "route-gateway 172.16.0.1"
;client-to-client
duplicate-cn
Keďže klienti budú mať rovnaký certifikát a autentizovať sa budú maximálne loginom a heslom, určíme, aby login bol použítý ako názov pre klienta. Samozrejme, nie všetci budú tieto informácie posielať, preto ich nebudeme vyžadovať.
username-as-common-name
auth-user-pass-optional
tls-server
keepalive 10 30
cipher AES-256-CBC
Aktuálny stav sa bude ukladať do datadir, pričom použijeme verziu výpisu s číslom 2. Je totiž dobre spracovateľná z bash skriptu.
status /etc/openvpn/demo-vpn/data/5131-udp.status
status-version 2
log-append /var/log/vpn/5131-udp-demo-vpn.log
mute-replay-warnings
mute 3
Mnohé z akcií bude „odobrovať“ náš druhý skript. Bezpečnosť skriptov musí byť nastavená na úroveň 3 kvôli tomu, aby OpenVPN odovzdalo všetky parametre skriptu cez environment. Táto úroveň umožňuje nastaviť úplne všetky parametre do prostredia, vrátane hesla. Nižšie úrovne to neumožňujú.
script-security 3
up "/usr/local/bin/rapidvpn-script.sh"
down-pre "/usr/local/bin/rapidvpn-script.sh"
client-connect "/usr/local/bin/rapidvpn-script.sh"
client-disconnect "/usr/local/bin/rapidvpn-script.sh"
auth-user-pass-verify "/usr/local/bin/rapidvpn-script.sh" via-env

Skript sa bude volať pri nasledúcich udalostiach:

  • up – spustí sa pri štarte OpenVPN servera, po vytvorení rozhrania
  • down-pre – spustí sa pri vypínaní OpenVPN, tesne predtým, ako sa zhodí rozhranie
  • client-connect – spustí sa pri pripájaní klienta, po tom, ako sa úspešne autentizuje
  • client-disconnect – spustí sa pri odpájaní klienta, či už vyžiadanom, alebo po timeoute
  • auth-user-pass-verify – spustí sa pri pokuse klienta o autentizáciu

Ďalej nasledujú už len skopírované parametre z konfiguračného súboru. Všetky budú taktiež vložené do prostredia skriptu, pričom k nim bude pridaný prefix OPENVPN_.

setenv-safe lockfile "/tmp/vpn-demo.lock"
setenv-safe datadir "/etc/openvpn/demo-vpn/data"
setenv-safe logdir "/var/log/vpn"
...
...
Takýchto súborov bolo vygenerovaných osem. Štyri pre UDP a štyri pre TCP. Všetky volajú skript /usr/local/bin/rapidvpn-script.sh, v ktorom sa vykonáva skoro všetka mágia. Poďme sa teda do neho pustiť:
run_trap()
{
  ret="$?"
  rm -f "$LOCKFILE"
  exit $ret
}
myexit()
{
  if [ -z "$1" ]; then exit 1; fi
  if [ -n "$2" ]; then echo "ERROR: $2"; fi
  exit $1
}
check_ip()
{
  if [ -z "$1" ] || [[ ! $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then myexit 1 "malformed IP"; fi
  read p1 p2 p3 p4 <<<$(IFS='.'; echo $1)
  if [ "$p1" -lt "0" ] || [ "$p1" -gt "255" ]; then myexit 1 "malformed IP"; fi
  if [ "$p2" -lt "0" ] || [ "$p2" -gt "255" ]; then myexit 1 "malformed IP"; fi
  if [ "$p3" -lt "0" ] || [ "$p3" -gt "255" ]; then myexit 1 "malformed IP"; fi
  if [ "$p4" -lt "0" ] || [ "$p4" -gt "255" ]; then myexit 1 "malformed IP"; fi
}
Niektorí klienti sa pripájajú s loginom a heslo, niektorí nie. Tých druhých teda nevieme od seba nijako efektívne odlíšiť. Aby sa nestalo to, že niekomu pridelíme adresu, ktorú pred krátkym časom používal niekto iný, budeme si pamätať adresu naposledy použitú a každému novému klientovi jednoducho pridelíme ďalšiu v poradí. Ak sa dostaneme na koniec, začneme opäť od začiatku. Ak budeme mať dosť veľký rozsah (napríklad /16), môžeme predpokladať, že pri stovkách klientov bude trvať dostatočne dlho, kým sa pretočíme.
next_ip()
{
  read p1 p2 p3 p4 <<<$(IFS='.'; echo $1)
  let "p4=p4+1"
  if [ "$p4" -gt "255" ]; then let "p4=0";let "p3=p3+1"
    if [ "$p3" -gt "255" ]; then let "p3=0";let "p2=p2+1"
      if [ "$p2" -gt "255" ]; then let "p2=0";let "p1=p1+1"
        if [ "$p1" -gt "255" ]; then myexit 1 "out of IP addresses"; fi
      fi
    fi
  fi
  IP="${p1}.${p2}.${p3}.${p4}"
  if [ "$IP" == "$IP_END" ]; then
    IP="$IP_START"
  fi
}
Predtým, ako môžeme vygenerovanú adresu skutočne niekomu prideliť, potrebujeme skontrolovať, či nie je momentálne používaná. Prezrieme teda stavové súbory, ktoré generujú jednotlivé serverové procesy. Medzi nimi je zamiešaný aj súbor obsahujúci trvalé rezervácie pre klientov používajúcich login a heslo. Preto automaticky týmto krokom vyradíme aj tieto.
check_if_used()
{
  rIP=${1//./\\.}
  for F in $(ls ${DATADIR}/*.status); do
    D=$(grep -E "^CLIENT_LIST,[-a-zA-Z0-9\._:]+,[-a-zA-Z0-9\._:]+,$rIP(,.*)?\$" $F | wc -l)
    if [ "$D" -gt "0" ]; then return 1; fi
  done
  return 0
}
Keďže si skript medzi jednotlivými spusteniami musí zapamätať naposledy pridelenú IP adresu, ukladá si ju do súboru. Ak tento súbor neexistuje, začne hneď prvou adresou určenou v konfiguračnom súbore. Následne skúša generovať nové adresy a skončí, ak nájde prvú nepoužívanú. Táto časť kódu nie je napísaná veľmi optimálne, no v reálnom nasadení to vždy bežalo dostatočne rýchlo a preto som nemal zatiaľ dôvod to zlepšovať.
find_next_unused_ip()
{
  if [ ! -f "$LASTIP_FNAME" ]; then
    IP="$IP_START"
  else
    IP=$(head -n1 "$LASTIP_FNAME")
    check_ip "$IP"
  fi
  let "OK=128"
  while [ "$OK" -gt "0" ]; do
    next_ip "$IP"
    if check_if_used "$IP"; then
      let "OK=-1"
    else
      let "OK=OK-1"
    fi
  done
  if [ "$OK" -eq "0" ]; then myexit 1 "could not find any free IP address"; fi
}
Ak sa klient autentizuje loginom a heslom, musíme sa pokúsiť nájsť jeho rezerváciu. Ak je prvým parametrom tejto funkcie reťazec checkpass, overuje sa aj zhodnosť hesla, inak sa vyhľadá len záznam podľa loginu.
find_userpass_ip()
{
  if [ ! -f "$CLIENTS_FNAME" ]; then return 1; fi
  read r_pass r_ip <<<$(grep "^CLIENT_LIST,$username," $CLIENTS_FNAME | head -n1 | awk -F',' '{print $3" "$4}')
  if [ -z "$r_pass" ] && [ -z "$r_ip" ]; then return 1; fi
  if [ "$1" == "checkpass" ] && [ "$r_pass" != "$password" ]; then myexit 1 "wrong password"; fi
  check_ip "$r_ip"
  IP="$r_ip"
  return 0
}
if [ -z "$dev" ] || [ -z "$script_type" ] || [ -z "$OPENVPN_lockfile" ] || [ -z "$OPENVPN_datadir" ] || [ -z "$OPENVPN_ip_start" ] || [ -z "$OPENVPN_ip_end" ] || [ -z "$OPENVPN_ip_mask" ] || [ -z "$OPENVPN_if_name" ] || [ -z "$OPENVPN_if_ip" ]; then
  echo "Same basic environment variables are missing!"
  exit 2
fi
LOCKFILE="$OPENVPN_lockfile"
DATADIR="$OPENVPN_datadir"
IP_START="$OPENVPN_ip_start"
IP_END="$OPENVPN_ip_end"
IP_MASK="$OPENVPN_ip_mask"
IF_NAME="$OPENVPN_if_name"
IF_IP="$OPENVPN_if_ip"
if [ ! -d "$DATADIR" ]; then
  echo "Data directory '$DATADIR' not found!"
  exit 3
fi
Nastavíme názvy súborov, v ktorých máme uloženú naposledy použitú IPv4 adresu a zoznam rezervácií.
LASTIP_FNAME="${DATADIR}/lastip.txt"
CLIENTS_FNAME="${DATADIR}/clients.status"
check_ip "$IP_START"
check_ip "$IP_END"
check_ip "$IP_MASK"
check_ip "$IF_IP"
Môže sa ľahko stať, že sa pokúsi súčasne prihlásiť viac klientov. Mnohí ľudia zvyknú zanedbávať túto možnosť, či už programujú normálne programy alebo web stránky. Ale ako sa hovorí, náhoda je sviňa a už aj pri zopár klientoch sa mi stalo, že sa proste pripojili skoro súčasne. Preto sa postaráme o to, aby skript nebežal viackrát súčasne. Ak sa nám podarí úspešne získať zámok, rovno si chytíme aj niektoré signály, konkrétne INT, TERM a EXIT, aby sme pri skončení vždy naisto vymazali súbor so zámkom. Vždy pri ukončení skriptu sa teda zavolá funkcia run_trap, ktorá bola definovaná úplne na začiatku.
let "OK=2"
while [ "$OK" -gt "0" ]; do
  if ( set -o noclobber; echo "$$" > "$LOCKFILE") 2> /dev/null; then
    trap 'run_trap' INT TERM EXIT
    let "OK=-1"
  else
    let "OK=OK-1"
    sleep 1
  fi
done
if [ "$OK" -eq "0" ]; then myexit 1 "Could not acquire lock!"; fi
Server OpenVPN oznámi spustenému skriptu, o akú udalosť sa jedná pomocou premennej script_type. Ako prvú teda vybavíme udalosť up.
if [ "$script_type" == "up" ]; then
Najprv skontrolujeme, či je vytvorený bridge a ak nie je, tak ho vytvoríme a priradíme mu IPv4 adresu. Pri písaní som predpokladal, že skript je spustený s potrebnými právami a taktiež sú k dispozícii všetky potrebné binárky. Tieto veci sú špecifické pre každý systém.
  /sbin/ifconfig $IF_NAME >/dev/null 2>/dev/null
  if [ "$?" -gt "0" ]; then
    /usr/sbin/brctl addbr $IF_NAME
    /sbin/ifconfig $IF_NAME $IF_IP netmask $IP_MASK
  fi
Ďalej zobudíme OpenVPN rozhranie a priložíme ho do bridge-u.
  /sbin/ifconfig $dev up
  /usr/sbin/brctl addif $IF_NAME $dev
  exit 0
fi
Pri vypínaní iba odoberieme rozhranie z bridge-u.
if [ "$script_type" == "down" ]; then
  /sbin/ifconfig $dev down
  /usr/sbin/brctl delif $IF_NAME $dev
  exit 0
fi
Pri pripájaní nového klienta (toto prebieha už po autentizácii) je skriptu predaný ešte jeden parameter, názov súboru, kam treba uložiť konfiguráciu pre klienta. Ak ste niekedy používali OpenVPN tak, že pre každého klienta ste vytvárali súbor z CN jeho certifikátu v ccd adresári, ide o to isté. Klientovi takto môžeme prideliť konkrétnu IP adresu a push-núť rôzne parametre.
if [ "$script_type" == "client-connect" ] && [ -n "$1" ]; then
Ak klient poskytol login, rovno mu pridelíme adresu, ktorú má uloženú v jeho rezervácii.
  if [ -n "$username" ]; then
    if find_userpass_ip; then
      echo "ifconfig-push $IP $IP_MASK" > "$1"
    else
      myexit 1 "did not find username, probably internal error"
    fi
  else
Ak je to klient bez loginu, nájdeme nasledujúcu nepoužitú IPv4 adresu, pridelíme mu ju a zároveň si ju poznačíme do súboru ako naposledy použitú.
    find_next_unused_ip
    echo "ifconfig-push $IP $IP_MASK" > "$1"
    echo "$IP" > "$LASTIP_FNAME"
  fi
  exit 0
fi
Pri odpájaní klienta momentálne nespravíme nič. Môžete si sem pridať nejakú akciu, napríklad vygenerovanie nejakého eventu, zapísanie do logu a iné.
if [ "$script_type" == "client-disconnect" ]; then
  exit 0
fi
Posledná činnosť, ktorú musíme ošetriť, je autentizácia. Tá sa zavolá vždy, aj v prípade, že klient neposiela login a heslo.
if [ "$script_type" == "user-pass-verify" ]; then
Ak predsa len nejaký login poslal, overíme, či už existuje v zozname. Zároveň overujeme aj heslo. Ak napríklad niekto pošle existujúci login, ale heslo je nesprávne, je žiadúce, aby bol takýto klient rovno odmietnutý. Ak však ešte v zozname nefiguruje, nájdeme pre neho najbližšiu nepoužitú IPv4 adresu a do zoznamu ho pridáme. Týmto zabezpečíme, že každý klient si môže sám určiť login a heslo a od tohto momentu bude dostávať stále tú istú adresu. My však nemusíme robiť nič na strane servera.
  if [ -n "$username" ]; then
    if [[ ! $username =~ ^[-a-zA-Z0-9\._:]+$ ]] || [[ ! $password =~ ^[-a-zA-Z0-9\._:]+$ ]]; then myexit 1 "bad characters in username or password"; fi
    if find_userpass_ip "checkpass"; then
      exit 0
    else
      find_next_unused_ip
      echo "CLIENT_LIST,$username,$password,$IP" >> "$CLIENTS_FNAME"
      echo "$IP" > "$LASTIP_FNAME"
    fi
  fi
  exit 0
fi
exit 1
Je nutné ešte poznamenať, že ak tento skript skončí s návratovou hodnotou inou než 0, server OpenVPN to bude považovať za chybu. Pri autentizácii a pripájaní odmietne a odpojí klienta. Týmto spôsobom teda môžeme určovať, kto sa môže pripojiť a kto nie.

Poďme si teda všetko zhrnúť. Po spustení príkazu prvého skriptu budú vytvorené konfigurácie:

[root@rapidvpn-server ~]# /usr/local/bin/rapidvpn-admin.sh install /etc/openvpn/demo.rapid
/etc/openvpn/5131-udp-demo-vpn.conf
/etc/openvpn/5132-udp-demo-vpn.conf
/etc/openvpn/5133-udp-demo-vpn.conf
/etc/openvpn/5134-udp-demo-vpn.conf
/etc/openvpn/5135-tcp-demo-vpn.conf
/etc/openvpn/5136-tcp-demo-vpn.conf
/etc/openvpn/5137-tcp-demo-vpn.conf
/etc/openvpn/5138-tcp-demo-vpn.conf

Po spustení servisu OpenVPN vznikne niekoľko rozhraní a jeden bridge:
[root@rapidvpn-server ~]# ip a show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:bf:02:9c brd ff:ff:ff:ff:ff:ff
    inet 169.254.0.162/26 brd 169.254.0.191 scope global eth0
21: tap5131: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether c2:81:c4:e0:29:6b brd ff:ff:ff:ff:ff:ff
22: br-demo: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 02:a9:81:78:80:b6 brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.1/16 brd 172.16.255.255 scope global br-demo
23: tap5132: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether 1a:a6:04:87:42:5e brd ff:ff:ff:ff:ff:ff
24: tap5133: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether ea:bb:e9:84:59:a7 brd ff:ff:ff:ff:ff:ff
25: tap5134: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether 5e:11:c2:8b:94:c9 brd ff:ff:ff:ff:ff:ff
26: tap5135: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether 9a:4e:65:4d:f0:0f brd ff:ff:ff:ff:ff:ff
27: tap5136: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether ce:c5:59:35:67:8c brd ff:ff:ff:ff:ff:ff
28: tap5137: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether 02:a9:81:78:80:b6 brd ff:ff:ff:ff:ff:ff
29: tap5138: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 100
    link/ether 86:c2:08:bd:a1:04 brd ff:ff:ff:ff:ff:ff
pričom tieto tap rozhrania sú všetky zapojené do bridge-u:
[root@rapidvpn-server ~]# brctl show
bridge name    bridge id              STP enabled    interfaces
br-demo        8000.02a9817880b6      no              tap5131
                                                        tap5132
                                                        tap5133
                                                        tap5134
                                                        tap5135
                                                        tap5136
                                                        tap5137
                                                        tap5138

Potrebujeme ešte vytvoriť konfiguračný súbor pre klienta, ktorý môže vyzerať nasledovne:
client
dev tap
server-poll-timeout 4
resolv-retry infinite
remote 169.254.0.162 53 udp
remote 169.254.0.162 123 udp
remote 169.254.0.162 161 udp
remote 169.254.0.162 443 tcp
remote 169.254.0.162 80 tcp
remote 169.254.0.162 993 tcp
remote 169.254.0.162 995 tcp
remote 169.254.0.162 110 tcp
remote 169.254.0.162 143 tcp
remote 169.254.0.162 21 tcp
nobind
persist-key
persist-tun
ca demo-vpn/ca.crt
cert demo-vpn/cert.crt
key demo-vpn/cert.key
auth-user-pass demo-vpn/userpass.txt
tls-client
tls-remote vpn.server.demo
ns-cert-type server
keepalive 10 30
cipher AES-256-CBC

Do súboru demo-vpn/userpass.txt je potrebné uložiť dva riadky, prvý obsahujúci login a druhý heslo. Ak sa v logu následne objaví hláška
Sorry, 'Auth' password cannot be read from a file
tak vieme, že si musíme vystačiť bez autentizácie a príslušný riadok v konfiguračnom súbore zakomentovať (alebo vymazať).

Po úspešnom prihlásení dostane v našom prípade prvý klient IP adresu 172.16.1.1, druhý 172.16.1.2, a tak ďalej. Ak sa prihlási niekto s loginom a heslom, jeho údaje sa zapíšu do súboru a v budúcnosti dostane opäť tú istú adresu.

Keďže ide o bridge, všetci klienti budú vidieť mnohé rámce vysielane broadcastom, napríklad arp requesty. Ak to chceme zamedziť, môžeme použiť ebtables. Komunikácia na tretej a vyššej vrstve však už nepôjde priamo cez bridge, ale príde z daného rozhrania do systému, ktorý sa o jej preposielanie musí postarať. Ak teda chceme povoliť komunikáciu medzi pripojenými klientmi, musíme povoliť routovanie a vo firewalle nastaviť FORWARD so vstupom i výstupom na tomto jednom bridge rozhraní. Takto vieme všetko pekne filtrovať podľa potreby.

Samozrejme, s týmto sa dajú robiť ďalšie pekné veci. Zapájať do bridge-u ďalšie tunely na vzdialené servery, hrať sa s rôznymi formami autentizácie, vyvolávať rôzne eventy a veľa veľa iných zaujímavých vecí. To už ale nechám na vás. Fantázii sa medze nekladú.

Dúfam, že vás tento blog inšpiroval k hraniu sa s OpenVPN a jeho širokému nasadzovaniu kde sa len dá. Ak máte akékoľvek otázky, vložte ich do diskusie pod túto druhú časť. Ak by ste mali záujem o nasadenie OpenVPN vo vašej firme, viete, na koho sa treba obrátiť :)

Doležité odkazy:

Average rating
(4 votes)

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
matej's picture

Re: OpenVPN vždy a všade – 2. časť

Snazil by som sa L2 zo siete odstranovat co najviac, a nie ho este pridavat. Cez pomalsie linky (mobilne pripojenie z hornej-dolnej) chodi vsetko pomaly uz same o sebe, s mnozstvom inych floodujucich klientov na sieti to musi byt desne. Donutit upgradnut na novsiu verziu kde by L3 behalo v pohode. Plus aj nevidim dovod aby klientske masiny kecali napriamo, SIP a podobne sluzby co by teoreticky mohli mat radi p2p rezim vybavis cez server, tak ci tak to tadial lezie.

Co sa tyka bezpecnosti, ten spolocny certifikat skor ci neskor moze priniest skazu. Ako ho budes vymienat pre N klientov, ked napr. niekto strati notebook/usb kluc/bude inak kompromitovany? Mozno by v mene Bezpecnosti stalo za to vyrobit web portal kde by siel cert lahko vygenerovat z browsera, aj ked nejakej admin resp. user interakcii sa asi nevyhnes. To len tak myslienka na vylepsovanie.
To samovytvaranie kont sa mi paci, aj ked vazba na LDAP by bola asi viac korporatna. Este mozno pridat admin lock option, aby si po uvodnej instalacii nemohli useri vytvarat haluzne "konta" bez vedomia admina - plus sa vyhnes omylom zle napisanym username.

bwpow's picture

Re: OpenVPN vždy a všade – 2. časť

Ved u mna ani nekecaju. Vsetku komunikaciu medzi klientami som jednoducho urezal cez ebtables. Cize prakticky je toto nasadenie ekvivalentne L3.

Tato siet je doslova myslena ako "verejna". Klientov do nej pridavaju ludia sami (teda pripajaju svojich znamych, atd.), cize presne tento ucel to splna. Islo cisto o to, aby existovalo sifrovane spojenie medzi sluzbami a klientom, navyse aby to preslo aj cez restriktivne siete.

Ak OpenVPN nasadzujem u realnych klientov, vzdy vyuzivam osobitne certifikaty.

Prisiel som, videl som, hmm...

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
webhosting by: WebSupport.sk UnlimitedHosting | CustomHosting | FreeWeb.sk