fix: restore tree dropped by 6d224861; land stem95su gdrive-sync (10m) [ci skip]
6d224861 came from a --no-checkout worktree whose empty index made the
commit drop every file except two. This restores 05b50d2b's full tree and
correctly adds stacks/stem95su/gdrive-sync.tf + the service-catalog stem95su
entry. Forward-only (parent=6d224861, no force-push); [ci skip] since the
live infra was never applied from the broken commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6d224861c4
commit
fd0f4a0365
1166 changed files with 358546 additions and 0 deletions
113
stacks/authentik/modules/authentik/main.tf
Normal file
113
stacks/authentik/modules/authentik/main.tf
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
variable "tls_secret_name" {}
|
||||
variable "secret_key" {}
|
||||
variable "postgres_password" {}
|
||||
variable "tier" { type = string }
|
||||
variable "redis_host" { type = string }
|
||||
variable "homepage_token" {
|
||||
type = string
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
|
||||
module "tls_secret" {
|
||||
source = "../../../../modules/kubernetes/setup_tls_secret"
|
||||
namespace = kubernetes_namespace.authentik.metadata[0].name
|
||||
tls_secret_name = var.tls_secret_name
|
||||
}
|
||||
|
||||
# The embedded outpost auto-creates an ingress expecting this secret name
|
||||
module "tls_secret_outpost" {
|
||||
source = "../../../../modules/kubernetes/setup_tls_secret"
|
||||
namespace = kubernetes_namespace.authentik.metadata[0].name
|
||||
tls_secret_name = "authentik-outpost-tls"
|
||||
}
|
||||
|
||||
resource "kubernetes_namespace" "authentik" {
|
||||
metadata {
|
||||
name = "authentik"
|
||||
labels = {
|
||||
tier = var.tier
|
||||
"resource-governance/custom-quota" = "true"
|
||||
"keel.sh/enrolled" = "true"
|
||||
}
|
||||
}
|
||||
lifecycle {
|
||||
# KYVERNO_LIFECYCLE_V1: goldilocks-vpa-auto-mode ClusterPolicy stamps this label on every namespace
|
||||
ignore_changes = [metadata[0].labels["goldilocks.fairwinds.com/vpa-update-mode"]]
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_resource_quota" "authentik" {
|
||||
metadata {
|
||||
name = "authentik-quota"
|
||||
namespace = kubernetes_namespace.authentik.metadata[0].name
|
||||
}
|
||||
spec {
|
||||
hard = {
|
||||
"requests.cpu" = "16"
|
||||
"requests.memory" = "16Gi"
|
||||
"limits.memory" = "96Gi"
|
||||
pods = "50"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "helm_release" "authentik" {
|
||||
namespace = kubernetes_namespace.authentik.metadata[0].name
|
||||
create_namespace = true
|
||||
name = "goauthentik"
|
||||
|
||||
repository = "https://charts.goauthentik.io/"
|
||||
chart = "authentik"
|
||||
# version = "2025.10.3"
|
||||
# version = "2025.12.4"
|
||||
version = "2026.2.2"
|
||||
atomic = true
|
||||
timeout = 6000
|
||||
|
||||
values = [templatefile("${path.module}/values.yaml", { postgres_password = var.postgres_password, secret_key = var.secret_key })]
|
||||
}
|
||||
|
||||
|
||||
module "ingress" {
|
||||
source = "../../../../modules/kubernetes/ingress_factory"
|
||||
# Authentik's own UI cannot be gated by Authentik forward-auth — that
|
||||
# creates a chicken-and-egg loop (users can't reach the login page).
|
||||
# auth = "none": Authentik UI cannot be gated by Authentik forward-auth (chicken-and-egg loop prevents login).
|
||||
auth = "none"
|
||||
dns_type = "proxied"
|
||||
namespace = kubernetes_namespace.authentik.metadata[0].name
|
||||
name = "authentik"
|
||||
service_name = "goauthentik-server"
|
||||
tls_secret_name = var.tls_secret_name
|
||||
anti_ai_scraping = false
|
||||
extra_annotations = {
|
||||
"gethomepage.dev/enabled" = "true"
|
||||
"gethomepage.dev/name" = "Authentik"
|
||||
"gethomepage.dev/description" = "Identity provider"
|
||||
"gethomepage.dev/icon" = "authentik.png"
|
||||
"gethomepage.dev/group" = "Identity & Security"
|
||||
"gethomepage.dev/pod-selector" = ""
|
||||
"gethomepage.dev/widget.type" = "authentik"
|
||||
"gethomepage.dev/widget.url" = "http://goauthentik-server.authentik.svc.cluster.local"
|
||||
"gethomepage.dev/widget.key" = var.homepage_token
|
||||
}
|
||||
}
|
||||
|
||||
module "ingress-outpost" {
|
||||
source = "../../../../modules/kubernetes/ingress_factory"
|
||||
# Authentik forward-auth outpost callback path — protecting this with
|
||||
# forward-auth would loop the outpost back onto itself.
|
||||
# auth = "none": Authentik outpost callback path for forward-auth flow; protecting with forward-auth creates circular dependency.
|
||||
auth = "none"
|
||||
namespace = kubernetes_namespace.authentik.metadata[0].name
|
||||
name = "authentik-outpost"
|
||||
host = "authentik"
|
||||
service_name = "ak-outpost-authentik-embedded-outpost"
|
||||
port = 9000
|
||||
ingress_path = ["/outpost.goauthentik.io"]
|
||||
tls_secret_name = var.tls_secret_name
|
||||
anti_ai_scraping = false
|
||||
exclude_crowdsec = true
|
||||
}
|
||||
14
stacks/authentik/modules/authentik/pgbouncer.ini
Normal file
14
stacks/authentik/modules/authentik/pgbouncer.ini
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[databases]
|
||||
authentik = host=postgresql.dbaas port=5432 dbname=authentik user=authentik password=${password}
|
||||
|
||||
[pgbouncer]
|
||||
listen_addr = 0.0.0.0
|
||||
listen_port = 6432
|
||||
auth_type = md5
|
||||
auth_file = /etc/pgbouncer/userlist.txt
|
||||
pool_mode = session
|
||||
max_client_conn = 200
|
||||
default_pool_size = 20
|
||||
reserve_pool_size = 5
|
||||
reserve_pool_timeout = 5
|
||||
ignore_startup_parameters = extra_float_digits
|
||||
207
stacks/authentik/modules/authentik/pgbouncer.tf
Normal file
207
stacks/authentik/modules/authentik/pgbouncer.tf
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
resource "kubernetes_config_map" "pgbouncer_config" {
|
||||
metadata {
|
||||
name = "pgbouncer-config"
|
||||
namespace = "authentik"
|
||||
}
|
||||
|
||||
data = {
|
||||
"pgbouncer.ini" = templatefile("${path.module}/pgbouncer.ini", { password = var.postgres_password })
|
||||
}
|
||||
}
|
||||
|
||||
# --- 2️⃣ Secret for user credentials ---
|
||||
resource "kubernetes_secret" "pgbouncer_auth" {
|
||||
metadata {
|
||||
name = "pgbouncer-auth"
|
||||
namespace = "authentik"
|
||||
}
|
||||
|
||||
data = {
|
||||
"userlist.txt" = templatefile("${path.module}/userlist.txt", { password = var.postgres_password })
|
||||
}
|
||||
|
||||
type = "Opaque"
|
||||
}
|
||||
|
||||
# --- 3️⃣ Deployment ---
|
||||
resource "kubernetes_deployment" "pgbouncer" {
|
||||
metadata {
|
||||
name = "pgbouncer"
|
||||
namespace = "authentik"
|
||||
labels = {
|
||||
app = "pgbouncer"
|
||||
tier = var.tier
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
replicas = 3
|
||||
|
||||
selector {
|
||||
match_labels = {
|
||||
app = "pgbouncer"
|
||||
}
|
||||
}
|
||||
|
||||
template {
|
||||
metadata {
|
||||
labels = {
|
||||
app = "pgbouncer"
|
||||
}
|
||||
}
|
||||
|
||||
spec {
|
||||
affinity {
|
||||
pod_anti_affinity {
|
||||
required_during_scheduling_ignored_during_execution {
|
||||
label_selector {
|
||||
match_expressions {
|
||||
key = "component"
|
||||
operator = "In"
|
||||
values = ["server"]
|
||||
}
|
||||
}
|
||||
topology_key = "kubernetes.io/hostname"
|
||||
}
|
||||
}
|
||||
}
|
||||
container {
|
||||
name = "pgbouncer"
|
||||
image = "edoburu/pgbouncer:latest"
|
||||
# `:latest` tag — keep `Always` so pod restarts pick up upstream
|
||||
# updates. The previous `IfNotPresent` value was declared at module
|
||||
# creation but the live cluster has reconciled to `Always` (likely
|
||||
# via a Helm/operator default). Match reality to drop the drift.
|
||||
image_pull_policy = "Always"
|
||||
|
||||
port {
|
||||
container_port = 6432
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "50m"
|
||||
memory = "128Mi"
|
||||
}
|
||||
limits = {
|
||||
memory = "512Mi"
|
||||
}
|
||||
}
|
||||
|
||||
readiness_probe {
|
||||
tcp_socket {
|
||||
port = 6432
|
||||
}
|
||||
initial_delay_seconds = 5
|
||||
period_seconds = 10
|
||||
timeout_seconds = 3
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
tcp_socket {
|
||||
port = 6432
|
||||
}
|
||||
initial_delay_seconds = 30
|
||||
period_seconds = 30
|
||||
timeout_seconds = 5
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
volume_mount {
|
||||
name = "config"
|
||||
mount_path = "/etc/pgbouncer/pgbouncer.ini"
|
||||
sub_path = "pgbouncer.ini"
|
||||
}
|
||||
|
||||
volume_mount {
|
||||
name = "auth"
|
||||
mount_path = "/etc/pgbouncer/userlist.txt"
|
||||
sub_path = "userlist.txt"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "DATABASES_AUTHENTIK"
|
||||
value = "host=postgres port=5432 dbname=authentik user=authentik password=${var.postgres_password}"
|
||||
}
|
||||
}
|
||||
|
||||
volume {
|
||||
name = "config"
|
||||
config_map {
|
||||
name = kubernetes_config_map.pgbouncer_config.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
volume {
|
||||
name = "auth"
|
||||
secret {
|
||||
secret_name = kubernetes_secret.pgbouncer_auth.metadata[0].name
|
||||
}
|
||||
}
|
||||
dns_config {
|
||||
option {
|
||||
name = "ndots"
|
||||
value = "2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
depends_on = [kubernetes_secret.pgbouncer_auth]
|
||||
lifecycle {
|
||||
# KYVERNO_LIFECYCLE_V1: Kyverno admission webhook mutates dns_config with ndots=2
|
||||
ignore_changes = [
|
||||
spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1
|
||||
metadata[0].annotations["keel.sh/policy"],
|
||||
metadata[0].annotations["keel.sh/trigger"],
|
||||
metadata[0].annotations["keel.sh/pollSchedule"], # KYVERNO_LIFECYCLE_V2
|
||||
metadata[0].annotations["keel.sh/match-tag"],
|
||||
spec[0].template[0].spec[0].container[0].image, # KEEL_IGNORE_IMAGE — Keel manages tag updates
|
||||
metadata[0].annotations["kubernetes.io/change-cause"],
|
||||
metadata[0].annotations["deployment.kubernetes.io/revision"],
|
||||
spec[0].template[0].metadata[0].annotations["keel.sh/update-time"], # KEEL_LIFECYCLE_V1
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# --- 3b️⃣ PodDisruptionBudget ---
|
||||
# Protects auth against simultaneous node drains. With 3 replicas and
|
||||
# minAvailable=2, a single drain rolls cleanly; a simultaneous two-node
|
||||
# outage is correctly blocked.
|
||||
resource "kubernetes_pod_disruption_budget_v1" "pgbouncer" {
|
||||
metadata {
|
||||
name = "pgbouncer"
|
||||
namespace = "authentik"
|
||||
}
|
||||
spec {
|
||||
min_available = 2
|
||||
selector {
|
||||
match_labels = {
|
||||
app = "pgbouncer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 4️⃣ Service ---
|
||||
resource "kubernetes_service" "pgbouncer" {
|
||||
metadata {
|
||||
name = "pgbouncer"
|
||||
namespace = "authentik"
|
||||
}
|
||||
|
||||
spec {
|
||||
selector = {
|
||||
app = "pgbouncer"
|
||||
}
|
||||
|
||||
port {
|
||||
port = 6432
|
||||
target_port = 6432
|
||||
protocol = "TCP"
|
||||
}
|
||||
|
||||
type = "ClusterIP"
|
||||
}
|
||||
}
|
||||
1
stacks/authentik/modules/authentik/userlist.txt
Normal file
1
stacks/authentik/modules/authentik/userlist.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
"authentik" "${password}"
|
||||
113
stacks/authentik/modules/authentik/values.yaml
Normal file
113
stacks/authentik/modules/authentik/values.yaml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
authentik:
|
||||
log_level: warning
|
||||
# log_level: trace
|
||||
secret_key: ""
|
||||
existingSecret:
|
||||
secretName: "goauthentik"
|
||||
# This sends anonymous usage-data, stack traces on errors and
|
||||
# performance data to authentik.error-reporting.a7k.io, and is fully opt-in
|
||||
error_reporting:
|
||||
enabled: false
|
||||
postgresql:
|
||||
# host: postgresql.dbaas
|
||||
host: pgbouncer.authentik
|
||||
port: 6432
|
||||
user: authentik
|
||||
password: ""
|
||||
# Persistent client-side connections (safe with PgBouncer session mode;
|
||||
# must be < pgbouncer server_idle_timeout=600s). Cuts Django connection
|
||||
# setup overhead off the ~70 sequential ORM ops per flow stage.
|
||||
conn_max_age: 60
|
||||
conn_health_checks: true
|
||||
cache:
|
||||
# Cache flow plans for 30m and policy evaluations for 15m. Authentik 2026.2
|
||||
# moved cache storage from Redis to Postgres, so a TTL hit is still a
|
||||
# SELECT — but a single indexed lookup beats re-evaluating PolicyBindings.
|
||||
timeout_flows: 1800
|
||||
timeout_policies: 900
|
||||
web:
|
||||
# Gunicorn: 3 workers × 4 threads per server pod (default 2×4).
|
||||
# Pairs with the server memory bump to 2Gi (each worker preloads Django ~500Mi).
|
||||
workers: 3
|
||||
threads: 4
|
||||
worker:
|
||||
# Celery-equivalent worker threads per pod (default 2, renamed from
|
||||
# AUTHENTIK_WORKER__CONCURRENCY in 2025.8).
|
||||
threads: 4
|
||||
|
||||
server:
|
||||
replicas: 3
|
||||
# Anonymous Django sessions (no completed login: bots, healthcheckers,
|
||||
# partial flows) expire in 2h. Default is days=1. Once login completes,
|
||||
# UserLoginStage.session_duration takes over via request.session.set_expiry.
|
||||
# Injected via server.env (not authentik.sessions.*) because we use
|
||||
# authentik.existingSecret.secretName, which makes the chart skip
|
||||
# rendering the AUTHENTIK_* secret — so the values block doesn't reach env.
|
||||
env:
|
||||
- name: AUTHENTIK_SESSIONS__UNAUTHENTICATED_AGE
|
||||
value: "hours=2"
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 1.5Gi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: server
|
||||
ingress:
|
||||
enabled: false
|
||||
# hosts:
|
||||
# - authentik.viktorbarzin.me
|
||||
podAnnotations:
|
||||
diun.enable: true
|
||||
diun.include_tags: "^202[0-9].[0-9]+.*$" # no need to annotate the worker as it uses the same image
|
||||
pdb:
|
||||
enabled: true
|
||||
minAvailable: 2
|
||||
global:
|
||||
addPrometheusAnnotations: true
|
||||
|
||||
worker:
|
||||
# 2 replicas: workers handle background tasks (LDAP sync, email,
|
||||
# certificate renewal) — no user-facing traffic, so 2-of-3 isn't
|
||||
# needed for availability. Drop saves ~100m sustained CPU.
|
||||
replicas: 2
|
||||
# Same unauthenticated_age cap as server — both the server (Django session
|
||||
# middleware) and worker (cleanup tasks) need to see the value.
|
||||
env:
|
||||
- name: AUTHENTIK_SESSIONS__UNAUTHENTICATED_AGE
|
||||
value: "hours=2"
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 1.5Gi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: worker
|
||||
pdb:
|
||||
enabled: true
|
||||
maxUnavailable: 1
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
Loading…
Add table
Add a link
Reference in a new issue