|
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. |
9 | | - |
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: |
37 | | - |
38 | | -```env |
39 | | -TRUST_PROXY=true |
40 | | -``` |
41 | | - |
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. |
| 7 | +**Note:** The app always trusts `X-Real-IP` / `X-Forwarded-For` headers for client IP resolution (used for login rate limiting and telemetry dedupe). It must be deployed behind a trusted reverse proxy (Caddy) — never exposed directly to the internet. |
43 | 8 |
|
44 | 9 | ## Admin Bootstrap |
45 | 10 |
|
@@ -78,18 +43,6 @@ wpcomposer cleanup-sessions |
78 | 43 |
|
79 | 44 | Run via systemd timer or cron (daily recommended). |
80 | 45 |
|
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 | 46 | ## Emergency Password Reset |
94 | 47 |
|
95 | 48 | If locked out of the admin panel: |
|
0 commit comments