|
2 | 2 |
|
3 | 3 | ## Security Model |
4 | 4 |
|
5 | | -Admin access uses defense in depth with two independent layers: |
| 5 | +Admin access is protected by in-app authentication. Email/password login and admin authorization are required for all protected `/admin/*` routes. |
6 | 6 |
|
7 | | -1. **Network layer** — Tailscale restricts access to `/admin/*` routes. Only devices on the tailnet can reach admin endpoints. |
8 | | -2. **Application layer** — in-app authentication (email/password) and admin authorization required for all protected `/admin/*` routes. |
| 7 | +## Proxy Trust |
9 | 8 |
|
10 | | -Both layers must pass. A valid Tailscale connection without app auth (or vice versa) is denied. |
11 | | - |
12 | | -## Network Restriction |
13 | | - |
14 | | -The Go app enforces IP-based access control on all `/admin/*` routes via the `ADMIN_ALLOW_CIDR` config. |
15 | | - |
16 | | -Default allowed ranges (Tailscale): |
17 | | -- `100.64.0.0/10` (Tailscale IPv4) |
18 | | -- `fd7a:115c:a1e0::/48` (Tailscale IPv6) |
19 | | - |
20 | | -Override via environment: |
21 | | - |
22 | | -```env |
23 | | -ADMIN_ALLOW_CIDR=100.64.0.0/10,fd7a:115c:a1e0::/48,10.0.0.0/8 |
24 | | -``` |
25 | | - |
26 | | -Set to empty to disable IP restriction (development only): |
27 | | - |
28 | | -```env |
29 | | -ADMIN_ALLOW_CIDR= |
30 | | -``` |
31 | | - |
32 | | -If all configured CIDRs are invalid, the middleware fails closed — all admin access is blocked until the configuration is corrected. |
33 | | - |
34 | | -### Proxy trust |
35 | | - |
36 | | -By default, the app uses the raw `RemoteAddr` for IP checks. If behind a reverse proxy (Caddy, nginx), enable proxy header trust: |
| 9 | +When behind a reverse proxy (Caddy, nginx), enable proxy header trust so the app sees the real client IP (used for login rate limiting and telemetry dedupe): |
37 | 10 |
|
38 | 11 | ```env |
39 | 12 | TRUST_PROXY=true |
40 | 13 | ``` |
41 | 14 |
|
42 | | -This enables Chi's `RealIP` middleware, which reads `X-Forwarded-For` / `X-Real-IP` headers to determine the client IP. **Only enable this when the app is behind a trusted proxy** — otherwise clients can spoof their IP via forwarding headers and bypass admin IP restrictions. |
| 15 | +This reads `X-Real-IP` / `X-Forwarded-For` headers to determine the client IP. **Only enable this when the app is behind a trusted proxy** — otherwise clients can spoof their IP. |
43 | 16 |
|
44 | 17 | ## Admin Bootstrap |
45 | 18 |
|
@@ -78,18 +51,6 @@ wpcomposer cleanup-sessions |
78 | 51 |
|
79 | 52 | Run via systemd timer or cron (daily recommended). |
80 | 53 |
|
81 | | -## Exposure Verification |
82 | | - |
83 | | -To verify admin is not publicly accessible: |
84 | | - |
85 | | -```bash |
86 | | -# From outside the tailnet — should return 403 |
87 | | -curl -s -o /dev/null -w "%{http_code}" https://app.example.com/admin/login |
88 | | - |
89 | | -# From inside the tailnet — should return 200 |
90 | | -curl -s -o /dev/null -w "%{http_code}" https://app.example.com/admin/login |
91 | | -``` |
92 | | - |
93 | 54 | ## Emergency Password Reset |
94 | 55 |
|
95 | 56 | If locked out of the admin panel: |
|
0 commit comments