diff --git a/Caddyfile b/Caddyfile index 0e624b2..aa2f4bd 100644 --- a/Caddyfile +++ b/Caddyfile @@ -36,6 +36,25 @@ remote_ip 192.168.1.0/24 172.16.0.0/12 100.64.0.0/10 127.0.0.1 } +# Shared-link gate — external users visit ?k={$SHARE_KEY} once to get a 30-day cookie. +# Internal IPs and tailnet clients pass through unconditionally. +# Usage: import share_gate (replaces import internal_only on shared services) +(share_gate) { + route { + @has_key query k={$SHARE_KEY} + handle @has_key { + header Set-Cookie "share_access={$SHARE_KEY}; Path=/; Max-Age=2592000; HttpOnly; Secure; SameSite=Strict" + redir {http.request.uri.path} 302 + } + + @deny_external { + not remote_ip 192.168.1.0/24 172.16.0.0/12 100.64.0.0/10 127.0.0.1 + not expression `{http.request.cookie.share_access} == "{$SHARE_KEY}"` + } + respond @deny_external "Access denied" 403 + } +} + # ============================================================= # FOUNDRY VTT — public # ============================================================= @@ -219,12 +238,12 @@ stash.ginnoir.com { # serve node (roms-share..ts.net), NOT public exposure here. # ============================================================= roms.ginnoir.com { - import internal_only + import share_gate reverse_proxy romm:8080 } romhacks.ginnoir.com { - import internal_only + import share_gate root * /srv/romhacks-wiki file_server } diff --git a/stacks/proxy/stack.env b/stacks/proxy/stack.env index 5011ca4..f2a2d56 100644 --- a/stacks/proxy/stack.env +++ b/stacks/proxy/stack.env @@ -2,3 +2,7 @@ # Cloudflare API token for Caddy's DNS-01 TLS challenge. # Scoped token: Zone > DNS > Edit on the ginnoir.com zone only. CF_API_TOKEN=cfut_ijIT9HDjXWKgd0dtnyLqxv8L3deB7hSDivSqquwp905a6c51 + +# Shared-link gate key — external users visit ?k= once to get a 30-day cookie. +# Rotate: change value here, push, Portainer redeploys proxy stack, post new URL in Discord. +SHARE_KEY=8e5d9f1ca20a448b66ed171aea97d172