Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b834cd06f3 | ||
|
|
22ffe2f617 |
No files matched your search
+11
-4
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in new issue
Block a user