working ig
This commit is contained in:
1 parent
b834cd06f3
commit
c14be296a8
13 files changed
+242
-24
No files matched your search
+1
-1
@@ -3,7 +3,7 @@ FROM debian:bookworm-slim
|
||||
# libpam-radius-auth: PAM -> RADIUS bridge to the Duo Auth Proxy on 127.0.0.1
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
openvpn libpam-radius-auth iproute2 procps ca-certificates \
|
||||
openvpn libpam-radius-auth iproute2 procps ca-certificates socat curl jq \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY pam-openvpn /etc/pam.d/openvpn
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
# OpenVPN learn-address hook: registers VPN clients in Technitium DNS.
|
||||
# add/update <ip> <cn> -> A <cn>.$VPN_DNS_ZONE + PTR, TTL 60
|
||||
# delete <ip> -> removes both records
|
||||
#
|
||||
# CRITICAL: this script must ALWAYS exit 0 — a non-zero exit makes
|
||||
# OpenVPN reject the client's address. DNS registration is best-effort;
|
||||
# it must never take the VPN down with it.
|
||||
|
||||
OP="$1"; ADDR="$2"; CN="${3:-}"
|
||||
|
||||
API="http://127.0.0.1:5380/api"
|
||||
[ -n "${TECHNITIUM_ADMIN_PASSWORD:-}" ] || exit 0
|
||||
[ -n "${VPN_DNS_ZONE:-}" ] || exit 0
|
||||
|
||||
log() { echo "learn-address: $*"; }
|
||||
|
||||
TOKEN=$(curl -sf -m 5 -G "$API/user/login" \
|
||||
--data-urlencode "user=admin" \
|
||||
--data-urlencode "pass=$TECHNITIUM_ADMIN_PASSWORD" 2>/dev/null \
|
||||
| jq -r '.token // empty')
|
||||
[ -n "$TOKEN" ] || { log "Technitium API login failed; skipping $OP $ADDR"; exit 0; }
|
||||
AUTH="Authorization: Bearer $TOKEN"
|
||||
|
||||
# 172.31.8.5 -> 5.8.31.172.in-addr.arpa
|
||||
PTR_HOST=$(printf '%s' "$ADDR" | awk -F. '{print $4"."$3"."$2"."$1".in-addr.arpa"}')
|
||||
|
||||
deregister() {
|
||||
# PTR tells us which A record this IP belonged to
|
||||
TARGET=$(curl -sf -m 5 -H "$AUTH" -G "$API/zones/records/get" \
|
||||
--data-urlencode "domain=$PTR_HOST" 2>/dev/null \
|
||||
| jq -r '.response.records[]? | select(.type=="PTR") | .rData.ptrName' | head -n1)
|
||||
[ -n "$TARGET" ] || return 0
|
||||
curl -sf -m 5 -H "$AUTH" -G "$API/zones/records/delete" \
|
||||
--data-urlencode "domain=$PTR_HOST" \
|
||||
--data-urlencode "type=PTR" \
|
||||
--data-urlencode "ptrName=$TARGET" >/dev/null 2>&1 || log "PTR delete failed for $ADDR"
|
||||
curl -sf -m 5 -H "$AUTH" -G "$API/zones/records/delete" \
|
||||
--data-urlencode "domain=$TARGET" \
|
||||
--data-urlencode "zone=$VPN_DNS_ZONE" \
|
||||
--data-urlencode "type=A" \
|
||||
--data-urlencode "ipAddress=$ADDR" >/dev/null 2>&1 || log "A delete failed for $TARGET/$ADDR"
|
||||
}
|
||||
|
||||
register() {
|
||||
# sanitize CN (= AD username) into a DNS label
|
||||
NAME=$(printf '%s' "$CN" | tr '[:upper:]' '[:lower:]' \
|
||||
| tr -c 'a-z0-9-' '-' | sed 's/^-*//;s/-*$//' | cut -c1-63)
|
||||
[ -n "$NAME" ] || return 0
|
||||
curl -sf -m 5 -H "$AUTH" -G "$API/zones/records/add" \
|
||||
--data-urlencode "domain=$NAME.$VPN_DNS_ZONE" \
|
||||
--data-urlencode "zone=$VPN_DNS_ZONE" \
|
||||
--data-urlencode "type=A" \
|
||||
--data-urlencode "ttl=60" \
|
||||
--data-urlencode "ipAddress=$ADDR" \
|
||||
--data-urlencode "ptr=true" \
|
||||
--data-urlencode "createPtrZone=true" >/dev/null 2>&1 \
|
||||
|| log "A/PTR add failed for $NAME/$ADDR"
|
||||
}
|
||||
|
||||
case "$OP" in
|
||||
add) register ;;
|
||||
update) deregister; register ;; # IP reassigned to a different client
|
||||
delete) deregister ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
+31
-11
@@ -10,21 +10,28 @@ dev tun0
|
||||
topology subnet
|
||||
|
||||
server 172.31.8.0 255.255.255.0
|
||||
ifconfig-pool-persist /var/lib/openvpn/ipp.txt
|
||||
|
||||
# Identity = AD username (username-as-common-name), and one user WILL
|
||||
# connect from several devices (laptop + phone). duplicate-cn allows the
|
||||
# concurrent sessions and gives each its own pool IP. Sticky per-user IPs
|
||||
# (ifconfig-pool-persist) are incompatible with that — do not re-enable.
|
||||
duplicate-cn
|
||||
|
||||
# Split tunnel: only the AD sites + transfer net ride the VPN.
|
||||
push "route 192.168.20.0 255.255.255.0"
|
||||
push "route 192.168.7.0 255.255.255.0"
|
||||
push "route 172.31.255.0 255.255.255.0"
|
||||
|
||||
# All client DNS goes to Technitium on this box (block-outside-dns stops
|
||||
# Windows from leaking AD lookups to the local LAN resolver).
|
||||
# ALL client DNS goes to Technitium. Deliberately NO "dhcp-option DOMAIN"
|
||||
# or DOMAIN-SEARCH here: in OpenVPN3 clients (Connect on macOS/iOS/
|
||||
# Android/Windows) those options MEAN split-DNS — pushing them scopes the
|
||||
# VPN DNS to just those domains. With only DNS pushed, 172.31.8.1 becomes
|
||||
# the default resolver for every query. ADAPTER_DOMAIN_SUFFIX sets the
|
||||
# Windows connection-specific suffix (short names like "ahdc01" still
|
||||
# resolve) without triggering split-DNS. block-outside-dns additionally
|
||||
# stops Windows from leaking queries to the local LAN resolver.
|
||||
push "dhcp-option DNS 172.31.8.1"
|
||||
push "dhcp-option DOMAIN ad.atlashorizon.net"
|
||||
push "dhcp-option DOMAIN-SEARCH ad.atlashorizon.net"
|
||||
push "dhcp-option DOMAIN-SEARCH ad.calliecorner.net"
|
||||
push "dhcp-option DOMAIN-SEARCH ad.oswaldtheotter.net"
|
||||
push "dhcp-option DOMAIN-SEARCH ad.trochalakis.net"
|
||||
push "dhcp-option ADAPTER_DOMAIN_SUFFIX ad.atlashorizon.net"
|
||||
push "block-outside-dns"
|
||||
|
||||
# PKI — server certificate only (offline CA, see pki/README.md);
|
||||
@@ -44,8 +51,12 @@ plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn
|
||||
verify-client-cert none
|
||||
username-as-common-name
|
||||
hand-window 120 # allow time for the Duo push round-trip
|
||||
reneg-sec 0 # no mid-session TLS renegotiation (would re-prompt Duo)
|
||||
auth-gen-token 43200 # reconnects within 12h reuse a token instead of a new push
|
||||
# Hourly rekey + 12h auth token: renegotiations are satisfied silently by
|
||||
# the pushed token (no mid-session Duo prompts); a fresh password + push
|
||||
# is only required once the token expires. (auth-gen-token requires a
|
||||
# non-zero reneg-sec — do not set reneg-sec 0 here.)
|
||||
reneg-sec 3600
|
||||
auth-gen-token 43200
|
||||
|
||||
keepalive 10 60
|
||||
explicit-exit-notify 1
|
||||
@@ -60,6 +71,15 @@ group nogroup
|
||||
persist-key
|
||||
persist-tun
|
||||
|
||||
# Register each client as <username>.$VPN_DNS_ZONE (A + PTR) in Technitium
|
||||
# on connect, clean up on disconnect. Invoked via sh so the mounted script
|
||||
# needs no exec bit; env (TECHNITIUM_ADMIN_PASSWORD, VPN_DNS_ZONE) comes
|
||||
# from the container environment.
|
||||
script-security 2
|
||||
learn-address "/bin/sh /etc/openvpn/learn-address.sh"
|
||||
|
||||
status /var/lib/openvpn/status.log 30
|
||||
management 127.0.0.1 7505
|
||||
# Unix socket (root-only) instead of a passwordless TCP port:
|
||||
# docker compose exec openvpn sh -c "socat - UNIX:/var/lib/openvpn/mgmt.sock"
|
||||
management /var/lib/openvpn/mgmt.sock unix
|
||||
verb 3
|
||||
Reference in new issue
Block a user