Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions images/common/init_command.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
# OpenWISP common module init script
set -e
source utils.sh
. ./utils.sh

init_conf

Expand All @@ -24,26 +24,23 @@ elif [ "$MODULE_NAME" = 'postfix' ]; then
elif [ "$MODULE_NAME" = 'freeradius' ]; then
wait_nginx_services
if [ "$DEBUG_MODE" = 'False' ]; then
source docker-entrypoint.sh
. ./docker-entrypoint.sh
else
source docker-entrypoint.sh -X
. ./docker-entrypoint.sh -X
fi
elif [ "$MODULE_NAME" = 'openvpn' ]; then
if [[ -z "$VPN_DOMAIN" ]]; then exit; fi
if [ -z "$VPN_DOMAIN" ]; then exit; fi
. ./openvpn_utils.sh
wait_nginx_services
openvpn_preconfig
openvpn_config
openvpn_config_download
crl_download
echo "*/1 * * * * sh /openvpn.sh" | crontab -
(
crontab -l
echo "0 0 * * * sh /revokelist.sh"
) | crontab -
crontab /openvpn.crontab
crond
# Schedule send topology script only when
# network topology module is enabled.
if [ "$USE_OPENWISP_TOPOLOGY" == "True" ]; then
if [ "$USE_OPENWISP_TOPOLOGY" = "True" ]; then
init_send_network_topology
fi
# Supervisor is used to start the service because OpenVPN
Expand Down Expand Up @@ -93,7 +90,7 @@ elif [ "$MODULE_NAME" = 'celery' ]; then
${OPENWISP_CELERY_NETWORK_COMMAND_FLAGS}
fi

if [[ "$USE_OPENWISP_FIRMWARE" == "True" && "$USE_OPENWISP_CELERY_FIRMWARE" == "True" ]]; then
if [ "$USE_OPENWISP_FIRMWARE" = "True" ] && [ "$USE_OPENWISP_CELERY_FIRMWARE" = "True" ]; then
echo "Starting the 'firmware_upgrader' celery worker"
celery -A openwisp worker -l ${DJANGO_LOG_LEVEL} --queues firmware_upgrader \
-n firmware_upgrader@%h --logfile /opt/openwisp/logs/celery_firmware_upgrader.log \
Expand All @@ -104,7 +101,7 @@ elif [ "$MODULE_NAME" = 'celery' ]; then
tail -f /opt/openwisp/logs/*
elif [ "$MODULE_NAME" = 'celery_monitoring' ]; then
python services.py database redis dashboard
if [[ "$USE_OPENWISP_MONITORING" == "True" && "$USE_OPENWISP_CELERY_MONITORING" == 'True' ]]; then
if [ "$USE_OPENWISP_MONITORING" = "True" ] && [ "$USE_OPENWISP_CELERY_MONITORING" = 'True' ]; then
echo "Starting the 'monitoring' celery worker"
celery -A openwisp worker -l ${DJANGO_LOG_LEVEL} --queues monitoring \
-n monitoring@%h --logfile /opt/openwisp/logs/celery_monitoring.log \
Expand Down
64 changes: 0 additions & 64 deletions images/common/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -197,67 +197,3 @@ postfix_config() {
postmap /etc/aliases
newaliases
}

get_redis_value() {
local key="$1"
echo -en "GET $key\r\n" | nc redis 6379 | awk 'NR==2 {gsub(/\r/, ""); print}'
}

openvpn_preconfig() {
mkdir -p /dev/net
if [ ! -c /dev/net/tun ]; then
mknod /dev/net/tun c 10 200
fi
ip -6 route show default 2>/dev/null
if [ $? = 0 ]; then
echo "Enabling IPv6 Forwarding"
sysctl -w net.ipv6.conf.all.disable_ipv6=0 || echo "Failed to enable IPv6 support"
sysctl -w net.ipv6.conf.default.forwarding=1 || echo "Failed to enable IPv6 Forwarding default"
sysctl -w net.ipv6.conf.all.forwarding=1 || echo "Failed to enable IPv6 Forwarding"
fi
}

openvpn_config() {
# Fectch UUID and Key of the default VPN only if they
# are not already set. The user may override the UUID and Key
# by setting them in the environment variables to use deploy
# a different VPN server.
if [ -z "$UUID" ]; then
export UUID=$(get_redis_value "openwisp_default_vpn_uuid")
export KEY=$(get_redis_value "openwisp_default_vpn_key")
export CA_UUID=$(get_redis_value "openwisp_default_vpn_ca_uuid")
fi
}

openvpn_config_checksum() {
OFILE=$(curl --silent --insecure \
"${API_INTERNAL}/controller/vpn/checksum/${UUID}/?key=${KEY}")
export OFILE
NFILE=$(cat checksum)
export NFILE
}

openvpn_config_download() {
curl --silent --retry 10 --retry-delay 5 --retry-max-time 300 --insecure --output vpn.tar.gz \
"${API_INTERNAL}/controller/vpn/download-config/${UUID}/?key=${KEY}"
curl --silent --insecure --output checksum \
"${API_INTERNAL}/controller/vpn/checksum/${UUID}/?key=${KEY}"
tar xzf vpn.tar.gz
chmod 600 ./*.pem
}

crl_download() {
curl --silent --insecure --output revoked.crl \
"${DASHBOARD_INTERNAL}/admin/pki/ca/x509/ca/${CA_UUID}.crl"
}

init_send_network_topology() {
if [ -z "$TOPOLOGY_UUID" ]; then
export TOPOLOGY_UUID=$(get_redis_value "default_openvpn_topology_uuid")
export TOPOLOGY_KEY=$(get_redis_value "default_openvpn_topology_key")
fi
(
crontab -l
echo "*/$TOPOLOGY_UPDATE_INTERVAL * * * * TOPOLOGY_UUID=$TOPOLOGY_UUID TOPOLOGY_KEY=$TOPOLOGY_KEY sh /send-topology.sh"
) | crontab -
}
2 changes: 2 additions & 0 deletions images/openwisp_openvpn/openvpn.crontab
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*/1 * * * * sh /openvpn.sh
*/5 * * * * sh /revokelist.sh
13 changes: 10 additions & 3 deletions images/openwisp_openvpn/openvpn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
# This script will be called by cronjob to
# update OpenVPN configurations periodically.
cd /
source /utils.sh
. /utils.sh
. /openvpn_utils.sh

openvpn_config
openvpn_config_checksum
if ! openvpn_config_checksum; then
echo "Failed to fetch or validate OpenVPN checksum" >&2
exit 1
fi

if [ "${OFILE}" != "${NFILE}" ]; then
openvpn_config_download
if ! openvpn_config_download; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add coverage for the config integrity path too. The CRL regression test is useful, but we also need to simulate a downloaded config/checksum mismatch and assert that the old config remains in place, checksum remains unchanged, supervisorctl restart openvpn is not called, and an error is logged. I am sorry this was not specified in high detail before.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added config-path regression coverage in tests/runtests.py.

The new test covers:

  • checksum mismatch preserving the existing config/checksum and avoiding restart
  • missing local checksum triggering safe re-download and restart

The updated branch is green.

echo "Failed to download updated OpenVPN configuration" >&2
exit 1
fi
supervisorctl restart openvpn
fi
200 changes: 200 additions & 0 deletions images/openwisp_openvpn/openvpn_utils.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/bin/sh

get_redis_value() {
local key="$1"
printf 'GET %s\r\n' "$key" | nc -w 5 redis 6379 | awk 'NR==2 {gsub(/\r/, ""); print}'
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

openvpn_preconfig() {
mkdir -p /dev/net
if [ ! -c /dev/net/tun ]; then
mknod /dev/net/tun c 10 200
fi
if ip -6 route show default >/dev/null 2>&1; then
echo "Enabling IPv6 Forwarding"
sysctl -w net.ipv6.conf.all.disable_ipv6=0 || echo "Failed to enable IPv6 support"
sysctl -w net.ipv6.conf.default.forwarding=1 || echo "Failed to enable IPv6 Forwarding default"
sysctl -w net.ipv6.conf.all.forwarding=1 || echo "Failed to enable IPv6 Forwarding"
fi
}

openvpn_config() {
# Fetch UUID and Key of the default VPN only if they
# are not already set. The user may override the UUID and Key
# by setting them in the environment variables to use deploy
# a different VPN server.
if [ -z "$UUID" ]; then
UUID="$(get_redis_value "openwisp_default_vpn_uuid")"
KEY="$(get_redis_value "openwisp_default_vpn_key")"
CA_UUID="$(get_redis_value "openwisp_default_vpn_ca_uuid")"
export UUID KEY CA_UUID
fi
}

openvpn_config_checksum() {
local remote_checksum
local local_checksum

remote_checksum=$(curl --silent --show-error --fail --insecure \
"${API_INTERNAL}/controller/vpn/checksum/${UUID}/?key=${KEY}") || return 1
if [ -z "$remote_checksum" ]; then
return 1
fi

if [ -s checksum ]; then
local_checksum=$(cat checksum)
fi
if [ -z "$local_checksum" ]; then
local_checksum=""
fi

export OFILE="$remote_checksum"
export NFILE="$local_checksum"
}

openvpn_config_download() {
local tmp_tar
local tmp_checksum
local tmp_extract_dir
local expected_checksum
local actual_checksum

tmp_tar=$(mktemp /tmp/vpn-config.XXXXXX) || return 1
tmp_checksum=$(mktemp /tmp/vpn-checksum.XXXXXX) || {
rm -f "$tmp_tar"
return 1
}
tmp_extract_dir=$(mktemp -d /tmp/vpn-config-dir.XXXXXX) || {
rm -f "$tmp_tar" "$tmp_checksum"
return 1
}
trap 'rm -f "$tmp_tar" "$tmp_checksum"; rm -rf "$tmp_extract_dir"' EXIT HUP INT TERM

curl --silent --show-error --fail --retry 10 --retry-delay 5 --retry-max-time 300 \
--insecure --output "$tmp_tar" \
"${API_INTERNAL}/controller/vpn/download-config/${UUID}/?key=${KEY}" || return 1
test -s "$tmp_tar" || return 1

curl --silent --show-error --fail --insecure --output "$tmp_checksum" \
"${API_INTERNAL}/controller/vpn/checksum/${UUID}/?key=${KEY}" || return 1
test -s "$tmp_checksum" || {
echo "Downloaded OpenVPN checksum is empty" >&2
return 1
}

expected_checksum=$(awk 'NF {print $1; exit}' "$tmp_checksum" | tr -d '\r')
if [ -z "$expected_checksum" ]; then
echo "Downloaded OpenVPN checksum is empty" >&2
return 1
fi

case "${#expected_checksum}" in
32)
actual_checksum=$(md5sum "$tmp_tar" | awk '{print $1}') || return 1
;;
40)
actual_checksum=$(sha1sum "$tmp_tar" | awk '{print $1}') || return 1
;;
64)
actual_checksum=$(sha256sum "$tmp_tar" | awk '{print $1}') || return 1
;;
128)
actual_checksum=$(sha512sum "$tmp_tar" | awk '{print $1}') || return 1
;;
*)
echo "Unsupported OpenVPN checksum format: $expected_checksum" >&2
return 1
;;
esac
if [ "$actual_checksum" != "$expected_checksum" ]; then
echo "Downloaded OpenVPN config checksum mismatch" >&2
return 1
fi

tar xzf "$tmp_tar" -C "$tmp_extract_dir" || return 1
set -- "$tmp_extract_dir"/*.pem
test -e "$1" || return 1
chmod 600 "$@" || return 1
cp -R "$tmp_extract_dir"/. / || return 1

mv "$tmp_checksum" checksum || return 1
rm -f "$tmp_tar"
rm -rf "$tmp_extract_dir"
trap - EXIT HUP INT TERM
}

crl_download_to() {
local output_path="${1:-revoked.crl}"
curl --silent --show-error --fail --retry 10 --retry-delay 5 --retry-max-time 300 \
--insecure --output "$output_path" \
"${DASHBOARD_INTERNAL}/admin/pki/ca/x509/ca/${CA_UUID}.crl" || return 1
test -s "$output_path"
}

crl_download() {
local tmp_crl

tmp_crl=$(mktemp /tmp/revoked.crl.XXXXXX) || return 1
if ! crl_download_to "$tmp_crl"; then
rm -f "$tmp_crl"
return 1
fi
if [ ! -s "$tmp_crl" ]; then
rm -f "$tmp_crl"
return 1
fi
mv "$tmp_crl" revoked.crl || {
rm -f "$tmp_crl"
return 1
}
}

crl_download_if_changed() {
local tmp_crl
tmp_crl=$(mktemp /tmp/revoked.crl.XXXXXX) || return 2
trap 'rm -f "$tmp_crl"' EXIT HUP INT TERM

if ! crl_download_to "$tmp_crl"; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we treat an empty CRL as a failed download here? curl --fail only catches HTTP errors. If the dashboard returns 200 with an empty body, this function will promote that empty file at line 150 and restart OpenVPN, which can temporarily disable revocation checks. I would add a test -s "$tmp_crl" check before comparing/promoting, return 2 on empty, keep the existing revoked.crl, and add a small case to the regression test.

trap - EXIT HUP INT TERM
rm -f "$tmp_crl"
return 2
fi
if [ ! -s "$tmp_crl" ]; then
trap - EXIT HUP INT TERM
rm -f "$tmp_crl"
return 2
fi

if [ ! -f revoked.crl ] || ! cmp -s "$tmp_crl" revoked.crl; then
mv "$tmp_crl" revoked.crl || return 2
trap - EXIT HUP INT TERM
return 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi

trap - EXIT HUP INT TERM
rm -f "$tmp_crl"
return 1
}

init_send_network_topology() {
case "$TOPOLOGY_UPDATE_INTERVAL" in
'' | *[!0-9]* | 0)
echo "Skipping topology cron: invalid TOPOLOGY_UPDATE_INTERVAL: $TOPOLOGY_UPDATE_INTERVAL" >&2
return 0
;;
esac

if [ -z "$TOPOLOGY_UUID" ]; then
TOPOLOGY_UUID="$(get_redis_value "default_openvpn_topology_uuid")"
TOPOLOGY_KEY="$(get_redis_value "default_openvpn_topology_key")"
export TOPOLOGY_UUID TOPOLOGY_KEY
fi
if [ -z "$TOPOLOGY_UUID" ] || [ -z "$TOPOLOGY_KEY" ]; then
echo "Skipping topology cron: missing TOPOLOGY_UUID or TOPOLOGY_KEY" >&2
return 0
fi
(
crontab -l
echo "*/$TOPOLOGY_UPDATE_INTERVAL * * * * TOPOLOGY_UUID=$TOPOLOGY_UUID TOPOLOGY_KEY=$TOPOLOGY_KEY sh /send-topology.sh"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
) | crontab -
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
13 changes: 10 additions & 3 deletions images/openwisp_openvpn/revokelist.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
# This script will be called by cronjob to
# update CRL periodically.
cd /
source /utils.sh
. /utils.sh
. /openvpn_utils.sh

openvpn_config
crl_download
supervisorctl restart openvpn
crl_download_if_changed
crl_status=$?

if [ "$crl_status" -eq 0 ]; then
supervisorctl restart openvpn
elif [ "$crl_status" -eq 2 ]; then
echo "Failed to download CRL, keeping existing revoked.crl" >&2
fi
Loading