Script per la gestione della configurazione di una vpn wireguard
Cos'è wireguard e come si configura, lo sappiamo ma mi serviva qualcosa che mi permettesse di fare provisioning e deprovisioning dei certificati in maniera più semplice di quanto già si faccia con i tool disponibili nella userland, wg e wg-quick.
E questo è il motivo principale per cui è nato questo script.
Prende come riferimento una configurazione road warrior, il primo scenario di una topologia point-to-site, quella più comune.
Alcune considerazioni
Lo script definisce una workdir dove deposita tutte le configurazioni e i certificati. Le precondizioni sono legate alla presenza dei tool userland wg-tools (ovviamente) e di ufw. Vengono comunque verificate nello script.
Altra semplificazione è data dal fatto di allocare una /24 come rete wireguard. 254 host per una rete domestica sono più che sufficienti.
L'allocazione degli ip viene fatta con un progressivo memorizzato nel file 'ip_renew'. Gli ip dei certificati revocati vengono mantenuti in un file, 'ip_release'. Quando si crea un nuovo certificato, si controlla prima che ci sia un ip da recuperare in ip_release. Se la lista è vuota si ricorre al progressivo.
La creazione della configurazione lato server passa da due file di configurazione: una configurazione globale ottenuta tracciando l'evoluzione iniziale in termini di aggiunta o rimozione dei client peer e destinata al rilascio in esecuzione. L'altra contenente la sola configurazione del server e usata unicamente per la rigenerazione della configurazione globale a partire dai peer esistenti.
L'operazione di init è propedeutica per addclient, removeclient, rebuild, deploy, share.
Le operazioni a disposizione sono:
- init: inizializza la configurazione di un server wireguard
- addclient: crea il certificato lato client e aggiunge il relativo peer sulla configurazione del server
- removeclient: cancella il certificato lato client e rimuove il relativo peer sulla configurazione del server
- rebuild: rigenera la configurazione del server usando i certificati client esistenti
- deploy: finalizza la configurazione riavviando il servizio con le nuove impostazioni
- share: crea i qr-code per i certificati client da utilizzare nelle configurazioni
Per tutto il resto, i commenti nel codice sono abbastanza esplicativi.
Lo script
#!/bin/bash
# Questo è il file di configurazione globale, i dati sono fittizi.
# Può essere mantenuto nello script o nella home dell'utente
# (es. $HOME/.config/wg_man.conf) e importato
# con 'source' all'inizio.
WG_INTERFACE=wg0
PHYSICAL_INTERFACE=eth0
NETWORK="192.168.15.0"
SUBNET_MASK="24"
SERVER_IP_ADDRESS="192.168.15.1"
DNS="1.1.1.1"
LISTEN_PORT="51820"
ENDPOINT=wireguard.nodns.net:51820"
WORKDIR="$HOME/wireguard"
check_dependency() {
if test -e /usr/sbin/ufw; then
if test -e /usr/bin/wg; then
return 0
else
return 2
fi
else
return 1
fi
}
is_init() {
test -e ${WORKDIR}/conf.d/server_wg.conf && return 0 || return 1
}
# Genera la chiave privata
gen_priv_key() {
wg genkey | tee ${WORKDIR}/keys/$1_privkey
}
# Genera la pre-shared key
gen_client_psk() {
# Evita il warning sui permessi del file che devono essere 600
umask 077
wg genpsk | tee ${WORKDIR}/psk/$1_psk
}
# Rilascio di un nuovo indirizzo ip. Questa funziona viene invocata da
# addclient().
#
# I primi 3 ottetti li ottengo dalla variabile globale NETWORK.
# L'ultimo ottetto, relativo all'host, lo ricavo dalla lista degli ip
# riutilizzabili ("ip_release").
#
# Se non è vuota, prelevo il primo e lo rimuovo dalla lista. Se è vuota,
# prelevo l'ultimo ottetto da "ip_renew" e lo incremento per il successivo
# assegnamento.
ip_renew() {
# Selezioni i primi 3 ottetti del network
SUB_IP=$(echo ${NETWORK}|awk -F "." '{print $1"."$2"."$3}')
if [[ ! -e ${WORKDIR}/conf.d/ip_release || ! -s ${WORKDIR}/conf.d/ip_release ]]; then
# Genero il nuovo ip (Incremento di 1 l'ultimo ottetto)
IP=$(cat ${WORKDIR}/conf.d/ip_renew)
echo $((++IP)) > ${WORKDIR}/conf.d/ip_renew
# Restituisco i 3 ottetti + il quarto
#echo -n ${SUB_IP};cat ${WORKDIR}/conf.d/ip_renew
else
IP=$(head -n 1 ${WORKDIR}/conf.d/ip_release)
sed -i "1d" ${WORKDIR}/conf.d/ip_release
fi
echo -n ${SUB_IP}"."${IP}
}
# Riallocazione di un indirizzo ip. Questa funzione viene invocata da
# removeclient().
# Aggiungo l'ultimo ottetto alla lista degli ip riallocabili.
# @par 1: ultimo ottetto.
ip_release() {
echo $1|cut -d"." -f 4 >> ${WORKDIR}/conf.d/ip_release
}
# Aggiunge il nuovo peer al file di configurazione del server
# @par 1: CLIENT_PUBLIC_KEY
# @par 2: CLIENT_PSK
# @par 3: CLIENT_IP_ADDRESS
# @par 4: CLIENT_NAME
add_peer_to_server() {
cat >> ${WORKDIR}/conf.d/server_wg.conf << EOF
# $4
[Peer]
PublicKey = $1
PresharedKey = $2
AllowedIPs = $3/32
PersistentKeepalive = 25
EOF
}
# Inizializza la configurazione del server wireguard.
#
# Questa operazione può essere fatta una sola volta a meno che non si
# cancelli tutta la workdir.
#
# Verranno creati due file di configurazione: server_wg.conf e
# server_wg_raw.conf
#
# Il primo conterrà la configurazione completa del server e dei peer,
# verrà aggiornato ad ogni modifica dei client (aggiunta o rimozione) e
# verrà usato per il deploy della configurazione.
#
# Il secondo conterrà la configurazione del solo server e verrà usato
# solo per rigenerare il primo file di configurazione.
init() {
# Controllo che wireguard sia almeno abilitato
echo -n "Inserire la password di Amministratore: "; read -s PASSWORD
sudo -k
if echo $PASSWORD|sudo -S echo "got a root" > /dev/null; then
if ! sudo systemctl is-enabled --quiet wg-quick@${WG_INTERFACE}; then
echo -en "\nAttivazione servizio wireguard... "
#sudo systemctl enable --quiet wg-quick@${WG_INTERFACE}
echo "fatto."
fi
sudo -k
#Verifico che queste cartelle esistano. Se lo sono, non faccio nulla, se no le creo.
echo -n "Creazione workdir... "
[[ ! -e ${WORKDIR}/conf.d || ! -e ${WORKDIR}/psk || ! -e ${WORKDIR}/keys ]] \
&& { mkdir -p ${WORKDIR}/{conf.d,keys,psk}; } \
|| { echo "inizializzazione già effettuata."; exit; }
echo "fatto."
# Genero la chiave privata per il server
echo -n "Creazione chiave private del server..."
SERVER_PRIV_KEY=$(gen_priv_key "server_wg")
echo "fatto."
# Genero il file di configurazione
echo -n "Inizializzazione del server... "
cat > ${WORKDIR}/conf.d/server_wg.conf << EOF
[Interface]
Address = ${SERVER_IP_ADDRESS}/${SUBNET_MASK}
# SaveConfig = true
PreUp = ufw route allow in on ${WG_INTERFACE} out on ${PHYSICAL_INTERFACE} log from ${NETWORK}/${SUBNET_MASK} to 0.0.0.0/0
PostDown = ufw route delete allow in on ${WG_INTERFACE} out on ${PHYSICAL_INTERFACE} log from ${NETWORK}/${SUBNET_MASK} to 0.0.0.0/0
# IP masquerading
PreUp = iptables -t nat -A POSTROUTING -o ${PHYSICAL_INTERFACE} -s ${NETWORK}/${SUBNET_MASK} -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o ${PHYSICAL_INTERFACE} -s ${NETWORK}/${SUBNET_MASK} -j MASQUERADE
# IP filtering
PreUp = ufw allow in on ${PHYSICAL_INTERFACE} log to 0.0.0.0/0 app Wireguard
PostDown = ufw delete allow in on ${PHYSICAL_INTERFACE} log to 0.0.0.0/0 app Wireguard
ListenPort = ${LISTEN_PORT}
PrivateKey = ${SERVER_PRIV_KEY}
EOF
echo "fatto."
cp ${WORKDIR}/conf.d/server_wg.conf ${WORKDIR}/conf.d/server_wg_raw.conf
# Inizializza l'erogatore di ip
echo 1 > ${WORKDIR}/conf.d/ip_renew
else
echo "Sono necessarie le credenziali di amministrazione per eseguire questo comando."
exit 1
fi
}
# Genera un certificato per il client composto da:
# * chiave privata del client
# * chiave pre-condivisa
# * client ip
# * chiave pubblica del server
# * nome simbolico del client
#
# Dopo la creazione e il salvataggio in <WORKDIR>/conf.d, verrà aggiunto
# il relativo peer nella configurazione del server (server_wg.conf).
# @par_1 @par_2 @par_3...: lista di nomi relativi ai certificati da creare.
add_client() {
if is_init; then
# Se non c'è alcun input, esco.
if [[ $# -lt 1 ]]; then
echo -e "Devi inserire almeno un nome."
exit 1
else
# Per ogni nome presente in input, creo una configurazione
# client e l'aggiungo alla configurazione del server.
for CLIENT_NAME in "$@"; do
echo -n "Creazione certificato per il client \"${CLIENT_NAME}\"... "
# Controllo se la configurazione esista già discriminando
# in base al nome del file.
if [[ -e ${WORKDIR}/conf.d/client_${CLIENT_NAME}.conf ]]; then
echo "già esistente."
else
# Creo la configurazione del client
CLIENT_PRIV_KEY=$(gen_priv_key ${CLIENT_NAME})
CLIENT_PUBLIC_KEY=$(echo ${CLIENT_PRIV_KEY} | wg pubkey)
CLIENT_PSK=$(gen_client_psk ${CLIENT_NAME})
CLIENT_IP_ADDRESS=$(ip_renew)
SERVER_PUBLIC_KEY=$(grep PrivateKey ${WORKDIR}/conf.d/server_wg.conf | cut -d " " -f 3 | tr -d " " | wg pubkey)
CLIENT_NAME_UPPER=$(echo ${CLIENT_NAME} | tr '[:lower:]' '[:upper:]')
cat > ${WORKDIR}/conf.d/client_${CLIENT_NAME}.conf << EOF
# ${CLIENT_NAME_UPPER}
[Interface]
Address = ${CLIENT_IP_ADDRESS}/${SUBNET_MASK}
DNS = ${DNS}
PrivateKey = ${CLIENT_PRIV_KEY}
[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
PresharedKey = ${CLIENT_PSK}
AllowedIPs = 0.0.0.0/0
Endpoint = ${ENDPOINT}
PersistentKeepalive = 25
EOF
echo "fatto."
echo -n "Aggiunta peer \"${CLIENT_NAME}\" al file di configurazione del server... "
# Aggiungo il peer alla configurazione del server.
add_peer_to_server ${CLIENT_PUBLIC_KEY} ${CLIENT_PSK} ${CLIENT_IP_ADDRESS} ${CLIENT_NAME_UPPER}
echo -e "fatto.\n"
fi
done
fi
else
echo "È necessario inizializzare il server con 'wg_init.sh init'."
echo "L'operazione richiesta non sarà completata"
fi
}
# Rimuove file di configurazioni, chiavi e pre-shared key realtive a file
# dati in input.
# Rimuove inoltre il peer dal file di configurazione server_wg.conf.
# @par_1 @par_2 @par_3...: lista di nomi relativi ai certificati da rimuovere.
remove_client() {
if is_init; then
if [[ $# -lt 1 ]]; then
echo -e "Devi inserire almeno un nome."
exit 1
else
for CLIENT_NAME in "$@"; do
echo -n "Rimozione client \"${CLIENT_NAME}\"... "
if [[ ! -e ${WORKDIR}/conf.d/client_${CLIENT_NAME}.conf ]]; then
echo "non esistente."
else
# Estraggo l'ultimo ottetto dell'ip del client.
IP=$(grep "Address" ${WORKDIR}/conf.d/client_${CLIENT_NAME}.conf|cut -d " " -f 3|tr -d " "|cut -d "/" -f 1)
# Lo inserisco nella lista degli ip riallocabili.
ip_release ${IP}
# Cancello file di configurazione, chiave e psk del client.
rm ${WORKDIR}/conf.d/client_${CLIENT_NAME}.conf ${WORKDIR}/keys/${CLIENT_NAME}_privkey ${WORKDIR}/psk/${CLIENT_NAME}_psk
echo "terminata con successo."
echo -n "Rimozione peer \"${CLIENT_NAME}\" dal file di configurazione del server... "
# Rimuovo il peer dalla configurazione del server.
CLIENT_NAME_UPPER=$(echo ${CLIENT_NAME} | tr '[:lower:]' '[:upper:]')
sed -i "/# ${CLIENT_NAME_UPPER}/,+6d" ${WORKDIR}/conf.d/server_wg.conf
echo -e "terminata con successo.\n"
fi
done
fi
else
echo "È necessario inizializzare il server con 'wg_init.sh init'."
echo "L'operazione richiesta non sarà completata"
fi
}
rebuild() {
if is_init; then
# Rigenero il file di configurazione del server da server_wg_raw.conf.
echo -n "Ripristino del file di configurazione del server... "
cat ${WORKDIR}/conf.d/server_wg_raw.conf > ${WORKDIR}/conf.d/server_wg.conf
echo "fatto."
# Per ogni file di configurazione client, aggiungo il relativo peer
# nel file di configurazione server_wg.conf.
for FILE_CONF in ${WORKDIR}/conf.d/client_*.conf; do
CLIENT_PUBLIC_KEY=$(grep "PrivateKey" ${FILE_CONF}|cut -d " " -f 3|tr -d " " | wg pubkey)
CLIENT_PSK=$(grep "PresharedKey" ${FILE_CONF}|cut -d " " -f 3|tr -d " ")
CLIENT_IP_ADDRESS=$(grep "Address" ${FILE_CONF}|cut -d " " -f 3|tr -d " "|cut -d "/" -f 1)
CLIENT_NAME_UPPER=$(grep "#" ${FILE_CONF} | cut -d " " -f 2)
echo -n "Aggiunta peer \"$(echo ${CLIENT_NAME_UPPER} | tr '[:upper:]' '[:lower:]')\"... "
add_peer_to_server ${CLIENT_PUBLIC_KEY} ${CLIENT_PSK} ${CLIENT_IP_ADDRESS} ${CLIENT_NAME_UPPER}
echo -e "fatto."
done
else
echo "È necessario inizializzare il server con 'wg_init.sh init'."
echo "L'operazione richiesta non sarà completata"
fi
}
# Avvia il server wireguard con la nuova configurazione
deploy() {
if is_init; then
echo -n "Inserire la password di Amministratore: "; read -s PASSWORD
sudo -k
if echo $PASSWORD|sudo -S echo "got a root" > /dev/null; then
# Stoppo il servizio wireguard
echo -ne "\nStop wireguard... "
sudo systemctl stop wg-quick@${WG_INTERFACE}
echo "fatto."
# Aggiorno la configurazione
echo -n "Copia della nuova configurazione... "
sudo cp ${WORKDIR}/conf.d/server_wg.conf /etc/wireguard/${WG_INTERFACE}
echo "fatto."
# Riavvio il servizio wireguard
echo -n "Riavvio wireguard... "
sudo systemctl start wg-quick@${WG_INTERFACE}
echo "fatto."
# Revoco i privilegi di amministrazione
sudo -k
else
echo "Sono necessarie le credenziali di amministrazione per eseguire questo comando."
exit 1
fi
else
echo "È necessario inizializzare il server con 'wg_init.sh init'."
echo "L'operazione richiesta non sarà completata"
fi
}
# Crea il qr-code della configurazione del client nella cartella corrente.
share() {
if is_init; then
if [[ $# -lt 1 ]]; then
echo -e "Devi inserire almeno un nome."
exit 1
else
for CLIENT_NAME in "$@"; do
echo -n "Creazione qr-code per il client \"${CLIENT_NAME}\"... "
qrencode -r ${WORKDIR}/conf.d/client_${CLIENT_NAME}.conf -o qrcode_client_${CLIENT_NAME}.jpg
echo "fatto."
done
fi
else
echo "È necessario inizializzare il server con 'wg_init.sh init'."
echo "L'operazione richiesta non sarà completata"
fi
}
help() {
[[ $1 != "" && $1 != "help" ]] && echo -e "Comando inesistente."
cat<<EOF
Usa come: ./wg_man.sh [command] ARG
dove:
[command]
init : Inizializza la configurazione del server.
addclient <lista nomi> : Aggiunge i client indicati in <lista nomi>.
removeclient <lista nomi>: Rimuove i client indicati in <lista nomi>.
rebuild : Ricostruisce il file di configurazione
del server partendo dalle configurazioni
di tutti i client registrati.
deploy : Riavvia il server wireguard con l'ultima
configurazione disponibile.
share <lista nomi> : Genera i qr-code relativi ai client indicati in <lista nomi>.
help : Stampa questa pagina di help.
ESEMPI:
INiZIALIZZA IL SERVER WIREGUARD
wg_man.sh init
AGGIUNGE I CLIENT 'macbook_pro', 'mobile'
wg_man.sh addclient macbook_pro mobile
RIMUOVE I CLIENT 'iphone_luca', 'workstation'
wg_man.sh removeclient iphone_luca workstation
RICOSTRUISCE LA CONFIGURAZIONE DEL SERVER WIREGUARD
wg_man.sh rebuild
ATTIVA LA NUOVA CONFIGURAZIONE
wg_man.sh deploy
GENERA I QRCODE PER 'pc_ufficio', 'laptop'
wg_man.sh share pc_ufficio laptop
EOF
}
main() {
case $1 in
init ) init ;;
addclient ) shift; add_client "$@" ;;
removeclient ) shift; remove_client "$@" ;;
rebuild ) rebuild ;;
deploy ) deploy ;;
share ) shift; share "$@" ;;
* ) help ;;
esac
}
check_dependency
case $? in
"0" ) main $* ;;
"1" ) echo -e "UFW non presente. Installare UFW."; exit 1 ;;
"2" ) echo -e "wireguard-tools non presenti. È necessario installarli."; exit 1 ;;
esac