diff --git a/kubernetes/apps/authentik/blueprints-configmap.yaml b/kubernetes/apps/authentik/blueprints-configmap.yaml index 0d68e315..0487290e 100644 --- a/kubernetes/apps/authentik/blueprints-configmap.yaml +++ b/kubernetes/apps/authentik/blueprints-configmap.yaml @@ -545,6 +545,88 @@ data: kubernetes_ingress_secret_name: "authentik-outpost-tls" kubernetes_json_patches: null + # ------------------------------------------------------------------ + # prometheus gate + # + # Prometheus (prometheus.renner.uno, monitoring ns, :9090) had no auth + # of its own. Its admin API is disabled (read-only), but the full 90d + # metrics DB is an infra-recon oracle — hostnames, IPs, service + # inventory (2026-07-10 audit). Same proxy-outpost pattern as + # alertmanager-gate above; reuses the shared "Monitoring Access" group + # (created generically for exactly this). Grafana is unaffected — it + # queries the ClusterIP Service, not this ingress. + - id: prometheus-gate-provider + model: authentik_providers_proxy.proxyprovider + identifiers: + name: prometheus-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-prometheus.monitoring.svc.cluster.local:9090 + internal_host_ssl_validation: false + external_host: https://prometheus.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 Prometheus UI is a plain React SPA with no + # service worker and no manifest (verified 2026-07-10 — index.html + # has no serviceWorker/manifest/webmanifest refs), same as the + # Alertmanager UI, so no credentials:'omit' PWA fetches to carve out. + + - model: authentik_core.application + identifiers: + slug: prometheus-gate + state: present + attrs: + name: Prometheus (Gate) + slug: prometheus-gate + provider: !KeyOf prometheus-gate-provider + meta_launch_url: https://prometheus.renner.uno + meta_description: "Authentik-gated Prometheus" + + # Only the Monitoring Access group passes the gate (shared with + # alertmanager-gate). + - model: authentik_policies.policybinding + identifiers: + target: !Find [authentik_core.application, [slug, prometheus-gate]] + group: !KeyOf monitoring-access-group + order: 0 + state: present + attrs: + enabled: true + negate: false + failure_result: false + + - model: authentik_outposts.outpost + identifiers: + name: prometheus-gate + state: present + attrs: + type: proxy + service_connection: !KeyOf k8s-conn + providers: + - !KeyOf prometheus-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 diff --git a/kubernetes/apps/authentik/kustomization.yaml b/kubernetes/apps/authentik/kustomization.yaml index 5c1c9c08..3e79f19b 100644 --- a/kubernetes/apps/authentik/kustomization.yaml +++ b/kubernetes/apps/authentik/kustomization.yaml @@ -10,6 +10,7 @@ resources: - vscode-gate-egress.yaml - shelly-manager-gate-egress.yaml - alertmanager-gate-egress.yaml + - prometheus-gate-egress.yaml - outpost-servicemonitors.yaml - ldap-outpost-cert.yaml - ldap-outpost-lb.yaml diff --git a/kubernetes/apps/authentik/prometheus-gate-egress.yaml b/kubernetes/apps/authentik/prometheus-gate-egress.yaml new file mode 100644 index 00000000..411c46e1 --- /dev/null +++ b/kubernetes/apps/authentik/prometheus-gate-egress.yaml @@ -0,0 +1,24 @@ +--- +# Allow the dedicated prometheus-gate outpost to egress into the monitoring +# namespace and reach Prometheus (:9090). Without this, the authentik +# 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. +# Mirrors alertmanager-gate-egress.yaml. +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: prometheus-gate-egress + namespace: authentik +spec: + endpointSelector: + matchLabels: + goauthentik.io/outpost-name: prometheus-gate + egress: + - toEndpoints: + - matchLabels: + "k8s:io.kubernetes.pod.namespace": monitoring + app.kubernetes.io/name: prometheus + toPorts: + - ports: + - port: "9090" + protocol: TCP diff --git a/kubernetes/apps/monitoring/helmrelease-prometheus.yaml b/kubernetes/apps/monitoring/helmrelease-prometheus.yaml index 0a8ff976..c09671f9 100644 --- a/kubernetes/apps/monitoring/helmrelease-prometheus.yaml +++ b/kubernetes/apps/monitoring/helmrelease-prometheus.yaml @@ -33,21 +33,24 @@ spec: values: # Prometheus prometheus: - # LAN-only ingress at prometheus.renner.uno. No - # external-dns/cloudflare label, so only external-dns-unifi - # publishes the hostname — resolves on LAN, NXDOMAIN from the - # public internet. + # prometheus.renner.uno is served by a standalone, Authentik-gated + # Ingress (prometheus-gate-ingress.yaml), NOT this Helm-generated one. + # Prometheus had no auth of its own — its full 90d metrics DB is an + # infra-recon oracle (hostnames, IPs, service inventory). Gated behind + # the prometheus-gate proxy outpost, same as alertmanager-gate (#146). + # The chart Ingress is disabled here — two Ingresses for one host would + # conflict. tls (prometheus-tls) and externalUrl below are unchanged; + # the standalone Ingress reuses the same secret. + # + # Grafana is unaffected: its datasource targets the ClusterIP Service + # (kube-prometheus-stack-prometheus.monitoring:9090), not this ingress. + # Blackbox uptime probes that target https://prometheus.renner.uno + # follow the gate's 302 to the Authentik login (200), so they stay + # green — but now measure Authentik reachability, not Prometheus. Same + # already-true for alerts.renner.uno since #146. Repointing those probes + # to an internal endpoint is a separate follow-up. ingress: - enabled: true - ingressClassName: traefik - annotations: - cert-manager.io/cluster-issuer: letsencrypt-cluster - hosts: - - prometheus.renner.uno - tls: - - secretName: prometheus-tls - hosts: - - prometheus.renner.uno + enabled: false prometheusSpec: # Rewrites the in-cluster URL embedded in alert generatorURLs and # status pages so click-through from emails works. diff --git a/kubernetes/apps/monitoring/kustomization.yaml b/kubernetes/apps/monitoring/kustomization.yaml index ce0f57bb..5d58c4c1 100644 --- a/kubernetes/apps/monitoring/kustomization.yaml +++ b/kubernetes/apps/monitoring/kustomization.yaml @@ -14,6 +14,8 @@ resources: - loki-ingress.yaml - alertmanager-gate-service.yaml - alertmanager-gate-ingress.yaml + - prometheus-gate-service.yaml + - prometheus-gate-ingress.yaml - dashboards/ # prometheusrule-garage.yaml lives in ./monitoring-rules/ and is applied by a # separate Flux Kustomization that dependsOn `monitoring` — otherwise the diff --git a/kubernetes/apps/monitoring/prometheus-gate-ingress.yaml b/kubernetes/apps/monitoring/prometheus-gate-ingress.yaml new file mode 100644 index 00000000..ad5b15b2 --- /dev/null +++ b/kubernetes/apps/monitoring/prometheus-gate-ingress.yaml @@ -0,0 +1,39 @@ +--- +# prometheus.renner.uno — Prometheus, gated behind Authentik. +# +# Prometheus has no auth of its own; its admin API is disabled (read-only), +# but the full 90d metrics DB exposes hostnames, IPs and service inventory +# to any unauthenticated LAN client. This Ingress backends the Authentik +# prometheus-gate proxy outpost (ExternalName → authentik ns); the +# monitoring CNP allows only that outpost to reach :9090. Replaces the +# Helm-chart-generated Prometheus Ingress (disabled via +# prometheus.ingress.enabled=false in helmrelease-prometheus.yaml) — two +# Ingresses for the same host would conflict. Mirrors +# alertmanager-gate-ingress.yaml. +# +# Grafana is unaffected: its datasource targets the ClusterIP Service +# (kube-prometheus-stack-prometheus:9090), not this hostname. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: prometheus-gate + namespace: monitoring + annotations: + cert-manager.io/cluster-issuer: letsencrypt-cluster +spec: + ingressClassName: traefik + rules: + - host: prometheus.renner.uno + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: ak-outpost-prometheus-gate + port: + number: 9000 + tls: + - secretName: prometheus-tls + hosts: + - prometheus.renner.uno diff --git a/kubernetes/apps/monitoring/prometheus-gate-service.yaml b/kubernetes/apps/monitoring/prometheus-gate-service.yaml new file mode 100644 index 00000000..43f33396 --- /dev/null +++ b/kubernetes/apps/monitoring/prometheus-gate-service.yaml @@ -0,0 +1,22 @@ +--- +# ExternalName mirror so the monitoring-namespace Prometheus gate Ingress +# can backend to the prometheus-gate outpost that Authentik deploys into the +# authentik namespace (cross-namespace Service refs aren't supported in a +# standard Ingress). Mirrors ak-outpost-alertmanager-gate. +# +# Traffic path: +# browser → traefik (prometheus.renner.uno) +# → ak-outpost-prometheus-gate (ExternalName → authentik ns) +# → outpost validates the Authentik session +# → http://kube-prometheus-stack-prometheus.monitoring.svc:9090 +apiVersion: v1 +kind: Service +metadata: + name: ak-outpost-prometheus-gate + namespace: monitoring +spec: + type: ExternalName + externalName: ak-outpost-prometheus-gate.authentik.svc.cluster.local + ports: + - port: 9000 + targetPort: 9000 diff --git a/kubernetes/infrastructure/network-policies/monitoring-ns.yaml b/kubernetes/infrastructure/network-policies/monitoring-ns.yaml index 40c2c604..0571ee6d 100644 --- a/kubernetes/infrastructure/network-policies/monitoring-ns.yaml +++ b/kubernetes/infrastructure/network-policies/monitoring-ns.yaml @@ -39,6 +39,17 @@ spec: - ports: - port: "9093" protocol: TCP + # The Authentik prometheus-gate outpost forwards gated browser traffic + # to Prometheus :9090. Same scoping as alertmanager-gate above. Matching + # egress: authentik/prometheus-gate-egress.yaml. + - fromEndpoints: + - matchLabels: + "k8s:io.kubernetes.pod.namespace": authentik + goauthentik.io/outpost-name: prometheus-gate + toPorts: + - ports: + - port: "9090" + 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