Add service exposure policy and external access retest script
This commit is contained in:
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PRIVATE_ADMIN_HOSTS=(
|
||||
gotify.lan.ddnsgeek.com
|
||||
grafana.lan.ddnsgeek.com
|
||||
prometheus.lan.ddnsgeek.com
|
||||
node-red.lan.ddnsgeek.com
|
||||
traefik.lan.ddnsgeek.com
|
||||
portainer.lan.ddnsgeek.com
|
||||
influxdb.lan.ddnsgeek.com
|
||||
kuma.lan.ddnsgeek.com
|
||||
monitor-kuma.lan.ddnsgeek.com
|
||||
edge.lan.ddnsgeek.com
|
||||
)
|
||||
|
||||
echo "[+] External retest started: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
|
||||
a11y_failures=0
|
||||
for host in "${PRIVATE_ADMIN_HOSTS[@]}"; do
|
||||
code=$(curl -k -sS -o /dev/null -m 10 -w '%{http_code}' "https://${host}" || true)
|
||||
if [[ "$code" == "000" || "$code" == "403" || "$code" == "401" ]]; then
|
||||
echo "[PASS] ${host} blocked externally (code=${code})"
|
||||
else
|
||||
echo "[FAIL] ${host} appears reachable externally (code=${code})"
|
||||
a11y_failures=$((a11y_failures + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
if [[ $a11y_failures -eq 0 ]]; then
|
||||
echo "[PASS] Non-public services are inaccessible from this source path."
|
||||
exit 0
|
||||
else
|
||||
echo "[FAIL] ${a11y_failures} private-admin service(s) still externally reachable."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,78 @@
|
||||
# Service Access Policy and External Exposure Hardening
|
||||
|
||||
## 1) Service classification
|
||||
|
||||
| Service/Host | Classification | Rationale |
|
||||
|---|---|---|
|
||||
| `auth.lan.ddnsgeek.com` | `authenticated-public` | Public identity/login entrypoint; internet-accessible but requires user authentication. |
|
||||
| `nextcloud.lan.ddnsgeek.com` | `authenticated-public` | Internet-facing collaboration app that must remain reachable to authenticated users. |
|
||||
| `passbolt.lan.ddnsgeek.com` | `authenticated-public` | Public password-management portal with strong authentication controls. |
|
||||
| `gitea.lan.ddnsgeek.com` | `authenticated-public` | Public developer endpoint with account-based access. |
|
||||
| `searxng.lan.ddnsgeek.com` | `public` | Intended anonymous/search access endpoint. |
|
||||
| `familytree.lan.ddnsgeek.com` | `authenticated-public` | End-user app; externally reachable but login-protected. |
|
||||
| `shifts.lan.ddnsgeek.com` | `authenticated-public` | End-user app; externally reachable but login-protected. |
|
||||
| `stockfill.lan.ddnsgeek.com` | `authenticated-public` | End-user app; externally reachable but login-protected. |
|
||||
| `gotify.lan.ddnsgeek.com` | `private-admin` | Admin/ops notification backend; should not be internet reachable. |
|
||||
| `grafana.lan.ddnsgeek.com` | `private-admin` | Infrastructure admin/observability console. |
|
||||
| `prometheus.lan.ddnsgeek.com` | `private-admin` | Monitoring datastore/query interface. |
|
||||
| `node-red.lan.ddnsgeek.com` | `private-admin` | Automation runtime and flow editor. |
|
||||
| `traefik.lan.ddnsgeek.com` | `private-admin` | Reverse-proxy admin/dashboard surface. |
|
||||
| `portainer.lan.ddnsgeek.com` | `private-admin` | Container management plane. |
|
||||
| `influxdb.lan.ddnsgeek.com` | `private-admin` | Metrics datastore admin/API surface. |
|
||||
| `kuma.lan.ddnsgeek.com` | `private-admin` | Monitoring admin surface. |
|
||||
| `monitor-kuma.lan.ddnsgeek.com` | `private-admin` | Monitoring admin surface. |
|
||||
| `edge.lan.ddnsgeek.com` | `private-admin` | Edge/network administration plane. |
|
||||
|
||||
## 2) Required controls for `private-admin`
|
||||
|
||||
Apply **at least one** trusted-path control (preferably layered):
|
||||
|
||||
- Private network only (no public DNS / no internet route).
|
||||
- WireGuard/Tailscale/OpenVPN access gate.
|
||||
- mTLS client certificate requirement at reverse proxy.
|
||||
- Source IP allowlist at firewall and reverse proxy.
|
||||
|
||||
Minimum target state for all `private-admin` hosts:
|
||||
|
||||
- Public internet: connection refused/timeout, or immediate `403` for untrusted source.
|
||||
- Trusted path (VPN/mTLS/allowlisted IP): normal authenticated access.
|
||||
|
||||
## 3) Gateway auth hardening
|
||||
|
||||
For all `public` and `authenticated-public` services:
|
||||
|
||||
- Keep SSO and MFA enforcement at the identity gateway.
|
||||
- Enforce lockout/backoff on `/login`, `/oauth/*`, `/auth/*`, `/api/auth/*`.
|
||||
- Rate-limit by source IP + account identifier to deter credential stuffing.
|
||||
|
||||
Suggested baseline:
|
||||
|
||||
- Soft limit: `10 req/min` per IP for auth endpoints.
|
||||
- Burst: `20`.
|
||||
- Temporary block: `15 min` after repeated failures.
|
||||
- Account lockout: `5-10` consecutive failed attempts (with secure unlock flow).
|
||||
|
||||
## 4) WAF / reverse-proxy protections
|
||||
|
||||
Deploy one of:
|
||||
|
||||
- WAF managed rules for bot/credential-stuffing signatures.
|
||||
- Reverse-proxy failed-auth throttling and tarpit/delay policy.
|
||||
|
||||
Implement logging + alerting thresholds:
|
||||
|
||||
- High failed-auth rate from one IP/CIDR.
|
||||
- Password spray pattern across many usernames.
|
||||
- Geo/ASN anomalies for sensitive apps.
|
||||
|
||||
## 5) External re-test procedure
|
||||
|
||||
Re-test from a non-trusted external network and record outcomes.
|
||||
|
||||
Success criteria:
|
||||
|
||||
- Every `private-admin` host is inaccessible without VPN/mTLS/allowlisted source.
|
||||
- `public` and `authenticated-public` hosts remain reachable.
|
||||
- Auth endpoints trigger rate-limit/lockout controls under failed-attempt simulation.
|
||||
|
||||
Use `./scripts/retest-external-access.sh` for a repeatable external validation pass.
|
||||
Reference in New Issue
Block a user