expose Traefik dashboard on traefik.pivoine.art, vpn-only gated
Previous attempt tried a separate internal-only :8080 entrypoint for pulsenode to reach the API directly - that turned out to 404 no matter what (--api.dashboard=true alone didn't register a working router there, and adding --api=true plus an explicit entrypoint still didn't produce a matching router; verified against a local traefik:v3 container before giving up on that path rather than keep guessing against the live one). Cleaner approach: expose it through the same web/web-secure entrypoints everything else already uses, via a new file-provider router (traefik/dynamic/dashboard.yaml) matching Traefik's own documented self-referencing pattern - Host(`traefik.pivoine.art`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)) routed to the built-in api@internal service, gated by vpn-only same as pulsenode's own router. vpn-only's sourceRange now also includes falcon_network's own subnet (172.18.0.0/16) alongside the tailnet ranges - a container on our own internal docker network is as trusted as a tailnet peer, and without this pulsenode's own request to the API (a container-to-container call, not a VPN-sourced one) would get the same 403 a random internet visitor would. pulsenode's traefik widget now points at https://traefik.pivoine.art/api instead of the internal :8080 attempt.
This commit is contained in:
@@ -12,7 +12,7 @@ groups:
|
|||||||
|
|
||||||
- type: traefik
|
- type: traefik
|
||||||
name: Traefik
|
name: Traefik
|
||||||
apiUrl: http://traefik:8080/api
|
apiUrl: https://traefik.pivoine.art/api
|
||||||
interval: 15s
|
interval: 15s
|
||||||
|
|
||||||
- type: docker
|
- type: docker
|
||||||
|
|||||||
+1
-3
@@ -3,6 +3,7 @@ services:
|
|||||||
image: traefik:v3
|
image: traefik:v3
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
command:
|
command:
|
||||||
|
- "--api=true"
|
||||||
- "--api.dashboard=true"
|
- "--api.dashboard=true"
|
||||||
- "--ping=true"
|
- "--ping=true"
|
||||||
- "--log.level=INFO"
|
- "--log.level=INFO"
|
||||||
@@ -16,9 +17,6 @@ services:
|
|||||||
- "--providers.file.watch=true"
|
- "--providers.file.watch=true"
|
||||||
- "--entrypoints.web.address=:80"
|
- "--entrypoints.web.address=:80"
|
||||||
- "--entrypoints.web-secure.address=:443"
|
- "--entrypoints.web-secure.address=:443"
|
||||||
# Internal-only: not in ports: below, so only reachable from other
|
|
||||||
# containers on falcon_network (i.e. pulsenode's traefik widget).
|
|
||||||
- "--entrypoints.traefik.address=:8080"
|
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.to=web-secure"
|
- "--entrypoints.web.http.redirections.entryPoint.to=web-secure"
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
||||||
- "--entrypoints.web.http.redirections.entryPoint.permanent=true"
|
- "--entrypoints.web.http.redirections.entryPoint.permanent=true"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Exposes Traefik's own API/dashboard (api@internal is a built-in service,
|
||||||
|
# not something defined below) on traefik.pivoine.art, gated by vpn-only.
|
||||||
|
# Path prefixes match Traefik's own documented self-referencing pattern:
|
||||||
|
# /api serves the JSON pulsenode's traefik widget reads, /dashboard serves
|
||||||
|
# the browsable UI.
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
traefik-redirect-web-secure:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
|
||||||
|
routers:
|
||||||
|
traefik-dashboard-web:
|
||||||
|
rule: "Host(`traefik.pivoine.art`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
||||||
|
entrypoints:
|
||||||
|
- web
|
||||||
|
middlewares:
|
||||||
|
- vpn-only@file
|
||||||
|
- traefik-redirect-web-secure
|
||||||
|
service: api@internal
|
||||||
|
|
||||||
|
traefik-dashboard-web-secure:
|
||||||
|
rule: "Host(`traefik.pivoine.art`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
|
||||||
|
entrypoints:
|
||||||
|
- web-secure
|
||||||
|
tls:
|
||||||
|
certResolver: resolver
|
||||||
|
middlewares:
|
||||||
|
- vpn-only@file
|
||||||
|
- security-headers@file
|
||||||
|
- no-index@file
|
||||||
|
service: api@internal
|
||||||
@@ -45,11 +45,15 @@ http:
|
|||||||
burst: 15
|
burst: 15
|
||||||
period: 1s
|
period: 1s
|
||||||
# Only let traffic through whose source IP is inside the Tailscale/Headscale
|
# Only let traffic through whose source IP is inside the Tailscale/Headscale
|
||||||
# mesh (100.64.0.0/10, fd7a:115c:a1e0::/48). Public DNS can still resolve
|
# mesh (100.64.0.0/10, fd7a:115c:a1e0::/48) or on falcon_network itself
|
||||||
# these hostnames, but anyone reaching them over the open internet gets a
|
# (172.18.0.0/16) - the latter so containers on our own internal docker
|
||||||
# 403 — this range is only reachable by actually being a peer on the tailnet.
|
# network (e.g. pulsenode calling this router directly to reach
|
||||||
|
# api@internal) count as trusted the same way a tailnet peer does. Public
|
||||||
|
# DNS can still resolve these hostnames, but anyone reaching them over the
|
||||||
|
# open internet gets a 403.
|
||||||
vpn-only:
|
vpn-only:
|
||||||
ipAllowList:
|
ipAllowList:
|
||||||
sourceRange:
|
sourceRange:
|
||||||
- "100.64.0.0/10"
|
- "100.64.0.0/10"
|
||||||
- "fd7a:115c:a1e0::/48"
|
- "fd7a:115c:a1e0::/48"
|
||||||
|
- "172.18.0.0/16"
|
||||||
|
|||||||
Reference in New Issue
Block a user