Updates to subnet generation and client creation (#1782)

* refactor(core): allow any subnet and netmask

* fix(scripts): prevent adding more clients than the subnet allows

* fix(scripts): correctly remove leading zeros from ipv6 quartets

* refactor(core): new probabilistic subnet generation with fallback to other RFC1918 subnets
This commit is contained in:
Orazio 2023-11-23 11:54:07 +01:00 committed by 4s3ti
parent 20d3a4ccd4
commit 850e665642
No known key found for this signature in database
GPG key ID: AC2D3B898F96BC51
6 changed files with 339 additions and 106 deletions

54
scripts/ipaddr_utils.sh Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env bash
decIPv4ToDot() {
local a b c d
a=$((($1 & 4278190080) >> 24))
b=$((($1 & 16711680) >> 16))
c=$((($1 & 65280) >> 8))
d=$(($1 & 255))
printf "%s.%s.%s.%s\n" $a $b $c $d
}
dotIPv4ToDec() {
local original_ifs=$IFS
IFS='.'
read -r -a array_ip <<< "$1"
IFS=$original_ifs
printf "%s\n" $((array_ip[0] * 16777216 + array_ip[1] * 65536 + array_ip[2] * 256 + array_ip[3]))
}
dotIPv4FirstDec() {
local decimal_ip decimal_mask
decimal_ip=$(dotIPv4ToDec "$1")
decimal_mask=$((2 ** 32 - 1 ^ (2 ** (32 - $2) - 1)))
printf "%s\n" "$((decimal_ip & decimal_mask))"
}
dotIPv4LastDec() {
local decimal_ip decimal_mask_inv
decimal_ip=$(dotIPv4ToDec "$1")
decimal_mask_inv=$((2 ** (32 - $2) - 1))
printf "%s\n" "$((decimal_ip | decimal_mask_inv))"
}
decIPv4ToHex() {
local hex
hex="$(printf "%08x\n" "$1")"
quartet_hi=${hex:0:4}
quartet_lo=${hex:4:4}
# Removes leading zeros from quartets, purely for aesthetic reasons
# Source: https://stackoverflow.com/a/19861690
leading_zeros_hi="${quartet_hi%%[!0]*}"
leading_zeros_lo="${quartet_lo%%[!0]*}"
printf "%s:%s\n" "${quartet_hi#"${leading_zeros_hi}"}" "${quartet_lo#"${leading_zeros_lo}"}"
}
cidrToMask() {
# Source: https://stackoverflow.com/a/20767392
set -- $((5 - (${1} / 8))) \
255 255 255 255 \
$(((255 << (8 - (${1} % 8))) & 255)) \
0 0 0
shift "${1}"
echo "${1-0}.${2-0}.${3-0}.${4-0}"
}

View file

@ -14,6 +14,12 @@ INDEX="/etc/openvpn/easy-rsa/pki/index.txt"
# shellcheck disable=SC1090
source "${setupVars}"
if [ ! -r /opt/pivpn/ipaddr_utils.sh ]; then
exit 1
fi
# shellcheck disable=SC1091
source /opt/pivpn/ipaddr_utils.sh
# shellcheck disable=SC2154
userGroup="${install_user}:${install_user}"
@ -162,16 +168,6 @@ keyPASS() {
cd pki || exit
}
cidrToMask() {
# Source: https://stackoverflow.com/a/20767392
set -- $((5 - (${1} / 8))) \
255 255 255 255 \
$(((255 << (8 - (${1} % 8))) & 255)) \
0 0 0
shift "${1}"
echo "${1-0}.${2-0}.${3-0}.${4-0}"
}
### Script
if [[ ! -f "${setupVars}" ]]; then
err "::: Missing setup vars file!"
@ -293,6 +289,35 @@ if [[ ! -d "${install_home}/ovpns" ]]; then
chmod 0750 "${install_home}/ovpns"
fi
# Exclude first, last and server addresses
# shellcheck disable=SC2154
MAX_CLIENTS="$((2 ** (32 - subnetClass) - 3))"
# shellcheck disable=SC2154
FIRST_IPV4_DEC="$(dotIPv4FirstDec "${pivpnNET}" "${subnetClass}")"
LAST_IPV4_DEC="$(dotIPv4LastDec "${pivpnNET}" "${subnetClass}")"
if [ "$(find /etc/openvpn/ccd -type f | wc -l)" -ge "${MAX_CLIENTS}" ]; then
echo "::: Can't add any more clients (max. ${MAX_CLIENTS})!"
exit 1
fi
# Find an unused address for the client IP
for ((ip = FIRST_IPV4_DEC + 2; ip <= LAST_IPV4_DEC - 1; ip++)); do
# find returns 0 if the folder is empty, so we create the 'ls -A [...]'
# exception to stop at the first static IP (10.8.0.2). Otherwise it would
# cycle to the end without finding and available octet.
# disabling SC2514, variable sourced externaly
ip_dot="$(decIPv4ToDot "${ip}")"
if [[ -z "$(ls -A /etc/openvpn/ccd)" ]] \
|| ! find /etc/openvpn/ccd -type f \
-exec grep -q "${ip_dot}" {} +; then
UNUSED_IPV4_DOT="${ip_dot}"
break
fi
done
#bitWarden
if [[ "${BITWARDEN}" =~ "2" ]]; then
useBitwarden
@ -469,33 +494,15 @@ if [[ "${iOS}" == 1 ]]; then
printf "========================================================\n\n"
fi
#disabling SC2514, variable sourced externaly
# shellcheck disable=SC2154
NET_REDUCED="${pivpnNET::-2}"
# Find an unused number for the last octet of the client IP
for i in {2..254}; do
# find returns 0 if the folder is empty, so we create the 'ls -A [...]'
# exception to stop at the first static IP (10.8.0.2). Otherwise it would
# cycle to the end without finding and available octet.
# disabling SC2514, variable sourced externaly
# shellcheck disable=SC2154
if [[ -z "$(ls -A /etc/openvpn/ccd)" ]] \
|| ! find /etc/openvpn/ccd -type f \
-exec grep -q "${NET_REDUCED}.${i}" {} +; then
COUNT="${i}"
echo -n "ifconfig-push ${NET_REDUCED}.${i} " >> /etc/openvpn/ccd/"${NAME}"
# The space after ${i} is important ------^!
cidrToMask "${subnetClass}" >> /etc/openvpn/ccd/"${NAME}"
# the end resuld should be a line like:
# ifconfig-push ${NET_REDUCED}.${i} ${subnetClass}
# ifconfig-push 10.205.45.8 255.255.255.0
break
fi
done
echo -n "ifconfig-push ${UNUSED_IPV4_DOT} " >> /etc/openvpn/ccd/"${NAME}"
# The space after ${UNUSED_IPV4_DOT} is important!
cidrToMask "${subnetClass}" >> /etc/openvpn/ccd/"${NAME}"
# the end resuld should be a line like:
# ifconfig-push ${UNUSED_IPV4_DOT} ${subnetClass}
# ifconfig-push 10.205.45.8 255.255.255.0
if [[ -f /etc/pivpn/hosts.openvpn ]]; then
echo "${NET_REDUCED}.${COUNT} ${NAME}.pivpn" >> /etc/pivpn/hosts.openvpn
echo "${UNUSED_IPV4_DOT} ${NAME}.pivpn" >> /etc/pivpn/hosts.openvpn
if killall -SIGHUP pihole-FTL; then
echo "::: Updated hosts file for Pi-hole"

View file

@ -4,9 +4,16 @@
### Constants
setupVars="/etc/pivpn/openvpn/setupVars.conf"
INDEX="/etc/openvpn/easy-rsa/pki/index.txt"
# shellcheck disable=SC1090
source "${setupVars}"
if [ ! -r /opt/pivpn/ipaddr_utils.sh ]; then
exit 1
fi
# shellcheck disable=SC1091
source /opt/pivpn/ipaddr_utils.sh
### Functions
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
@ -167,10 +174,8 @@ for ((ii = 0; ii < ${#CERTS_TO_REVOKE[@]}; ii++)); do
# Disabling SC2154 $pivpnNET sourced externally
# shellcheck disable=SC2154
# Grab the client IP address
NET_REDUCED="${pivpnNET::-2}"
STATIC_IP="$(grep -v "^#" /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}" \
| grep -w ifconfig-push \
| grep -oE "${NET_REDUCED}\.[0-9]{1,3}")"
| grep -w ifconfig-push | awk '{print $2}')"
rm -rf /etc/openvpn/ccd/"${CERTS_TO_REVOKE[ii]}"
# disablung warning SC2154, $install_home sourced externally

View file

@ -10,6 +10,12 @@ setupVars="/etc/pivpn/wireguard/setupVars.conf"
# shellcheck disable=SC1090
source "${setupVars}"
if [ ! -r /opt/pivpn/ipaddr_utils.sh ]; then
exit 1
fi
# shellcheck disable=SC1091
source /opt/pivpn/ipaddr_utils.sh
# shellcheck disable=SC2154
userGroup="${install_user}:${install_user}"
@ -109,6 +115,27 @@ fi
cd /etc/wireguard || exit
# Exclude first, last and server addresses
# shellcheck disable=SC2154
MAX_CLIENTS="$((2 ** (32 - subnetClass) - 3))"
if [ "$(wc -l configs/clients.txt | awk '{print $1}')" -ge "${MAX_CLIENTS}" ]; then
echo "::: Can't add any more clients (max. ${MAX_CLIENTS})!"
exit 1
fi
# shellcheck disable=SC2154
FIRST_IPV4_DEC="$(dotIPv4FirstDec "${pivpnNET}" "${subnetClass}")"
LAST_IPV4_DEC="$(dotIPv4LastDec "${pivpnNET}" "${subnetClass}")"
# Find an unused address for the client IP
for ((ip = FIRST_IPV4_DEC + 2; ip <= LAST_IPV4_DEC - 1; ip++)); do
if ! grep -q " ${ip}$" configs/clients.txt; then
UNUSED_IPV4_DEC="${ip}"
break
fi
done
if [[ -z "${CLIENT_NAME}" ]]; then
read -r -p "Enter a Name for the Client: " CLIENT_NAME
checkName
@ -122,28 +149,17 @@ wg genkey \
wg genpsk | tee "keys/${CLIENT_NAME}_psk" &> /dev/null
echo "::: Client Keys generated"
# Find an unused number for the last octet of the client IP
for i in {2..254}; do
if ! grep -q " ${i}$" configs/clients.txt; then
COUNT="${i}"
echo "${CLIENT_NAME} $(< keys/"${CLIENT_NAME}"_pub) $(date +%s) ${COUNT}" \
| tee -a configs/clients.txt > /dev/null
break
fi
done
# Disabling SC2154, variables sourced externaly
# shellcheck disable=SC2154
NET_REDUCED="${pivpnNET::-2}"
UNUSED_IPV4_DOT="$(decIPv4ToDot "${UNUSED_IPV4_DEC}")"
UNUSED_IPV4_HEX="$(decIPv4ToHex "${UNUSED_IPV4_DEC}")"
# shellcheck disable=SC2154
{
echo '[Interface]'
echo "PrivateKey = $(cat "keys/${CLIENT_NAME}_priv")"
echo -n "Address = ${NET_REDUCED}.${COUNT}/${subnetClass}"
echo -n "Address = ${UNUSED_IPV4_DOT}/${subnetClass}"
if [[ "${pivpnenableipv6}" == 1 ]]; then
echo ",${pivpnNETv6}${COUNT}/${subnetClassv6}"
echo ",${pivpnNETv6}${UNUSED_IPV4_HEX}/${subnetClassv6}"
else
echo
fi
@ -175,10 +191,10 @@ echo "::: Client config generated"
echo '[Peer]'
echo "PublicKey = $(cat "keys/${CLIENT_NAME}_pub")"
echo "PresharedKey = $(cat "keys/${CLIENT_NAME}_psk")"
echo -n "AllowedIPs = ${NET_REDUCED}.${COUNT}/32"
echo -n "AllowedIPs = ${UNUSED_IPV4_DOT}/32"
if [[ "${pivpnenableipv6}" == 1 ]]; then
echo ",${pivpnNETv6}${COUNT}/128"
echo ",${pivpnNETv6}${UNUSED_IPV4_HEX}/128"
else
echo
fi
@ -188,12 +204,15 @@ echo "::: Client config generated"
echo "::: Updated server config"
echo "${CLIENT_NAME} $(< keys/"${CLIENT_NAME}"_pub) $(date +%s) ${UNUSED_IPV4_DEC}" \
| tee -a configs/clients.txt > /dev/null
if [[ -f /etc/pivpn/hosts.wireguard ]]; then
echo "${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn" \
echo "${UNUSED_IPV4_DOT} ${CLIENT_NAME}.pivpn" \
| tee -a /etc/pivpn/hosts.wireguard > /dev/null
if [[ "${pivpnenableipv6}" == 1 ]]; then
echo "${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn" \
echo "${pivpnNETv6}${UNUSED_IPV4_HEX} ${CLIENT_NAME}.pivpn" \
| tee -a /etc/pivpn/hosts.wireguard > /dev/null
fi

View file

@ -6,6 +6,12 @@ setupVars="/etc/pivpn/wireguard/setupVars.conf"
# shellcheck disable=SC1090
source "${setupVars}"
if [ ! -r /opt/pivpn/ipaddr_utils.sh ]; then
exit 1
fi
# shellcheck disable=SC1091
source /opt/pivpn/ipaddr_utils.sh
### Functions
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
@ -100,8 +106,8 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do
fi
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
# Grab the least significant octed of the client IP address
COUNT="$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $4}')"
# Grab the decimal representation of the client IP address
IPV4_DEC="$(grep "^${CLIENT_NAME} " configs/clients.txt | awk '{print $4}')"
# The creation date of the client
CREATION_DATE="$(grep "^${CLIENT_NAME} " configs/clients.txt \
| awk '{print $3}')"
@ -111,7 +117,7 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do
# Then remove the client matching the variables above
sed \
-e "\#${CLIENT_NAME} ${PUBLIC_KEY} ${CREATION_DATE} ${COUNT}#d" \
-e "\#${CLIENT_NAME} ${PUBLIC_KEY} ${CREATION_DATE} ${IPV4_DEC}#d" \
-i configs/clients.txt
# Remove the peer section from the server config
@ -147,10 +153,11 @@ for CLIENT_NAME in "${CLIENTS_TO_REMOVE[@]}"; do
# Disabling SC2154, variable sourced externaly and may vary
# shellcheck disable=SC2154
if [[ -f /etc/pivpn/hosts.wireguard ]]; then
NET_REDUCED="${pivpnNET::-2}"
IPV4_DOT="$(decIPv4ToDot "${IPV4_DEC}")"
IPV4_HEX="$(decIPv4ToHex "${IPV4_DEC}")"
sed \
-e "\#${NET_REDUCED}.${COUNT} ${CLIENT_NAME}.pivpn#d" \
-e "\#${pivpnNETv6}${COUNT} ${CLIENT_NAME}.pivpn#d" \
-e "\#${IPV4_DOT} ${CLIENT_NAME}.pivpn#d" \
-e "\#${pivpnNETv6}${IPV4_HEX} ${CLIENT_NAME}.pivpn#d" \
-i /etc/pivpn/hosts.wireguard
if killall -SIGHUP pihole-FTL; then