working ig
This commit is contained in:
1 parent
b834cd06f3
commit
c14be296a8
13 files changed
+242
-24
No files matched your search
@@ -32,6 +32,10 @@ RADIUS_SECRET=
|
||||
# Applied on FIRST start only; change later via the console itself.
|
||||
TECHNITIUM_ADMIN_PASSWORD=
|
||||
|
||||
# ── DNS zone for VPN client registration (A + PTR via learn-address) ──
|
||||
# Leave empty to disable client DNS registration entirely.
|
||||
VPN_DNS_ZONE=roam.atlashorizon.net
|
||||
|
||||
# ── Optional (defaults shown) ─────────────────────────────────────────
|
||||
# Duo Auth Proxy version; pin (e.g. 6.5.1) for deterministic updates
|
||||
#DUO_AUTHPROXY_VERSION=latest
|
||||
|
||||
@@ -34,6 +34,11 @@ radius_secret: ""
|
||||
# console (this var must still match for the init job's API login).
|
||||
technitium_admin_password: ""
|
||||
|
||||
# DNS zone where VPN clients get registered as <username>.<zone> with a
|
||||
# matching PTR (Technitium primary zone, created by the init job). Set to
|
||||
# "" to disable registration.
|
||||
vpn_dns_zone: roam.atlashorizon.net
|
||||
|
||||
# ── Optional ──
|
||||
# Pin for deterministic Duo updates (e.g. "6.5.1"); bumping it forces a
|
||||
# refetch of the tarball on the next rebuild.
|
||||
|
||||
+39
-9
@@ -23,6 +23,7 @@
|
||||
vpnhub_base_dir: /opt
|
||||
vpnhub_stack_dir: "{{ vpnhub_base_dir }}/vpnhub"
|
||||
vpnhub_force_rebuild: false
|
||||
vpnhub_host_dns: [1.1.1.1, 9.9.9.9]
|
||||
public_iface: "{{ ansible_default_ipv4.interface }}"
|
||||
wg_peers_configured: >-
|
||||
{{ (troch_wg_public_key | default('')) != ''
|
||||
@@ -30,9 +31,9 @@
|
||||
|
||||
handlers:
|
||||
- name: reload firewall
|
||||
# the reload flushes the Docker-managed chains, so restart docker after
|
||||
ansible.builtin.shell: |
|
||||
netfilter-persistent reload
|
||||
# the reload flushes Docker's chains — put them back
|
||||
if systemctl is-active --quiet docker; then systemctl restart docker; fi
|
||||
|
||||
- name: restart wireguard
|
||||
@@ -50,17 +51,18 @@
|
||||
chdir: "{{ vpnhub_stack_dir }}"
|
||||
|
||||
- name: recreate duo-authproxy
|
||||
ansible.builtin.command: docker compose up -d --force-recreate duo-authproxy
|
||||
# --build: entrypoint/cfg-template changes are baked into the image
|
||||
ansible.builtin.command: docker compose up -d --build --force-recreate duo-authproxy
|
||||
args:
|
||||
chdir: "{{ vpnhub_stack_dir }}"
|
||||
|
||||
- name: recreate openvpn
|
||||
ansible.builtin.command: docker compose up -d --force-recreate openvpn
|
||||
ansible.builtin.command: docker compose up -d --build --force-recreate openvpn
|
||||
args:
|
||||
chdir: "{{ vpnhub_stack_dir }}"
|
||||
|
||||
- name: recreate stack
|
||||
ansible.builtin.command: docker compose up -d --force-recreate
|
||||
ansible.builtin.command: docker compose up -d --build --force-recreate
|
||||
args:
|
||||
chdir: "{{ vpnhub_stack_dir }}"
|
||||
|
||||
@@ -76,9 +78,12 @@
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
# Distro-agnostic: ansible_distribution is Ubuntu or Debian, and
|
||||
# Docker publishes separate repos per distro (a Debian codename
|
||||
# like trixie does not exist under linux/ubuntu and vice versa).
|
||||
- name: Docker apt signing key
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
url: "https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg"
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: "0644"
|
||||
|
||||
@@ -87,7 +92,7 @@
|
||||
repo: >-
|
||||
deb [arch={{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}
|
||||
signed-by=/etc/apt/keyrings/docker.asc]
|
||||
https://download.docker.com/linux/ubuntu
|
||||
https://download.docker.com/linux/{{ ansible_distribution | lower }}
|
||||
{{ ansible_distribution_release }} stable
|
||||
filename: docker
|
||||
|
||||
@@ -124,10 +129,19 @@
|
||||
sysctl_file: /etc/sysctl.d/99-vpnhub.conf
|
||||
reload: true
|
||||
|
||||
# Technitium binds 0.0.0.0:53 — systemd-resolved's stub listener on
|
||||
# 127.0.0.53:53 would collide with it.
|
||||
# Technitium binds 0.0.0.0:53 — free the port while keeping host DNS
|
||||
# working. Ubuntu runs systemd-resolved (stub on 127.0.0.53:53 must be
|
||||
# disabled); plain Debian has no resolved and just needs a sane static
|
||||
# /etc/resolv.conf.
|
||||
- name: Check whether systemd-resolved is in use
|
||||
tags: host
|
||||
ansible.builtin.stat:
|
||||
path: /run/systemd/resolve/resolv.conf
|
||||
register: resolved_upstream
|
||||
|
||||
- name: resolved drop-in directory
|
||||
tags: host
|
||||
when: resolved_upstream.stat.exists
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/resolved.conf.d
|
||||
state: directory
|
||||
@@ -135,6 +149,7 @@
|
||||
|
||||
- name: Disable systemd-resolved stub listener (frees :53 for Technitium)
|
||||
tags: host
|
||||
when: resolved_upstream.stat.exists
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/resolved.conf.d/99-vpnhub.conf
|
||||
mode: "0644"
|
||||
@@ -143,14 +158,29 @@
|
||||
DNSStubListener=no
|
||||
notify: restart systemd-resolved
|
||||
|
||||
- name: Point /etc/resolv.conf at resolved's upstream file (not the dead stub)
|
||||
- name: Point /etc/resolv.conf at resolved upstream file (not the dead stub)
|
||||
tags: host
|
||||
when: resolved_upstream.stat.exists
|
||||
ansible.builtin.file:
|
||||
src: /run/systemd/resolve/resolv.conf
|
||||
dest: /etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Static resolv.conf (no systemd-resolved on this host)
|
||||
tags: host
|
||||
when: not resolved_upstream.stat.exists
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/resolv.conf
|
||||
follow: false # replace a dangling symlink rather than write through it
|
||||
mode: "0644"
|
||||
content: |
|
||||
# Managed by Ansible (vpnhub). Public resolvers on purpose: the
|
||||
# host must resolve registries/repos even when the stack is down.
|
||||
{% for ns in vpnhub_host_dns %}
|
||||
nameserver {{ ns }}
|
||||
{% endfor %}
|
||||
|
||||
- name: Firewall rules (rendered from host/rules.v4/v6.template)
|
||||
tags: [host, firewall]
|
||||
ansible.builtin.copy:
|
||||
|
||||
@@ -18,6 +18,8 @@ RADIUS_SECRET={{ radius_secret }}
|
||||
|
||||
TECHNITIUM_ADMIN_PASSWORD={{ technitium_admin_password }}
|
||||
|
||||
VPN_DNS_ZONE={{ vpn_dns_zone | default('roam.atlashorizon.net') }}
|
||||
|
||||
DUO_AUTHPROXY_VERSION={{ duo_authproxy_version | default('latest') }}
|
||||
|
||||
# /opt layout — consumed by compose ${*_DIR} interpolation
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
client
|
||||
dev tun
|
||||
proto udp
|
||||
remote connect.atlashorizon.net 1194
|
||||
resolv-retry infinite
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
dhcp-option DNS 172.31.8.1
|
||||
|
||||
remote-cert-tls server
|
||||
tls-version-min 1.2
|
||||
data-ciphers AES-256-GCM:CHACHA20-POLY1305
|
||||
auth SHA256
|
||||
|
||||
auth-user-pass
|
||||
auth-retry interact
|
||||
hand-window 120
|
||||
verb 3
|
||||
|
||||
<ca>
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDTjCCAjagAwIBAgIUFUdfgwj+K1Tt/fHBAvkfbCzjVhkwDQYJKoZIhvcNAQEL
|
||||
BQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMjYwNzE2MDQyNTQyWhcNMzYw
|
||||
NzEzMDQyNTQyWjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAKFZW5dKKR0/l6iPab513g8mCD+h8Kg8VmFY+PYS
|
||||
jURdeb1hhTtVYuRir8jVKBmX1ck/KSATomzozhA97CpAkrZSrruHGwtJLAZDr+oQ
|
||||
Fj7bzH2T94Pk35IbwI1HgIg9ZG18yONo9Lv6ODECZpVLQHzZpsBPWbWTSLiGYylp
|
||||
1EiUo9PW7Dwx5xl/X1/oFjs+2wzv2F9GZZNNqQfDWupq18KwobUzpUAgNXbB1ZiV
|
||||
AU//XWJg/s4fSdSsWY5ih+PoJawQBpdTxd4DWwC/tK3NDia/Rr5oeL6RctlO+Tps
|
||||
BGjOtP5OHdqnuEC0QbuJcc9b8wuBdQzrWV5weCUOjMD6F6UCAwEAAaOBkzCBkDAP
|
||||
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSEH5TGxRg/19B0j1Gih0XUWTJW3DBR
|
||||
BgNVHSMESjBIgBSEH5TGxRg/19B0j1Gih0XUWTJW3KEapBgwFjEUMBIGA1UEAwwL
|
||||
RWFzeS1SU0EgQ0GCFBVHX4MI/itU7f3xwQL5H2ws41YZMAsGA1UdDwQEAwIBBjAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEAoI0jRxiKlNPbxSguWqW3Lr5t/a20Ntck5YY/6I3z
|
||||
CR97hRJhLHOfrni4EOKASpEW/afFezH/yuQyYwWxjzkJYUnKA1SQxQYGtaQbV+yu
|
||||
0n6uWwvhBwZTsESe0SrWLgw1IhKf609cRt6F/DdaX7oujSgJCQT/yzNjmCHHts87
|
||||
4qEN/fM3yE5peacO3g1zc0wjhdlG4sp8GJcpXFnCDcJsfNbgj9JYZTHulL9dhxmh
|
||||
NZKMUGK3pXIQq0IU12GDh9gKCFUpe3ILfQ8gJCm/u1EYRSoC27ZDmtD+Pgsjwr0E
|
||||
uLKM4f2c43Kq2XLdldreuRHMPLOZ0XQDo3lOdfVRp8Rcqw==
|
||||
-----END CERTIFICATE-----
|
||||
</ca>
|
||||
<tls-crypt>
|
||||
-----BEGIN OpenVPN Static key V1-----
|
||||
5fedf4b825a32eb88722a14f801ef416
|
||||
2d015c4b5c41654c556108fc2fa5006e
|
||||
f83c97ea210113935dbaf47469ba9cd4
|
||||
752f928caf48926b47b6da2b4573f6fa
|
||||
ed909502d027ba02e509dc48c6542d74
|
||||
53c3013ce00e443b43faac4f8f87717f
|
||||
1fe942bd1f1412fb38566a88ba7e0d5a
|
||||
b722ea79cb4a1b5e172185d2644db81b
|
||||
33fdc631815e47f6ae5ce099beee5c37
|
||||
b0880c7ff8f9c4ff7a8302e7eb692ce9
|
||||
eab50f743de8aa91bff266b2dc4f504c
|
||||
775137ae64d71c79e35a40e8bedb8f67
|
||||
65bfa02d05bfc2a5e8824eff9be34a26
|
||||
a8c8b151a215ae26c67c006e431c8a57
|
||||
8985d2feec6787b629b2e30dfdc5ffea
|
||||
a0d26b0b0f1aca83ccbfc77b660cceed
|
||||
-----END OpenVPN Static key V1-----
|
||||
</tls-crypt>
|
||||
@@ -69,6 +69,7 @@ services:
|
||||
env_file: .env
|
||||
volumes:
|
||||
- ${OPENVPN_DIR:-./openvpn}/server.conf:/etc/openvpn/server.conf:ro
|
||||
- ${OPENVPN_DIR:-./openvpn}/learn-address.sh:/etc/openvpn/learn-address.sh:ro
|
||||
- ${OPENVPN_DIR:-./openvpn}/pki:/etc/openvpn/pki:ro
|
||||
- openvpn-state:/var/lib/openvpn
|
||||
depends_on:
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Duo publishes no official image; build from their source tarball.
|
||||
# Install flags match Duo's documented non-interactive install.
|
||||
#
|
||||
# Slimming option: switch BOTH stages to debian:bookworm-slim (~80MB less,
|
||||
# and the base layer is shared with the openvpn image). Both must move
|
||||
# together — the proxy links against the builder's glibc. Alpine/musl
|
||||
# will not work.
|
||||
#FROM debian:bookworm-slim AS build
|
||||
|
||||
FROM ubuntu:24.04 AS build
|
||||
|
||||
@@ -20,6 +26,8 @@ RUN groupadd -r -g 35505 duo \
|
||||
&& ./install --install-dir /opt/duoauthproxy --service-user duo \
|
||||
--log-group duo --create-init-script no --enable-selinux no
|
||||
|
||||
# Slim variant (must match the build stage base — see note above):
|
||||
#FROM debian:bookworm-slim
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# gettext-base = envsubst for rendering authproxy.cfg from env vars
|
||||
|
||||
@@ -8,7 +8,7 @@ debug=false
|
||||
|
||||
[ad_client]
|
||||
host=${TROCH_DC1_IP}
|
||||
host_2=${TROCH_DC2_IP}
|
||||
;host_2=${TROCH_DC2_IP}
|
||||
service_account_username=${DUO_SVC_USER}
|
||||
service_account_password=${DUO_SVC_PASSWORD}
|
||||
search_dn=${AD_SEARCH_DN}
|
||||
@@ -22,7 +22,7 @@ ssl_ca_certs_file=/certs/ad-ldaps-ca.pem
|
||||
ssl_verify_hostname=false
|
||||
; Restrict VPN access to one AD group (fill DUO_SECURITY_GROUP_DN in .env
|
||||
; and uncomment):
|
||||
;security_group_dn=${DUO_SECURITY_GROUP_DN}
|
||||
security_group_dn=${DUO_SECURITY_GROUP_DN}
|
||||
|
||||
[radius_server_auto]
|
||||
ikey=${DUO_IKEY}
|
||||
|
||||
@@ -13,7 +13,7 @@ if [ ! -f /certs/ad-ldaps-ca.pem ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
envsubst '${TROCH_DC1_IP} ${TROCH_DC2_IP} ${DUO_IKEY} ${DUO_SKEY} ${DUO_API_HOST} ${DUO_SVC_USER} ${DUO_SVC_PASSWORD} ${AD_SEARCH_DN} ${RADIUS_SECRET}' \
|
||||
envsubst '${TROCH_DC1_IP} ${TROCH_DC2_IP} ${DUO_IKEY} ${DUO_SKEY} ${DUO_API_HOST} ${DUO_SVC_USER} ${DUO_SVC_PASSWORD} ${AD_SEARCH_DN} ${RADIUS_SECRET} ${DUO_SECURITY_GROUP_DN}' \
|
||||
< /opt/duoauthproxy/conf/authproxy.cfg.template \
|
||||
> /opt/duoauthproxy/conf/authproxy.cfg
|
||||
chmod 600 /opt/duoauthproxy/conf/authproxy.cfg
|
||||
|
||||
+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
|
||||
@@ -77,4 +77,23 @@ ensure_fwd_zone ad.oswaldtheotter.net "$OSWALD_DC_IP"
|
||||
ensure_fwd_zone 20.168.192.in-addr.arpa "$TROCH_DC1_IP" "$TROCH_DC2_IP"
|
||||
ensure_fwd_zone 7.168.192.in-addr.arpa "$OSWALD_DC_IP"
|
||||
|
||||
# Primary zones for VPN client registration (openvpn learn-address hook
|
||||
# writes <username>.$VPN_DNS_ZONE A records + PTRs for 172.31.8.0/24)
|
||||
ensure_primary_zone() {
|
||||
zone=$1
|
||||
if zone_exists "$zone"; then
|
||||
echo "zone $zone already exists — leaving as-is"
|
||||
return 0
|
||||
fi
|
||||
echo "creating primary zone $zone"
|
||||
curl -sf -H "$AUTH" -G "$API/zones/create" \
|
||||
--data-urlencode "zone=$zone" \
|
||||
--data-urlencode "type=Primary" | api_ok
|
||||
}
|
||||
|
||||
if [ -n "${VPN_DNS_ZONE:-}" ]; then
|
||||
ensure_primary_zone "$VPN_DNS_ZONE"
|
||||
ensure_primary_zone "8.31.172.in-addr.arpa"
|
||||
fi
|
||||
|
||||
echo "Technitium AD forwarder zones OK."
|
||||
Reference in new issue
Block a user