Compare commits

..
2 Commits
Author SHA1 Message Date
nick b834cd06f3 sigh 2026-07-15 21:24:42 -07:00
nick 22ffe2f617 idk 2026-07-15 21:22:15 -07:00
4 changed files with 41 additions and 41 deletions

No files matched your search

+11 -4
View File
@@ -3,9 +3,10 @@
# later run uses — key-only auth, passwordless sudo — verify it actually # later run uses — key-only auth, passwordless sudo — verify it actually
# works, then lock sshd down to keys. Run once per fresh VPS: # works, then lock sshd down to keys. Run once per fresh VPS:
# #
# ansible-playbook bootstrap.yml # root already has your key # ansible-playbook bootstrap.yml
# ansible-playbook bootstrap.yml -k # root still uses a password #
# # (needs sshpass installed) # It prompts for the root SSH password (needs sshpass on the control
# node). If root already authenticates with your key, just press Enter.
# #
# Afterwards set `ansible_user: ansible` in inventory.yml and never # Afterwards set `ansible_user: ansible` in inventory.yml and never
# connect as root again. Overridable with -e: # connect as root again. Overridable with -e:
@@ -22,10 +23,16 @@
- name: Create the automation user (connects as root) - name: Create the automation user (connects as root)
hosts: vpnhub hosts: vpnhub
gather_facts: false gather_facts: false
vars_prompt:
- name: bootstrap_root_password
prompt: "Root SSH password (Enter if root already uses your key)"
private: true
default: ""
vars: vars:
ansible_user: root # play var — outranks inventory on purpose ansible_user: root # play var — outranks inventory on purpose
ansible_password: "{{ bootstrap_root_password }}"
bootstrap_user: ansible bootstrap_user: ansible
bootstrap_ssh_public_key_file: "~/.ssh/id_ed25519.pub" bootstrap_ssh_public_key_file: "~/.ssh/togo-ansible.pub"
tasks: tasks:
- name: Create the user (no password hash set, account password locked) - name: Create the user (no password hash set, account password locked)
ansible.builtin.user: ansible.builtin.user:
+3 -9
View File
@@ -1,7 +1,7 @@
# AtlasHorizon VPN — client profile template. # AtlasHorizon VPN — client profile template.
# Per user: paste ca.crt, the user's cert/key, and ta.key (from the offline # ONE profile for everyone: paste ca.crt and ta.key (from the offline CA,
# CA, see openvpn/pki/README.md) into the inline blocks below, save as # see openvpn/pki/README.md) into the inline blocks below and distribute.
# <username>.ovpn, import into OpenVPN Connect. # No per-user certificates — identity is AD user/pass + Duo.
# #
# Login: AD username; password field takes either # Login: AD username; password field takes either
# ADpassword -> Duo push # ADpassword -> Duo push
@@ -33,12 +33,6 @@ verb 3
<ca> <ca>
# ----- paste ca.crt ----- # ----- paste ca.crt -----
</ca> </ca>
<cert>
# ----- paste <username>.crt -----
</cert>
<key>
# ----- paste <username>.key -----
</key>
<tls-crypt> <tls-crypt>
# ----- paste ta.key ----- # ----- paste ta.key -----
</tls-crypt> </tls-crypt>
+21 -22
View File
@@ -1,10 +1,14 @@
# OpenVPN PKI — offline CA workflow # OpenVPN PKI — offline CA workflow (server cert only)
There are **no individual client certificates** in this design — users
authenticate with AD password + Duo. The PKI exists solely so clients can
verify they're talking to the real server (`remote-cert-tls server`), and
`ta.key` (tls-crypt) gates who can attempt a handshake at all.
The CA private key **never touches the VPS**. Run easy-rsa on an offline The CA private key **never touches the VPS**. Run easy-rsa on an offline
machine (or at minimum a machine that isn't this server), then copy only the machine, then copy only the issued material here.
issued material here.
## One-time CA + server cert (on the offline machine) ## One-time setup (on the offline machine)
```bash ```bash
git clone https://github.com/OpenVPN/easy-rsa.git && cd easy-rsa/easyrsa3 git clone https://github.com/OpenVPN/easy-rsa.git && cd easy-rsa/easyrsa3
@@ -12,39 +16,34 @@ git clone https://github.com/OpenVPN/easy-rsa.git && cd easy-rsa/easyrsa3
./easyrsa build-ca # sets the CA passphrase — keep offline ./easyrsa build-ca # sets the CA passphrase — keep offline
./easyrsa build-server-full racknerd-vpn nopass ./easyrsa build-server-full racknerd-vpn nopass
openvpn --genkey tls-crypt ta.key # shared tls-crypt key openvpn --genkey tls-crypt ta.key # shared tls-crypt key
./easyrsa gen-crl
``` ```
## Copy to the VPS (this directory) ## Copy to the VPS (this directory)
| File on offline box | Name here | | File on offline box | Name here |
|--------------------------------------|---------------| |--------------------------------|--------------|
| `pki/ca.crt` | `ca.crt` | | `pki/ca.crt` | `ca.crt` |
| `pki/issued/racknerd-vpn.crt` | `server.crt` | | `pki/issued/racknerd-vpn.crt` | `server.crt` |
| `pki/private/racknerd-vpn.key` | `server.key` | | `pki/private/racknerd-vpn.key` | `server.key` |
| `ta.key` | `ta.key` | | `ta.key` | `ta.key` |
| `pki/crl.pem` (optional, see server.conf) | `crl.pem` |
`chmod 600 server.key ta.key` after copying. `chmod 600 server.key ta.key` after copying.
## Per-user client certs (offline) ## Client profile
```bash Build ONE `.ovpn` from `client/client.ovpn.template`, pasting `ca.crt` and
./easyrsa build-client-full nick nopass `ta.key` into the inline blocks. The same profile goes to every user.
```
Build the client's `.ovpn` from `client/client.ovpn.template`, pasting ## Revoking access
`ca.crt`, the client cert/key, and `ta.key` into the inline blocks.
CN should match the AD username — the server logs use it
(`username-as-common-name` keeps them consistent).
## Revoking a client No client certs means no CRL — access control lives where the identity
lives:
```bash - **Disable the AD account** (or remove it from the VPN group if you use
./easyrsa revoke nick && ./easyrsa gen-crl `security_group_dn`) — kills new logins immediately.
``` - Remove/disable the user in **Duo** as backup.
- If a *profile* leaks (ta.key compromised): regenerate `ta.key`, replace
Copy the fresh `crl.pem` here, uncomment `crl-verify` in `server.conf`, and it here, restart openvpn, redistribute the profile — old profiles stop
`docker compose restart openvpn`. working instantly.
Everything in this directory except this README is gitignored. Everything in this directory except this README is gitignored.
+6 -6
View File
@@ -1,6 +1,8 @@
# OpenVPN road-warrior endpoint — RackNerd hub # OpenVPN road-warrior endpoint — RackNerd hub
# Auth chain: client cert (offline CA) + user/pass -> PAM -> RADIUS # Auth chain: AD user/pass -> PAM -> RADIUS
# -> Duo Auth Proxy (127.0.0.1:1812) -> LDAPS to the DCs. # -> Duo Auth Proxy (127.0.0.1:1812) -> LDAPS to the DCs.
# No individual client certs: identity is LDAP + Duo only. tls-crypt
# (shared key in the profile) still gates who can attempt a handshake.
port 1194 port 1194
proto udp4 proto udp4
@@ -25,23 +27,21 @@ push "dhcp-option DOMAIN-SEARCH ad.oswaldtheotter.net"
push "dhcp-option DOMAIN-SEARCH ad.trochalakis.net" push "dhcp-option DOMAIN-SEARCH ad.trochalakis.net"
push "block-outside-dns" push "block-outside-dns"
# PKI — issued from the offline CA, see pki/README.md # PKI — server certificate only (offline CA, see pki/README.md);
# clients present no certificate.
ca /etc/openvpn/pki/ca.crt ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/server.crt cert /etc/openvpn/pki/server.crt
key /etc/openvpn/pki/server.key key /etc/openvpn/pki/server.key
dh none dh none
tls-crypt /etc/openvpn/pki/ta.key tls-crypt /etc/openvpn/pki/ta.key
# Enable once you publish a CRL from the offline CA (revoked laptops etc.):
;crl-verify /etc/openvpn/pki/crl.pem
remote-cert-tls client
tls-version-min 1.2 tls-version-min 1.2
data-ciphers AES-256-GCM:CHACHA20-POLY1305 data-ciphers AES-256-GCM:CHACHA20-POLY1305
auth SHA256 auth SHA256
# User auth via Duo (password only = push; "password,123456" = passcode) # User auth via Duo (password only = push; "password,123456" = passcode)
plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn
verify-client-cert require verify-client-cert none
username-as-common-name username-as-common-name
hand-window 120 # allow time for the Duo push round-trip hand-window 120 # allow time for the Duo push round-trip
reneg-sec 0 # no mid-session TLS renegotiation (would re-prompt Duo) reneg-sec 0 # no mid-session TLS renegotiation (would re-prompt Duo)