security: harden traefik auth flow — fix header spoofing, TLS leak, DERP rate-limit
- Auth-proxy fallback now sets ALL X-authentik-* headers (username, uid, email, name, groups) to prevent client-supplied header spoofing when Authentik is down. Previously only username was set, allowing a malicious client to inject fake X-authentik-groups. - Catch-all IngressRoute restricted to *.viktorbarzin.me only. Non-matching domains no longer get the wildcard cert served (TLS info leak). - Added rate-limit and CrowdSec middleware to catch-all IngressRoute. - Added rate-limit middleware to Headscale DERP IngressRoute. - Rotated auth-proxy basicAuth credentials (bcrypt cost 5 → 12, admin → emergency-admin). - Created Authentik brute-force reputation policy (threshold -5, IP+username).
This commit is contained in:
parent
3d02036a18
commit
0e3c0fb503
3 changed files with 50 additions and 8 deletions
|
|
@ -324,11 +324,17 @@ resource "kubernetes_manifest" "derp_ingress_route" {
|
||||||
name = kubernetes_service.headscale.metadata[0].name
|
name = kubernetes_service.headscale.metadata[0].name
|
||||||
port = 8080
|
port = 8080
|
||||||
}]
|
}]
|
||||||
# Only retry middleware — no CrowdSec, rate limit, anti-AI, error pages
|
# Minimal middleware — retry + rate-limit. No CrowdSec/anti-AI (DERP is a relay protocol)
|
||||||
middlewares = [{
|
middlewares = [
|
||||||
name = "retry"
|
{
|
||||||
namespace = "traefik"
|
name = "retry"
|
||||||
}]
|
namespace = "traefik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "rate-limit"
|
||||||
|
namespace = "traefik"
|
||||||
|
},
|
||||||
|
]
|
||||||
}]
|
}]
|
||||||
tls = {
|
tls = {
|
||||||
secretName = var.tls_secret_name
|
secretName = var.tls_secret_name
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,9 @@ resource "kubernetes_manifest" "tlsstore_default" {
|
||||||
depends_on = [helm_release.traefik, module.tls_secret]
|
depends_on = [helm_release.traefik, module.tls_secret]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Catch-all IngressRoute — serves 404 for unknown hosts (lowest priority)
|
# Catch-all IngressRoute — serves 404 for unmatched *.viktorbarzin.me hosts (lowest priority)
|
||||||
|
# Only matches *.viktorbarzin.me — non-viktorbarzin.me domains get TLS rejection (no matching router)
|
||||||
|
# This prevents leaking the wildcard cert to attackers who point arbitrary domains at our IP
|
||||||
resource "kubernetes_manifest" "ingressroute_catchall" {
|
resource "kubernetes_manifest" "ingressroute_catchall" {
|
||||||
manifest = {
|
manifest = {
|
||||||
apiVersion = "traefik.io/v1alpha1"
|
apiVersion = "traefik.io/v1alpha1"
|
||||||
|
|
@ -170,9 +172,13 @@ resource "kubernetes_manifest" "ingressroute_catchall" {
|
||||||
spec = {
|
spec = {
|
||||||
entryPoints = ["websecure"]
|
entryPoints = ["websecure"]
|
||||||
routes = [{
|
routes = [{
|
||||||
match = "HostRegexp(`.+`)"
|
match = "HostRegexp(`^(.+\\.)?viktorbarzin\\.me$`)"
|
||||||
kind = "Rule"
|
kind = "Rule"
|
||||||
priority = 1
|
priority = 1
|
||||||
|
middlewares = [
|
||||||
|
{ name = "rate-limit", namespace = kubernetes_namespace.traefik.metadata[0].name },
|
||||||
|
{ name = "crowdsec", namespace = kubernetes_namespace.traefik.metadata[0].name },
|
||||||
|
]
|
||||||
services = [{
|
services = [{
|
||||||
name = "error-pages"
|
name = "error-pages"
|
||||||
namespace = kubernetes_namespace.traefik.metadata[0].name
|
namespace = kubernetes_namespace.traefik.metadata[0].name
|
||||||
|
|
|
||||||
|
|
@ -494,9 +494,17 @@ resource "kubernetes_config_map" "auth_proxy_config" {
|
||||||
location @fallback_auth {
|
location @fallback_auth {
|
||||||
auth_basic "Emergency Access";
|
auth_basic "Emergency Access";
|
||||||
auth_basic_user_file /etc/nginx/htpasswd;
|
auth_basic_user_file /etc/nginx/htpasswd;
|
||||||
|
# Set ALL X-authentik-* headers to prevent client-supplied header spoofing.
|
||||||
|
# Without this, a client could inject fake X-authentik-groups and backends
|
||||||
|
# that trust these headers would grant elevated access.
|
||||||
add_header X-authentik-username $remote_user always;
|
add_header X-authentik-username $remote_user always;
|
||||||
|
add_header X-authentik-uid "" always;
|
||||||
|
add_header X-authentik-email "" always;
|
||||||
|
add_header X-authentik-name "" always;
|
||||||
|
add_header X-authentik-groups "" always;
|
||||||
add_header X-Auth-Fallback "true" always;
|
add_header X-Auth-Fallback "true" always;
|
||||||
return 200;
|
root /usr/share/nginx/fallback;
|
||||||
|
try_files /ok =403;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /outpost.goauthentik.io/ {
|
location /outpost.goauthentik.io/ {
|
||||||
|
|
@ -518,6 +526,17 @@ resource "kubernetes_config_map" "auth_proxy_config" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_config_map" "auth_proxy_fallback" {
|
||||||
|
metadata {
|
||||||
|
name = "auth-proxy-fallback"
|
||||||
|
namespace = kubernetes_namespace.traefik.metadata[0].name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"ok" = "authenticated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource "kubernetes_deployment" "auth_proxy" {
|
resource "kubernetes_deployment" "auth_proxy" {
|
||||||
metadata {
|
metadata {
|
||||||
name = "auth-proxy"
|
name = "auth-proxy"
|
||||||
|
|
@ -577,6 +596,11 @@ resource "kubernetes_deployment" "auth_proxy" {
|
||||||
sub_path = "htpasswd"
|
sub_path = "htpasswd"
|
||||||
read_only = true
|
read_only = true
|
||||||
}
|
}
|
||||||
|
volume_mount {
|
||||||
|
name = "fallback"
|
||||||
|
mount_path = "/usr/share/nginx/fallback"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
|
||||||
liveness_probe {
|
liveness_probe {
|
||||||
http_get {
|
http_get {
|
||||||
|
|
@ -618,6 +642,12 @@ resource "kubernetes_deployment" "auth_proxy" {
|
||||||
secret_name = kubernetes_secret.auth_proxy_htpasswd.metadata[0].name
|
secret_name = kubernetes_secret.auth_proxy_htpasswd.metadata[0].name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
volume {
|
||||||
|
name = "fallback"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.auth_proxy_fallback.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue