Gate Alertmanager behind Authentik #146

Merged
k.renner merged 1 commits from fix/alertmanager-authentik-gate into master 2026-07-10 15:31:56 +00:00
8 changed files with 224 additions and 12 deletions
@@ -0,0 +1,25 @@
---
# Allow the dedicated alertmanager-gate outpost to egress into the
# monitoring namespace and reach Alertmanager (:9093). Without this, the
# authentik-namespace baseline CNP has no egress rule to monitoring, so the
# gate outpost's forwarded requests are dropped. Scoped to the gate
# outpost's pod label so no other authentik component can reach it.
# Mirrors shelly-manager-gate-egress.yaml.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: alertmanager-gate-egress
namespace: authentik
spec:
endpointSelector:
matchLabels:
goauthentik.io/outpost-name: alertmanager-gate
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": monitoring
app.kubernetes.io/name: alertmanager
toPorts:
- ports:
- port: "9093"
protocol: TCP
@@ -433,6 +433,118 @@ data:
kubernetes_ingress_secret_name: "authentik-outpost-tls"
kubernetes_json_patches: null
# ------------------------------------------------------------------
# alertmanager gate
#
# Alertmanager (alerts.renner.uno, monitoring ns, :9093) had no auth
# of its own: its v2 API is writable, so any unauthenticated LAN
# client could POST /api/v2/silences and suppress ALL alerting —
# a tamper capability, not just disclosure (2026-07-10 audit).
#
# Same proxy-outpost pattern as the gates above. Traefik routes
# alerts.renner.uno only to this outpost (the Helm-generated ingress
# is disabled; a standalone gate ingress replaces it), and the
# monitoring CNP allows ONLY this outpost to reach :9093 from the
# authentik namespace (see monitoring-ns.yaml +
# alertmanager-gate-egress.yaml). Prometheus's own alerting path is
# untouched — it posts to the ClusterIP Service
# (kube-prometheus-stack-alertmanager:9093), not through the ingress.
#
# implicit-consent flow, NOT passkey step-up: Alertmanager is a
# frequently-checked monitoring UI, and requiring an authenticated
# Authentik session already closes the unauthenticated-LAN exposure.
# Passkey re-auth on every visit would be disproportionate friction
# for a dashboard (paperless-gate uses implicit-consent for the same
# reason). Bump to passkey-stepup-authorization if the write API
# warrants it.
- id: monitoring-access-group
model: authentik_core.group
identifiers:
name: Monitoring Access
state: present
attrs:
is_superuser: false
# Git owns membership, as with the other gate groups. Named
# generically ("Monitoring") so the same allowlist can gate the
# still-deferred Prometheus UI when that is done. Add/remove
# members HERE, not in the UI — blueprint apply is authoritative.
users:
- !Find [authentik_core.user, [username, k.renner]]
- id: alertmanager-gate-provider
model: authentik_providers_proxy.proxyprovider
identifiers:
name: alertmanager-gate
state: present
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
mode: proxy
internal_host: http://kube-prometheus-stack-alertmanager.monitoring.svc.cluster.local:9093
internal_host_ssl_validation: false
external_host: https://alerts.renner.uno
cookie_domain: renner.uno
access_token_validity: hours=24
refresh_token_validity: days=30
intercept_header_auth: true
# No skip_path_regex: the Alertmanager UI is a plain Elm SPA with
# no service worker and no manifest (verified 2026-07-10 — the
# index.html has no serviceWorker/manifest/webmanifest refs), so
# it has none of the credentials:'omit' PWA fetches that forced
# skip carve-outs on paperless/vscode and killed the
# music-assistant gate. Everything is a same-origin credentialed
# fetch and stays gated.
- model: authentik_core.application
identifiers:
slug: alertmanager-gate
state: present
attrs:
name: Alertmanager (Gate)
slug: alertmanager-gate
provider: !KeyOf alertmanager-gate-provider
meta_launch_url: https://alerts.renner.uno
meta_description: "Authentik-gated Alertmanager"
# Only the Monitoring Access group passes the gate.
- model: authentik_policies.policybinding
identifiers:
target: !Find [authentik_core.application, [slug, alertmanager-gate]]
group: !KeyOf monitoring-access-group
order: 0
state: present
attrs:
enabled: true
negate: false
failure_result: false
- model: authentik_outposts.outpost
identifiers:
name: alertmanager-gate
state: present
attrs:
type: proxy
service_connection: !KeyOf k8s-conn
providers:
- !KeyOf alertmanager-gate-provider
config:
log_level: info
authentik_host: "http://authentik-server.authentik.svc.cluster.local"
authentik_host_insecure: false
authentik_host_browser: "https://auth.renner.uno/"
refresh_interval: "minutes=5"
container_image: null
object_naming_template: "ak-outpost-%(name)s"
kubernetes_namespace: authentik
kubernetes_replicas: 1
kubernetes_service_type: ClusterIP
kubernetes_disabled_components: ["ingress"]
kubernetes_image_pull_secrets: []
kubernetes_ingress_annotations: {}
kubernetes_ingress_class_name: null
kubernetes_ingress_secret_name: "authentik-outpost-tls"
kubernetes_json_patches: null
- model: authentik_outposts.outpost
identifiers:
name: authentik Embedded Outpost
@@ -9,6 +9,7 @@ resources:
- paperless-gate-egress.yaml
- vscode-gate-egress.yaml
- shelly-manager-gate-egress.yaml
- alertmanager-gate-egress.yaml
- outpost-servicemonitors.yaml
- ldap-outpost-cert.yaml
- ldap-outpost-lb.yaml
@@ -0,0 +1,39 @@
---
# alerts.renner.uno — Alertmanager, gated behind Authentik.
#
# Alertmanager has no auth of its own and its v2 API is writable
# (POST /api/v2/silences can suppress all alerting), so Traefik must not
# route to it directly. This Ingress backends the Authentik
# alertmanager-gate proxy outpost (ExternalName → authentik ns); the
# monitoring CNP allows only that outpost to reach :9093. Replaces the
# Helm-chart-generated Alertmanager Ingress (disabled via
# alertmanager.ingress.enabled=false in helmrelease-prometheus.yaml) —
# two Ingresses for the same host would conflict. Standalone-manifest
# pattern mirrors loki-ingress.yaml.
#
# Prometheus's alerting path is unaffected: it posts to the ClusterIP
# Service (kube-prometheus-stack-alertmanager:9093), not this hostname.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: alertmanager-gate
namespace: monitoring
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
spec:
ingressClassName: traefik
rules:
- host: alerts.renner.uno
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ak-outpost-alertmanager-gate
port:
number: 9000
tls:
- secretName: alertmanager-tls
hosts:
- alerts.renner.uno
@@ -0,0 +1,22 @@
---
# ExternalName mirror so the monitoring-namespace alertmanager gate Ingress
# can backend to the alertmanager-gate outpost that Authentik deploys into
# the authentik namespace (cross-namespace Service refs aren't supported in
# a standard Ingress). Mirrors ak-outpost-shelly-manager-gate.
#
# Traffic path:
# browser → traefik (alerts.renner.uno)
# → ak-outpost-alertmanager-gate (ExternalName → authentik ns)
# → outpost validates the Authentik session
# → http://kube-prometheus-stack-alertmanager.monitoring.svc:9093
apiVersion: v1
kind: Service
metadata:
name: ak-outpost-alertmanager-gate
namespace: monitoring
spec:
type: ExternalName
externalName: ak-outpost-alertmanager-gate.authentik.svc.cluster.local
ports:
- port: 9000
targetPort: 9000
@@ -246,19 +246,15 @@ spec:
# Alertmanager
alertmanager:
# LAN-only ingress at alerts.renner.uno. Same pattern as
# Prometheus above: unifi-DNS only, not Cloudflare-published.
# alerts.renner.uno is served by a standalone, Authentik-gated Ingress
# (alertmanager-gate-ingress.yaml) instead of this Helm-generated one.
# Alertmanager has no auth and its v2 API is writable (silence-all),
# so it must sit behind the alertmanager-gate proxy outpost. The
# chart Ingress is disabled here — two Ingresses for the same host
# would conflict. The tls secret (alertmanager-tls) and externalUrl
# below are unchanged; the standalone Ingress reuses the same secret.
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-cluster
hosts:
- alerts.renner.uno
tls:
- secretName: alertmanager-tls
hosts:
- alerts.renner.uno
enabled: false
alertmanagerSpec:
# Embedded in the AlertmanagerURL footer of every email plus
# the "Source" link of every grouped alert, so the email link
@@ -12,6 +12,8 @@ resources:
- loki-basic-auth.sops.yaml
- loki-basic-auth-middleware.yaml
- loki-ingress.yaml
- alertmanager-gate-service.yaml
- alertmanager-gate-ingress.yaml
- dashboards/
# prometheusrule-garage.yaml lives in ./monitoring-rules/ and is applied by a
# separate Flux Kustomization that dependsOn `monitoring` — otherwise the
@@ -24,6 +24,21 @@ spec:
- fromEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": flux-system
# The Authentik alertmanager-gate outpost forwards gated browser traffic
# to Alertmanager :9093. Scoped to the outpost's pod label + port, so no
# other authentik pod gains monitoring-namespace reach. Matching egress
# rule: authentik/alertmanager-gate-egress.yaml. NOTE: this CNP's
# endpointSelector is {} (namespace-wide), so this :9093 grant reaches
# every monitoring pod listening on 9093 — today only Alertmanager; the
# egress side is what actually confines the outpost to Alertmanager.
- fromEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": authentik
goauthentik.io/outpost-name: alertmanager-gate
toPorts:
- ports:
- port: "9093"
protocol: TCP
# Kubelet probes + control-plane (PrometheusRule/ServiceMonitor admission
# webhooks via kube-prometheus-stack-operator are called by kube-apiserver).
# Also covers external alloy-syslog traffic — ETP=Cluster on the LB SNATs