--- apiVersion: v1 kind: ConfigMap metadata: name: amneziawg-scripts data: firewall-up.sh: | #!/usr/bin/env bash set -euo pipefail PORT="${1:-5847}" VPN_CIDR="${2:-10.8.0.0/16}" external_interface() { ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}' } ensure_insert_rule() { local table_args=() if [ "${1:-}" = "-t" ]; then table_args=("$1" "$2") shift 2 fi local chain="$1" shift if ! iptables "${table_args[@]}" -C "${chain}" "$@" >/dev/null 2>&1; then iptables "${table_args[@]}" -I "${chain}" 1 "$@" fi } delete_rule() { local table_args=() if [ "${1:-}" = "-t" ]; then table_args=("$1" "$2") shift 2 fi local chain="$1" shift while iptables "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do true done } ensure_append_rule() { local table_args=() if [ "${1:-}" = "-t" ]; then table_args=("$1" "$2") shift 2 fi local chain="$1" shift if ! iptables "${table_args[@]}" -C "${chain}" "$@" >/dev/null 2>&1; then iptables "${table_args[@]}" -A "${chain}" "$@" fi } EXT_IF="$(external_interface || true)" if [ -z "${EXT_IF}" ]; then EXT_IF="$(ip route show default | awk '{print $5; exit}')" fi if [ -z "${EXT_IF}" ]; then echo "Unable to detect external interface" exit 1 fi sysctl -w net.ipv4.ip_forward=1 delete_rule INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP ensure_insert_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT ensure_insert_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP ensure_append_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT ensure_append_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT ensure_append_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT ensure_append_rule -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE firewall-down.sh: | #!/usr/bin/env bash set -euo pipefail PORT="${1:-5847}" VPN_CIDR="${2:-10.8.0.0/16}" external_interface() { ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}' } delete_rule() { local table_args=() if [ "${1:-}" = "-t" ]; then table_args=("$1" "$2") shift 2 fi local chain="$1" shift while iptables "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do true done } EXT_IF="$(external_interface || true)" if [ -z "${EXT_IF}" ]; then EXT_IF="$(ip route show default | awk '{print $5; exit}')" fi if [ -n "${EXT_IF}" ]; then delete_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT delete_rule -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE fi delete_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP delete_rule INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP delete_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT delete_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT delete_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT run.sh: | #!/usr/bin/env bash set -euo pipefail SERVER_CONFIG="/etc/amnezia/server/awg0.conf" CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}" RUNTIME_CONFIG="/run/amnezia/awg0.conf" SYNC_CONFIG="/run/amnezia/awg0.sync.conf" STATUS_FILE="/run/amnezia/reload-status" RELOAD_INTERVAL="${AMNEZIAWG_RELOAD_INTERVAL:-10}" cleanup() { if awg show awg0 >/dev/null 2>&1; then awg-quick down "${RUNTIME_CONFIG}" || ip link delete awg0 || true fi } render_config() { mkdir -p "$(dirname "${RUNTIME_CONFIG}")" local tmp_config="${RUNTIME_CONFIG}.tmp" cp "${SERVER_CONFIG}" "${tmp_config}" chmod 0600 "${tmp_config}" local clients_found=0 for client_config in "${CLIENTS_DIR}"/*; do [ -f "${client_config}" ] || continue [ -s "${client_config}" ] || continue printf '\n' >> "${tmp_config}" cat "${client_config}" >> "${tmp_config}" clients_found=1 done if [ "${clients_found}" = "0" ]; then echo "No client peer configs found in ${CLIENTS_DIR}; starting without peers" fi mv "${tmp_config}" "${RUNTIME_CONFIG}" chmod 0600 "${RUNTIME_CONFIG}" } client_config_hash() { { for client_config in "${CLIENTS_DIR}"/*; do [ -f "${client_config}" ] || continue sha256sum "${client_config}" done } | sha256sum | awk '{print $1}' } write_reload_status() { local state="${1}" local hash="${2:-}" local applied_at_ms="" if [ "${state}" = "applied" ]; then applied_at_ms="$(($(date +%s) * 1000))" fi mkdir -p "$(dirname "${STATUS_FILE}")" { printf 'state=%s\n' "${state}" printf 'hash=%s\n' "${hash}" printf 'applied_at_ms=%s\n' "${applied_at_ms}" } > "${STATUS_FILE}.tmp" mv "${STATUS_FILE}.tmp" "${STATUS_FILE}" } apply_live_config() { render_config awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}" chmod 0600 "${SYNC_CONFIG}" awg syncconf awg0 "${SYNC_CONFIG}" } watch_client_config() { local last_hash="${1}" while true; do sleep "${RELOAD_INTERVAL}" & wait "$!" || return 0 local current_hash current_hash="$(client_config_hash)" if [ "${current_hash}" = "${last_hash}" ]; then continue fi echo "Detected AmneziaWG client peer config change; applying with awg syncconf" if apply_live_config; then last_hash="${current_hash}" write_reload_status applied "${current_hash}" awg show awg0 || true else echo "ERROR: failed to hot-reload AmneziaWG client peer config" >&2 write_reload_status error "${current_hash}" fi done } trap cleanup EXIT trap 'exit 0' TERM INT initial_hash="$(client_config_hash)" render_config cleanup awg-quick up "${RUNTIME_CONFIG}" awg show awg0 || true write_reload_status applied "${initial_hash}" watch_client_config "${initial_hash}" client-secret-sync.sh: | #!/usr/bin/env bash set -euo pipefail CLIENT_SECRET="${AMNEZIAWG_CLIENT_SECRET:-amneziawg-clients}" CLIENT_SECRET_KEY="${AMNEZIAWG_CLIENT_SECRET_KEY:-peers.conf}" CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}" PEERS_FILE="${CLIENTS_DIR}/peers.conf" SYNC_INTERVAL="${AMNEZIAWG_CLIENT_SECRET_SYNC_INTERVAL:-5}" NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}" write_empty_once() { mkdir -p "${CLIENTS_DIR}" if [ ! -f "${PEERS_FILE}" ]; then : > "${PEERS_FILE}" chmod 0600 "${PEERS_FILE}" fi } sync_once() { mkdir -p "${CLIENTS_DIR}" local tmp_file="${PEERS_FILE}.tmp" local encoded="" if ! encoded="$(kubectl get secret "${CLIENT_SECRET}" -n "${NAMESPACE}" -o "go-template={{ index .data \"${CLIENT_SECRET_KEY}\" }}" 2>/dev/null)"; then echo "WARN: failed to read Secret ${NAMESPACE}/${CLIENT_SECRET}; keeping current peers" >&2 write_empty_once return 0 fi if [ -n "${encoded}" ]; then printf '%s' "${encoded}" | base64 -d > "${tmp_file}" else : > "${tmp_file}" fi chmod 0600 "${tmp_file}" if [ -f "${PEERS_FILE}" ] && cmp -s "${tmp_file}" "${PEERS_FILE}"; then rm -f "${tmp_file}" return 0 fi mv "${tmp_file}" "${PEERS_FILE}" echo "Synced AmneziaWG client peers from Secret ${NAMESPACE}/${CLIENT_SECRET}:${CLIENT_SECRET_KEY}" } if [ "${1:-}" = "once" ]; then sync_once exit 0 fi while true; do sync_once || true sleep "${SYNC_INTERVAL}" done status-patch.sh: | #!/usr/bin/env bash set -euo pipefail STATUS_FILE="/run/amnezia/reload-status" PATCH_INTERVAL="${AMNEZIAWG_STATUS_PATCH_INTERVAL:-5}" NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}" : "${POD_NAME:?POD_NAME is required}" last_file_hash="" patch_status() { local state="unknown" local hash="" local applied_at_ms="" # The file is generated by run.sh and contains only shell assignments. # shellcheck disable=SC1090 source "${STATUS_FILE}" kubectl patch pod "${POD_NAME}" -n "${NAMESPACE}" --type merge -p "{\"metadata\":{\"annotations\":{\"amnezia-fellow.hexor.cy/client-secret-reload-status\":\"${state}\",\"amnezia-fellow.hexor.cy/client-secret-applied-at-ms\":\"${applied_at_ms}\",\"amnezia-fellow.hexor.cy/client-secret-applied-hash\":\"${hash}\"}}}" } while true; do if [ -f "${STATUS_FILE}" ]; then file_hash="$(sha256sum "${STATUS_FILE}" | awk '{print $1}')" if [ "${file_hash}" != "${last_file_hash}" ]; then patch_status || true last_file_hash="${file_hash}" fi fi sleep "${PATCH_INTERVAL}" done