From d988c8831a4f87235ee1ac5a1d1c28f7d66d033d Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Sat, 5 Sep 2026 11:51:27 +0200 Subject: [PATCH] Introduce CI/CD --- .gitea/workflows/deploy.yml | 54 ++ .gitignore | 8 + README.md | 31 +- authentik-media-redirect.yaml | 29 + authentik-namespace.yaml | 4 + authentik-outpost-path.yaml | 19 + authentik-secrets.env.example | 4 + ceph-object-user.yaml | 8 + ci-deployer.yaml | 79 +++ create-ci-kubeconfig.sh | 15 + install.sh | 107 +++ render.sh | 10 + test.sh | 9 + values-default.yaml | 1170 +++++++++++++++++++++++++++++++++ values.yaml | 246 +++++++ 15 files changed, 1792 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 authentik-media-redirect.yaml create mode 100644 authentik-namespace.yaml create mode 100644 authentik-outpost-path.yaml create mode 100644 authentik-secrets.env.example create mode 100644 ceph-object-user.yaml create mode 100644 ci-deployer.yaml create mode 100755 create-ci-kubeconfig.sh create mode 100755 install.sh create mode 100755 render.sh create mode 100755 test.sh create mode 100644 values-default.yaml create mode 100644 values.yaml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..b23059f --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,54 @@ +name: Validate and deploy Authentik +on: {push: {}, pull_request: {}} +env: + AUTHENTIK_CHART_VERSION: 2026.8.0 + AUTHENTIK_CHART_SHA256: fb51f1ab970a15e37f8d2b4fe2767cea33afe7e6f84bee646d2690f31fcde7e5 + HELM_VERSION: v4.2.2 + HELM_SHA256: 9adafecab4d406853bba163a70e9f104f47dbbf65ce24b7653bae7e36150bcb6 + KUBECTL_VERSION: v1.36.3 + KUBERNETES_API: https://host.containers.internal:6443 + KUBERNETES_TLS_SERVER_NAME: 192.168.0.2 + NAMESPACE: authentik +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: | + curl -fsSL -o "$RUNNER_TEMP/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" + printf '%s %s\n' "$HELM_SHA256" "$RUNNER_TEMP/helm.tgz" | sha256sum -c + tar -xzf "$RUNNER_TEMP/helm.tgz" -C "$RUNNER_TEMP" --strip-components=1 linux-amd64/helm + echo "$RUNNER_TEMP" >> "$GITHUB_PATH" + - run: bash test.sh + deploy: + if: gitea.ref == 'refs/heads/main' + needs: validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: | + curl -fsSL -o "$RUNNER_TEMP/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" + printf '%s %s\n' "$HELM_SHA256" "$RUNNER_TEMP/helm.tgz" | sha256sum -c + tar -xzf "$RUNNER_TEMP/helm.tgz" -C "$RUNNER_TEMP" --strip-components=1 linux-amd64/helm + curl -fsSL -o "$RUNNER_TEMP/kubectl" "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" + curl -fsSL -o "$RUNNER_TEMP/kubectl.sha" "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256" + printf '%s %s\n' "$(cat "$RUNNER_TEMP/kubectl.sha")" "$RUNNER_TEMP/kubectl" | sha256sum -c + chmod 700 "$RUNNER_TEMP/helm" "$RUNNER_TEMP/kubectl"; echo "$RUNNER_TEMP" >> "$GITHUB_PATH" + - env: {KUBE_CONFIG_BASE64: "${{ secrets.KUBE_CONFIG_BASE64 }}"} + run: | + test -n "$KUBE_CONFIG_BASE64"; printf %s "$KUBE_CONFIG_BASE64" | base64 -d > "$RUNNER_TEMP/kubeconfig"; chmod 600 "$RUNNER_TEMP/kubeconfig" + export KUBECONFIG="$RUNNER_TEMP/kubeconfig"; kubectl config set-cluster cluster --server="$KUBERNETES_API" --tls-server-name="$KUBERNETES_TLS_SERVER_NAME" + - run: | + export KUBECONFIG="$RUNNER_TEMP/kubeconfig"; ./render.sh "$RUNNER_TEMP/authentik.yaml" + kubectl apply --server-side --force-conflicts --dry-run=server --validate=false -f "$RUNNER_TEMP/authentik.yaml" -f authentik-outpost-path.yaml -f authentik-media-redirect.yaml + kubectl apply --server-side --force-conflicts --validate=false -f "$RUNNER_TEMP/authentik.yaml" -f authentik-outpost-path.yaml -f authentik-media-redirect.yaml + for resource in deployment/authentik-server deployment/authentik-worker statefulset/authentik-postgresql; do + for attempt in {1..180}; do + IFS='|' read -r generation observed desired updated ready available <<< "$(kubectl -n "$NAMESPACE" get "$resource" -o jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.spec.replicas}|{.status.updatedReplicas}|{.status.readyReplicas}|{.status.availableReplicas}')" + echo "$resource $attempt/180: $updated/$desired updated, $ready/$desired ready" + [[ "$observed" == "$generation" && "$updated" == "$desired" && "$ready" == "$desired" && "$available" == "$desired" ]] && break + [[ "$attempt" == 180 ]] && { echo "$resource rollout timed out" >&2; exit 1; } + sleep 5 + done + done + curl -fsS --max-time 15 https://auth.brunner.ninja/-/health/live/ >/dev/null diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27a03d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Private legacy values retained locally for migration only +authentik-values.yaml +authentik-secrets.env +*.kubeconfig +kubeconfig* +*-secret.yaml +.env +.DS_Store diff --git a/README.md b/README.md index 6ca2a0a..6894350 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ -# authentik-deployment +# Authentik on Kubernetes +Authentik `2026.8.0` is rendered from the pinned official chart. `values.yaml` +contains only public configuration; the ignored legacy `authentik-values.yaml` +is retained locally. Runtime credentials live in `authentik-runtime` and the +existing PostgreSQL Secret, and neither is readable by CI. + +`authentik-blueprints` declaratively creates the Argo CD OIDC application, +provider, and group claim. Its client secret comes from +`ARGOCD_OIDC_CLIENT_SECRET` in `authentik-runtime`; `install.sh` generates it +once when absent and mirrors it into the labeled `argocd-oidc` Secret. Only the +existing `authentik Admins` group is mapped to Argo CD administrator access. + +Copy `authentik-secrets.env.example` to the ignored `authentik-secrets.env`, +preserve the existing values, and run `./install.sh` for administrator secret +updates. Run `./test.sh` for a secret-free render and schema validation. +The installer refuses a PostgreSQL password mismatch instead of attempting a +blind rotation; rotate the database role and both Kubernetes Secrets together +during a dedicated maintenance window. + +Run `./create-ci-kubeconfig.sh` and store its final line as the Gitea Actions +secret `KUBE_CONFIG_BASE64`. CI can update only exact existing Authentik chart, +Ingress, Middleware, and event-filter objects; it cannot read Secrets, create +objects, delete objects, or manage the Ceph object user. + +`authentik-client-credentials-event-filter` is a local compatibility patch +introduced on 2026-08-30. It replaces Authentik's private +`providers/oauth2/token/client_credentials.py` module to retain application +password grants while suppressing loopback (`::1`) login-event noise. Recheck +or remove it on every Authentik upgrade because it is not an upstream chart +feature or supported extension interface. diff --git a/authentik-media-redirect.yaml b/authentik-media-redirect.yaml new file mode 100644 index 0000000..2788cb2 --- /dev/null +++ b/authentik-media-redirect.yaml @@ -0,0 +1,29 @@ +apiVersion: traefik.io/v1alpha1 +#apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: authentik-media-redirect + namespace: authentik +spec: + entryPoints: + - websecure + routes: + - match: Host(`auth.brunner.ninja`) && PathPrefix(`/media/`) + kind: Rule + services: + - name: rook-ceph-rgw-my-store + namespace: rook-ceph + port: 80 + middlewares: + - name: media-strip-prefix +--- +apiVersion: traefik.io/v1alpha1 +#apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: media-strip-prefix + namespace: authentik +spec: + stripPrefix: + prefixes: + - /media diff --git a/authentik-namespace.yaml b/authentik-namespace.yaml new file mode 100644 index 0000000..bb24d8d --- /dev/null +++ b/authentik-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: authentik diff --git a/authentik-outpost-path.yaml b/authentik-outpost-path.yaml new file mode 100644 index 0000000..750b304 --- /dev/null +++ b/authentik-outpost-path.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: authentik-outpost-path + namespace: authentik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.priority: "10000" +spec: + rules: + - http: + paths: + - path: /outpost.goauthentik.io/ + pathType: Prefix + backend: + service: + name: authentik-server + port: + number: 80 diff --git a/authentik-secrets.env.example b/authentik-secrets.env.example new file mode 100644 index 0000000..27cf71f --- /dev/null +++ b/authentik-secrets.env.example @@ -0,0 +1,4 @@ +AUTHENTIK_SECRET_KEY=REPLACE_WITH_EXISTING_OR_RANDOM_SECRET_KEY +AUTHENTIK_POSTGRESQL__PASSWORD=REPLACE_WITH_EXISTING_POSTGRESQL_PASSWORD +AUTHENTIK_STORAGE__MEDIA__S3__ACCESS_KEY=REPLACE_WITH_ROOK_ACCESS_KEY +AUTHENTIK_STORAGE__MEDIA__S3__SECRET_KEY=REPLACE_WITH_ROOK_SECRET_KEY diff --git a/ceph-object-user.yaml b/ceph-object-user.yaml new file mode 100644 index 0000000..8259360 --- /dev/null +++ b/ceph-object-user.yaml @@ -0,0 +1,8 @@ +apiVersion: ceph.rook.io/v1 +kind: CephObjectStoreUser +metadata: + name: authentik + namespace: rook-ceph +spec: + store: my-store + diff --git a/ci-deployer.yaml b/ci-deployer.yaml new file mode 100644 index 0000000..848126e --- /dev/null +++ b/ci-deployer.yaml @@ -0,0 +1,79 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: {name: authentik-deployer, namespace: authentik} +automountServiceAccountToken: false +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: {name: authentik-deployer} +rules: + - apiGroups: [networking.k8s.io] + resources: [networkpolicies] + resourceNames: [authentik-postgresql] + verbs: [get, patch, update] + - apiGroups: [policy] + resources: [poddisruptionbudgets] + resourceNames: [authentik-postgresql] + verbs: [get, patch, update] + - apiGroups: [""] + resources: [serviceaccounts] + resourceNames: [authentik, authentik-postgresql] + verbs: [get, patch, update] + - apiGroups: [""] + resources: [configmaps] + resourceNames: [authentik-postgresql-configuration, authentik-postgresql-extended-configuration, authentik-client-credentials-event-filter, authentik-blueprints] + verbs: [get, patch, update] + - apiGroups: [""] + resources: [services] + resourceNames: [authentik-postgresql-hl, authentik-postgresql, authentik-server] + verbs: [get, patch, update] + - apiGroups: [apps] + resources: [deployments] + resourceNames: [authentik-server, authentik-worker] + verbs: [get, patch, update] + - apiGroups: [apps] + resources: [statefulsets] + resourceNames: [authentik-postgresql] + verbs: [get, patch, update] + - apiGroups: [networking.k8s.io] + resources: [ingresses] + resourceNames: [authentik-server, authentik-outpost-path] + verbs: [get, patch, update] + - apiGroups: [traefik.io] + resources: [ingressroutes] + resourceNames: [authentik-media-redirect] + verbs: [get, patch, update] + - apiGroups: [traefik.io] + resources: [middlewares] + resourceNames: [media-strip-prefix] + verbs: [get, patch, update] + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterroles] + resourceNames: [authentik-authentik] + verbs: [get, patch, update, escalate, bind] + - apiGroups: [rbac.authorization.k8s.io] + resources: [clusterrolebindings] + resourceNames: [authentik-authentik] + verbs: [get, patch, update] + - apiGroups: [rbac.authorization.k8s.io] + resources: [roles] + resourceNames: [authentik] + verbs: [get, patch, update, escalate, bind] + - apiGroups: [rbac.authorization.k8s.io] + resources: [rolebindings] + resourceNames: [authentik] + verbs: [get, patch, update] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: {name: authentik-deployer} +roleRef: {apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: authentik-deployer} +subjects: [{kind: ServiceAccount, name: authentik-deployer, namespace: authentik}] +--- +apiVersion: v1 +kind: Secret +metadata: + name: authentik-deployer-token + namespace: authentik + annotations: {kubernetes.io/service-account.name: authentik-deployer} +type: kubernetes.io/service-account-token diff --git a/create-ci-kubeconfig.sh b/create-ci-kubeconfig.sh new file mode 100755 index 0000000..fe22503 --- /dev/null +++ b/create-ci-kubeconfig.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd); namespace=authentik +account=authentik-deployer; secret=authentik-deployer-token; remote=${KUBECTL_SSH_HOST:-} +k() { if [[ -n "${remote}" ]]; then ssh "${remote}" kubectl "$@"; else kubectl "$@"; fi; } +if [[ -n "${remote}" ]]; then k apply -f - < "${project_dir}/ci-deployer.yaml" >&2; else k apply -f "${project_dir}/ci-deployer.yaml" >&2; fi +for attempt in {1..30}; do data=$(k -n "$namespace" get secret "$secret" -o jsonpath='{.data.token}' 2>/dev/null || true); [[ -n "$data" ]] && break; sleep 1; done +[[ -n "${data:-}" ]] || { echo "Token was not issued" >&2; exit 1; } +work=$(mktemp -d); trap 'rm -rf "${work}"' EXIT +server=$(k config view --minify -o jsonpath='{.clusters[0].cluster.server}') +k -n "$namespace" get secret "$secret" -o jsonpath='{.data.ca\.crt}' | base64 -d > "$work/ca" +export KUBECONFIG="$work/config"; kubectl config set-cluster cluster --server="$server" --certificate-authority="$work/ca" --embed-certs=true >/dev/null +kubectl config set-credentials "$account" --token="$(printf %s "$data" | base64 -d)" >/dev/null +kubectl config set-context authentik --cluster=cluster --user="$account" --namespace="$namespace" >/dev/null; kubectl config use-context authentik >/dev/null +echo "Store as Gitea Actions secret KUBE_CONFIG_BASE64; do not commit." >&2; base64 -w0 "$KUBECONFIG"; printf '\n' diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4e98c6e --- /dev/null +++ b/install.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +set -eu + +chart_version=2026.8.0 +chart_sha256=fb51f1ab970a15e37f8d2b4fe2767cea33afe7e6f84bee646d2690f31fcde7e5 +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +secret_file=${AUTHENTIK_SECRET_FILE:-$project_dir/authentik-secrets.env} + +for command in helm kubectl openssl base64 curl sha256sum; do + if ! command -v "$command" >/dev/null 2>&1; then + echo "Required command not found: $command" >&2 + exit 1 + fi +done + +kubectl apply --filename "$project_dir/authentik-namespace.yaml" + +if [ -f "$secret_file" ]; then + if grep -q 'REPLACE_WITH_' "$secret_file"; then + echo "Refusing placeholder secrets in $secret_file" >&2 + exit 1 + fi + if kubectl --namespace authentik get secret authentik-postgresql >/dev/null 2>&1; then + candidate_password=$(awk -F= '$1 == "AUTHENTIK_POSTGRESQL__PASSWORD" {sub(/^[^=]*=/, ""); print; exit}' "$secret_file") + candidate_password_base64=$(printf '%s' "$candidate_password" | base64 | tr -d '\n') + existing_password_base64=$(kubectl --namespace authentik get secret authentik-postgresql --output='jsonpath={.data.password}') + if [ -z "$candidate_password" ] || [ "$candidate_password_base64" != "$existing_password_base64" ]; then + echo "Refusing an uncoordinated PostgreSQL password change; rotate the database role and both Secrets during maintenance." >&2 + exit 1 + fi + unset candidate_password candidate_password_base64 existing_password_base64 + fi + kubectl --namespace authentik create secret generic authentik-runtime \ + --from-env-file="$secret_file" --dry-run=client --output=yaml \ + | kubectl apply --filename=- +elif kubectl --namespace authentik get secret authentik-runtime >/dev/null 2>&1; then + echo "Keeping existing authentik/authentik-runtime Secret." +else + echo "Missing $secret_file and authentik/authentik-runtime Secret." >&2 + exit 1 +fi + +oidc_secret_file=$(mktemp) +oidc_patch_file=$(mktemp) +postgres_password_file=$(mktemp) +trap 'rm -f "$oidc_secret_file" "$oidc_patch_file" "$postgres_password_file"' EXIT HUP INT TERM +chmod 600 "$oidc_secret_file" "$oidc_patch_file" "$postgres_password_file" + +runtime_password_base64=$(kubectl --namespace authentik get secret authentik-runtime \ + --output='jsonpath={.data.AUTHENTIK_POSTGRESQL__PASSWORD}') +if [ -z "$runtime_password_base64" ]; then + echo "authentik-runtime is missing AUTHENTIK_POSTGRESQL__PASSWORD." >&2 + exit 1 +fi +if kubectl --namespace authentik get secret authentik-postgresql >/dev/null 2>&1; then + existing_password_base64=$(kubectl --namespace authentik get secret authentik-postgresql \ + --output='jsonpath={.data.password}') + if [ "$runtime_password_base64" != "$existing_password_base64" ]; then + echo "The Authentik runtime and PostgreSQL Secret passwords differ; refusing to restart workloads." >&2 + exit 1 + fi +else + printf '%s' "$runtime_password_base64" | base64 --decode > "$postgres_password_file" + kubectl --namespace authentik create secret generic authentik-postgresql \ + --from-file="password=$postgres_password_file" \ + --from-file="postgres-password=$postgres_password_file" +fi +unset runtime_password_base64 existing_password_base64 + +oidc_secret_base64=$(kubectl --namespace authentik get secret authentik-runtime \ + --output='jsonpath={.data.ARGOCD_OIDC_CLIENT_SECRET}') +if [ -n "$oidc_secret_base64" ]; then + printf '%s' "$oidc_secret_base64" | base64 --decode > "$oidc_secret_file" +else + oidc_secret=$(openssl rand -hex 32) + printf '%s' "$oidc_secret" > "$oidc_secret_file" + oidc_secret_base64=$(printf '%s' "$oidc_secret" | base64 | tr -d '\n') + printf '{"data":{"ARGOCD_OIDC_CLIENT_SECRET":"%s"}}\n' \ + "$oidc_secret_base64" > "$oidc_patch_file" + kubectl --namespace authentik patch secret authentik-runtime \ + --type=merge --patch-file="$oidc_patch_file" >/dev/null + unset oidc_secret +fi + +kubectl create namespace argocd --dry-run=client --output=yaml \ + | kubectl apply --filename=- +kubectl --namespace argocd create secret generic argocd-oidc \ + --from-file="client-secret=$oidc_secret_file" \ + --dry-run=client --output=yaml \ + | kubectl apply --filename=- +kubectl --namespace argocd label secret argocd-oidc \ + app.kubernetes.io/part-of=argocd --overwrite >/dev/null + +# The Authentik-aware error context and public authentik middleware chain are +# managed by ../traefik-error-pages so their diagnostics stay in one project. +kubectl apply --filename "$project_dir/ceph-object-user.yaml" +chart_archive=$(mktemp) +trap 'rm -f "$oidc_secret_file" "$oidc_patch_file" "$postgres_password_file" "$chart_archive"' EXIT HUP INT TERM +curl --fail --silent --show-error --location --output "$chart_archive" \ + "https://github.com/goauthentik/helm/releases/download/authentik-${chart_version}/authentik-${chart_version}.tgz" +printf '%s %s\n' "$chart_sha256" "$chart_archive" | sha256sum --check >&2 +helm upgrade --install --namespace authentik authentik "$chart_archive" \ + --version "$chart_version" --values "$project_dir/values.yaml" \ + --wait --timeout 15m +kubectl apply --filename "$project_dir/authentik-outpost-path.yaml" +kubectl apply --filename "$project_dir/authentik-media-redirect.yaml" diff --git a/render.sh b/render.sh new file mode 100755 index 0000000..253dc80 --- /dev/null +++ b/render.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +version=${AUTHENTIK_CHART_VERSION:-2026.8.0} +digest=${AUTHENTIK_CHART_SHA256:-fb51f1ab970a15e37f8d2b4fe2767cea33afe7e6f84bee646d2690f31fcde7e5} +output=${1:?Usage: render.sh OUTPUT_FILE} +chart=$(mktemp); trap 'rm -f "${chart}"' EXIT +curl -fsSL -o "${chart}" "https://github.com/goauthentik/helm/releases/download/authentik-${version}/authentik-${version}.tgz" +printf '%s %s\n' "${digest}" "${chart}" | sha256sum -c >&2 +helm template authentik "${chart}" --namespace authentik --values "${project_dir}/values.yaml" > "${output}" diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..0ab0289 --- /dev/null +++ b/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd); work=$(mktemp -d); trap 'rm -rf "${work}"' EXIT +sh -n "$dir/install.sh"; bash -n "$dir/render.sh" "$dir/create-ci-kubeconfig.sh" "$dir/test.sh" +"$dir/render.sh" "$work/authentik.yaml" +grep -Eq '^kind: Secret$' "$work/authentik.yaml" && { echo "Chart rendered a Secret" >&2; exit 1; } +image=ghcr.io/yannh/kubeconform:v0.7.0@sha256:85dbef6b4b312b99133decc9c6fc9495e9fc5f92293d4ff3b7e1b30f5611823c +for file in "$work/authentik.yaml" "$dir/authentik-outpost-path.yaml" "$dir/authentik-media-redirect.yaml" "$dir/ci-deployer.yaml"; do docker run --rm -i "$image" -strict -exit-on-error -ignore-missing-schemas -summary < "$file"; done +echo "Authentik public desired state is valid and contains no Secrets." diff --git a/values-default.yaml b/values-default.yaml new file mode 100644 index 0000000..9beff31 --- /dev/null +++ b/values-default.yaml @@ -0,0 +1,1170 @@ +--- +# -- Provide a name in place of `authentik`. Prefer using global.nameOverride if possible +nameOverride: "" +# -- String to fully override `"authentik.fullname"`. Prefer using global.fullnameOverride if possible +fullnameOverride: "" +# -- Override the Kubernetes version, which is used to evaluate certain manifests +kubeVersionOverride: "" + + +## Globally shared configuration for authentik components. +global: + + # To override bitnami images + security: + allowInsecureImages: true + + # -- Provide a name in place of `authentik` + nameOverride: "" + # -- String to fully override `"authentik.fullname"` + fullnameOverride: "" + # -- A custom namespace to override the default namespace for the deployed resources. + namespaceOverride: "" + # -- Common labels for all resources. + additionalLabels: {} + # app: authentik + + # Number of old deployment ReplicaSets to retain. The rest will be garbage collected. + revisionHistoryLimit: 3 + + # Default image used by all authentik components. For GeoIP configuration, see the geoip values below. + image: + # -- If defined, a repository applied to all authentik deployments + repository: ghcr.io/goauthentik/server + # -- Overrides the global authentik whose default is the chart appVersion + tag: "" + # -- If defined, an image digest applied to all authentik deployments + digest: "" + # -- If defined, an imagePullPolicy applied to all authentik deployments + pullPolicy: IfNotPresent + + # -- Secrets with credentials to pull images from a private registry + imagePullSecrets: [] + + # -- Annotations for all deployed Deployments + deploymentAnnotations: {} + + # -- Annotations for all deployed pods + podAnnotations: {} + + # -- Annotations for all deployed secrets + secretAnnotations: {} + + # -- Labels for all deployed pods + podLabels: {} + + # -- Add Prometheus scrape annotations to all metrics services. This can be used as an alternative to the ServiceMonitors. + addPrometheusAnnotations: false + + # -- Toggle and define pod-level security context. + # @default -- `{}` (See [values.yaml]) + securityContext: {} + # runAsUser: 1000 + # runAsGroup: 1000 + # fsGroup: 1000 + + # -- Mapping between IP and hostnames that will be injected as entries in the pod's hosts files + hostAliases: [] + # - ip: 10.20.30.40 + # hostnames: + # - my.hostname + + # -- Default priority class for all components + priorityClassName: "" + + # -- Default node selector for all components + nodeSelector: {} + + # -- Default tolerations for all components + tolerations: [] + + # Default affinity preset for all components + affinity: + # -- Default pod anti-affinity rules. Either: `none`, `soft` or `hard` + podAntiAffinity: soft + # Node affinity rules + nodeAffinity: + # -- Default node affinity rules. Either `none`, `soft` or `hard` + type: hard + # -- Default match expressions for node affinity + matchExpressions: [] + # - key: topology.kubernetes.io/zone + # operator: In + # values: + # - zonea + # - zoneb + + # -- Default [TopologySpreadConstraints] rules for all components + ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + + # -- Deployment strategy for all deployed Deployments + deploymentStrategy: {} + # type: RollingUpdate + # rollingUpdate: + # maxSurge: 25% + # maxUnavailable: 25% + + # -- Environment variables to pass to all deployed Deployments. Does not apply to GeoIP + # See configuration options at https://goauthentik.io/docs/installation/configuration/ + # @default -- `[]` (See [values.yaml]) + env: [] + # - name: AUTHENTIK_VAR_NAME + # value: VALUE + # - name: AUTHENTIK_VAR_OTHER + # valueFrom: + # secretKeyRef: + # name: secret-name + # key: secret-key + # - name: AUTHENTIK_VAR_ANOTHER + # valueFrom: + # configMapKeyRef: + # name: config-map-name + # key: config-map-key + + # -- envFrom to pass to all deployed Deployments. Does not apply to GeoIP + # @default -- `[]` (See [values.yaml]) + envFrom: [] + # - configMapRef: + # name: config-map-name + # - secretRef: + # name: secret-name + + # -- Additional volumeMounts to all deployed Deployments. Does not apply to GeoIP + # @default -- `[]` (See [values.yaml]) + volumeMounts: [] + # - name: custom + # mountPath: /custom + + # -- Additional volumes to all deployed Deployments. + # @default -- `[]` (See [values.yaml]) + volumes: [] + # - name: custom + # emptyDir: {} + + +## Authentik configuration +authentik: + # -- whether to create the authentik configuration secret + enabled: true + # -- Log level for server and worker + log_level: info + # -- Secret key used for cookie singing and unique user IDs, + # don't change this after the first install + secret_key: "" + events: + context_processors: + # -- Path for the GeoIP City database. If the file doesn't exist, GeoIP features are disabled. + geoip: /geoip/GeoLite2-City.mmdb + # -- Path for the GeoIP ASN database. If the file doesn't exist, GeoIP features are disabled. + asn: /geoip/GeoLite2-ASN.mmdb + web: + # -- Relative path the authentik instance will be available at. Value _must_ contain both a leading and trailing slash. + path: / + email: + # -- SMTP Server emails are sent from, fully optional + host: "" + # -- SMTP server port + port: 587 + # -- SMTP credentials, when left empty, no authentication will be done + username: "" + # -- SMTP credentials, when left empty, no authentication will be done + password: "" + # -- Use StartTLS. Enable either use_tls or use_ssl, they can't be enabled at the same time. + use_tls: false + # -- Use SSL. Enable either use_tls or use_ssl, they can't be enabled at the same time. + use_ssl: false + # -- Connection timeout + timeout: 30 + # -- Email from address, can either be in the format "foo@bar.baz" or "authentik " + from: "" + outposts: + # -- Template used for managed outposts. The following placeholders can be used + # %(type)s - the type of the outpost + # %(version)s - version of your authentik install + # %(build_hash)s - only for beta versions, the build hash of the image + container_image_base: ghcr.io/goauthentik/%(type)s:%(version)s + error_reporting: + # -- This sends anonymous usage-data, stack traces on errors and + # performance data to sentry.beryju.org, and is fully opt-in + enabled: false + # -- This is a string that is sent to sentry with your error reports + environment: "k8s" + # -- Send PII (Personally identifiable information) data to sentry + send_pii: false + postgresql: + # -- set the postgresql hostname to talk to + # if unset and .Values.postgresql.enabled == true, will generate the default + # @default -- `{{ .Release.Name }}-postgresql` + host: "{{ .Release.Name }}-postgresql" + # -- postgresql Database name + # @default -- `authentik` + name: "authentik" + # -- postgresql Username + # @default -- `authentik` + user: "authentik" + password: "" + port: 5432 + redis: + # -- set the redis hostname to talk to + # @default -- `{{ .Release.Name }}-redis-master` + host: "{{ .Release.Name }}-redis-master" + password: "" + + +blueprints: + # -- List of config maps to mount blueprints from. + # Only keys in the configMap ending with `.yaml` will be discovered and applied. + configMaps: [] + # -- List of secrets to mount blueprints from. + # Only keys in the secret ending with `.yaml` will be discovered and applied. + secrets: [] + + +## authentik server +server: + # -- whether to enable server resources + enabled: true + + # -- authentik server name + name: server + + # -- The number of server pods to run + replicas: 1 + + ## authentik server Horizontal Pod Autoscaler + autoscaling: + # -- Enable Horizontal Pod Autoscaler ([HPA]) for the authentik server + enabled: false + # -- Minimum number of replicas for the authentik server [HPA] + minReplicas: 1 + # -- Maximum number of replicas for the authentik server [HPA] + maxReplicas: 5 + # -- Average CPU utilization percentage for the authentik server [HPA] + targetCPUUtilizationPercentage: 50 + # -- Average memory utilization percentage for the authentik server [HPA] + targetMemoryUtilizationPercentage: ~ + # -- Configures the scaling behavior of the target in both Up and Down directions. + behavior: {} + # scaleDown: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 180 + # scaleUp: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 2 + # periodSeconds: 60 + # -- Configures custom HPA metrics for the authentik server + # Ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ + metrics: [] + + ## authentik server Pod Disruption Budget + ## Ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + pdb: + # -- Deploy a [PodDistrubtionBudget] for the authentik server + enabled: false + # -- Labels to be added to the authentik server pdb + labels: {} + # -- Annotations to be added to the authentik server pdb + annotations: {} + # -- Number of pods that are available after eviction as number or percentage (eg.: 50%) + # @default -- `""` (defaults to 0 if not specified) + minAvailable: "" + # -- Number of pods that are unavailable after eviction as number or percentage (eg.: 50%) + ## Has higher precedence over `server.pdb.minAvailable` + maxUnavailable: "" + + ## authentik server image + ## This should match what is deployed in the worker. Prefer using global.image + image: + # -- Repository to use to the authentik server + # @default -- `""` (defaults to global.image.repository) + repository: "" # defaults to global.image.repository + # -- Tag to use to the authentik server + # @default -- `""` (defaults to global.image.tag) + tag: "" # defaults to global.image.tag + # -- Digest to use to the authentik server + # @default -- `""` (defaults to global.image.digest) + digest: "" # defaults to global.image.digest + # -- Image pull policy to use to the authentik server + # @default -- `""` (defaults to global.image.pullPolicy) + pullPolicy: "" # defaults to global.image.pullPolicy + + # -- Secrets with credentials to pull images from a private registry + # @default -- `[]` (defaults to global.imagePullSecrets) + imagePullSecrets: [] + + # -- Environment variables to pass to the authentik server. Does not apply to GeoIP + # See configuration options at https://goauthentik.io/docs/installation/configuration/ + # @default -- `[]` (See [values.yaml]) + env: [] + # - name: AUTHENTIK_VAR_NAME + # value: VALUE + # - name: AUTHENTIK_VAR_OTHER + # valueFrom: + # secretKeyRef: + # name: secret-name + # key: secret-key + # - name: AUTHENTIK_VAR_ANOTHER + # valueFrom: + # configMapKeyRef: + # name: config-map-name + # key: config-map-key + + # -- envFrom to pass to the authentik server. Does not apply to GeoIP + # @default -- `[]` (See [values.yaml]) + envFrom: [] + # - configMapRef: + # name: config-map-name + # - secretRef: + # name: secret-name + + # -- Specify postStart and preStop lifecycle hooks for you authentik server container + lifecycle: {} + + # -- Additional containers to be added to the authentik server pod + ## Note: Supports use of custom Helm templates + extraContainers: [] + # - name: my-sidecar + # image: nginx:latest + + # -- Init containers to add to the authentik server pod + ## Note: Supports use of custom Helm templates + initContainers: [] + # - name: download-tools + # image: alpine:3 + # command: [sh, -c] + # args: + # - echo init + + # -- Additional volumeMounts to the authentik server main container + volumeMounts: [] + # - name: custom + # mountPath: /custom + + # -- Additional volumes to the authentik server pod + volumes: [] + # - name: custom + # emptyDir: {} + + # -- Annotations to be added to the authentik server Deployment + deploymentAnnotations: {} + + # -- Annotations to be added to the authentik server pods + podAnnotations: {} + + # -- Labels to be added to the authentik server pods + podLabels: {} + + # -- Resource limits and requests for the authentik server + resources: {} + # requests: + # cpu: 100m + # memory: 512Mi + # limits: + # memory: 512Mi + + # authentik server container ports + containerPorts: + # -- http container port + http: 9000 + # -- https container port + https: 9443 + # -- metrics container port + metrics: 9300 + + # -- Host Network for authentik server pods + hostNetwork: false + + # -- [DNS configuration] + dnsConfig: {} + # -- Alternative DNS policy for authentik server pods + dnsPolicy: "" + + # -- serviceAccount to use for authentik server pods + serviceAccountName: ~ + + # -- authentik server pod-level security context + # @default -- `{}` (See [values.yaml]) + securityContext: {} + # runAsUser: 1000 + # runAsGroup: 1000 + # fsGroup: 1000 + + # -- authentik server container-level security context + # @default -- See [values.yaml] + containerSecurityContext: {} + # Not all of the following has been tested. Use at your own risk. + # runAsNonRoot: true + # readOnlyRootFilesystem: true + # allowPrivilegeEscalation: false + # seccomProfile: + # type: RuntimeDefault + # capabilities: + # drop: + # - ALL + + ## Liveness, readiness and startup probes for authentik server + ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ + livenessProbe: + # -- Minimum consecutive failures for the [probe] to be considered failed after having succeeded + failureThreshold: 3 + # -- Number of seconds after the container has started before [probe] is initiated + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the [probe] + periodSeconds: 10 + # -- Minimum consecutive successes for the [probe] to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the [probe] times out + timeoutSeconds: 3 + ## Probe configuration + httpGet: + path: "{{ .Values.authentik.web.path }}-/health/live/" + port: http + + readinessProbe: + # -- Minimum consecutive failures for the [probe] to be considered failed after having succeeded + failureThreshold: 3 + # -- Number of seconds after the container has started before [probe] is initiated + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the [probe] + periodSeconds: 10 + # -- Minimum consecutive successes for the [probe] to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the [probe] times out + timeoutSeconds: 3 + ## Probe configuration + httpGet: + path: "{{ .Values.authentik.web.path }}-/health/ready/" + port: http + + startupProbe: + # -- Minimum consecutive failures for the [probe] to be considered failed after having succeeded + failureThreshold: 60 + # -- Number of seconds after the container has started before [probe] is initiated + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the [probe] + periodSeconds: 10 + # -- Minimum consecutive successes for the [probe] to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the [probe] times out + timeoutSeconds: 3 + ## Probe configuration + httpGet: + path: "{{ .Values.authentik.web.path }}-/health/live/" + port: http + + # -- terminationGracePeriodSeconds for container lifecycle hook + terminationGracePeriodSeconds: 30 + + # -- Prority class for the authentik server pods + # @default -- `""` (defaults to global.priorityClassName) + priorityClassName: "" + + # -- [Node selector] + # @default -- `{}` (defaults to global.nodeSelector) + nodeSelector: {} + + # -- [Tolerations] for use with node taints + # @default -- `[]` (defaults to global.tolerations) + tolerations: [] + + # -- Assign custom [affinity] rules to the deployment + # @default -- `{}` (defaults to the global.affinity preset) + affinity: {} + + # -- Assign custom [TopologySpreadConstraints] rules to the authentik server + # @default -- `[]` (defaults to global.topologySpreadConstraints) + ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## If labelSelector is left out, it will default to the labelSelector configuration of the deployment + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + + # -- Deployment strategy to be added to the authentik server Deployment + # @default -- `{}` (defaults to global.deploymentStrategy) + deploymentStrategy: {} + # type: RollingUpdate + # rollingUpdate: + # maxSurge: 25% + # maxUnavailable: 25% + + ## authentik server service configuration + service: + # -- authentik server service annotations + annotations: {} + # -- authentik server service labels + labels: {} + # -- authentik server service type + type: ClusterIP + # -- authentik server service http port for NodePort service type (only if `server.service.type` is set to `NodePort`) + nodePortHttp: 30080 + # -- authentik server service https port for NodePort service type (only if `server.service.type` is set to `NodePort`) + nodePortHttps: 30443 + # -- authentik server service http port + servicePortHttp: 80 + # -- authentik server service https port + servicePortHttps: 443 + # -- authentik server service http port name + servicePortHttpName: http + # -- authentik server service https port name + servicePortHttpsName: https + # -- authentik server service http port appProtocol + # servicePortHttpAppProtocol: HTTP + # -- authentik server service https port appProtocol + # servicePortHttpsAppProtocol: HTTPS + # -- LoadBalancer will get created with the IP specified in this field + loadBalancerIP: "" + # -- Source IP ranges to allow access to service from + loadBalancerSourceRanges: [] + # -- authentik server service external IPs + externalIPs: [] + # -- Denotes if this service desires to route external traffic to node-local or cluster-wide endpoints + externalTrafficPolicy: "" + # -- Used to maintain session affinity. Supports `ClientIP` and `None` + sessionAffinity: "" + # -- Session affinity configuration + sessionAffinityConfig: {} + + ## authentik server metrics service configuration + metrics: + # -- deploy metrics service + enabled: false + service: + # -- metrics service type + type: ClusterIP + # -- metrics service clusterIP. `None` makes a "headless service" (no virtual IP) + clusterIP: "" + # -- metrics service annotations + annotations: {} + # -- metrics service labels + labels: {} + # -- metrics service port + servicePort: 9300 + # -- metrics service port name + portName: metrics + serviceMonitor: + # -- enable a prometheus ServiceMonitor + enabled: false + # -- Prometheus ServiceMonitor interval + interval: 30s + # -- Prometheus ServiceMonitor scrape timeout + scrapeTimeout: 3s + # -- Prometheus [RelabelConfigs] to apply to samples before scraping + relabelings: [] + # -- Prometheus [MetricsRelabelConfigs] to apply to samples before ingestion + metricRelabelings: [] + # -- Prometheus ServiceMonitor selector + selector: {} + # prometheus: kube-prometheus + + # -- Prometheus ServiceMonitor scheme + scheme: "" + # -- Prometheus ServiceMonitor tlsConfig + tlsConfig: {} + # -- Prometheus ServiceMonitor namespace + namespace: "" + # -- Prometheus ServiceMonitor labels + labels: {} + # -- Prometheus ServiceMonitor annotations + annotations: {} + + ingress: + # -- enable an ingress resource for the authentik server + enabled: false + # -- additional ingress annotations + annotations: {} + # -- additional ingress labels + labels: {} + # -- defines which ingress controller will implement the resource + ingressClassName: "" + # -- List of ingress hosts + hosts: [] + # - authentik.domain.tld + + # -- List of ingress paths + paths: + - "{{ .Values.authentik.web.path }}" + # -- Ingress path type. One of `Exact`, `Prefix` or `ImplementationSpecific` + pathType: Prefix + # -- additional ingress paths + extraPaths: [] + # - path: /* + # pathType: Prefix + # backend: + # service: + # name: ssl-redirect + # port: + # name: use-annotation + + # -- ingress TLS configuration + tls: [] + # - secretName: authentik-tls + # hosts: + # - authentik.domain.tld + + # -- uses `server.service.servicePortHttps` instead of `server.service.servicePortHttp` + https: false + + route: + main: + # -- enable an HTTPRoute resource for the authentik server. + # Be aware that this is an early beta of this feature. We don't guarantee this works and is subject to change. + enabled: false + # -- Set the route apiVersion + apiVersion: gateway.networking.k8s.io/v1 + # -- Set the route kind + kind: HTTPRoute + + # -- Route annotations + annotations: {} + # -- Route labels + labels: {} + + # -- Route hostnames + hostnames: [] + # -- Reference to parent gateways + parentRefs: [] + + # -- Create http route for redirect (https://gateway-api.sigs.k8s.io/guides/http-redirect-rewrite/#http-to-https-redirects). + # Take care that you only enable this on the http listener of the gateway to avoid an infinite redirect. + # Matches, filters and additionalRules will be ignored if this is set to true + httpsRedirect: false + + # -- uses `server.service.servicePortHttps` instead of `server.service.servicePortHttp` + https: false + + # -- Route matches + matches: + - path: + type: PathPrefix + value: "{{ .Values.authentik.web.path }}" + + # -- Route filters + filters: [] + + # -- Additional custom rules that can be added to the route + additionalRules: [] + + +## authentik worker +worker: + # -- whether to enable worker resources + enabled: true + + # -- authentik worker name + name: worker + + # -- The number of worker pods to run + replicas: 1 + + ## authentik worker Horizontal Pod Autoscaler + autoscaling: + # -- Enable Horizontal Pod Autoscaler ([HPA]) for the authentik worker + enabled: false + # -- Minimum number of replicas for the authentik worker [HPA] + minReplicas: 1 + # -- Maximum number of replicas for the authentik worker [HPA] + maxReplicas: 5 + # -- Average CPU utilization percentage for the authentik worker [HPA] + targetCPUUtilizationPercentage: 50 + # -- Average memory utilization percentage for the authentik worker [HPA] + targetMemoryUtilizationPercentage: ~ + # -- Configures the scaling behavior of the target in both Up and Down directions. + behavior: {} + # scaleDown: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 180 + # scaleUp: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 2 + # periodSeconds: 60 + # -- Configures custom HPA metrics for the authentik worker + # Ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ + metrics: [] + + ## authentik worker Pod Disruption Budget + ## Ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + pdb: + # -- Deploy a [PodDistrubtionBudget] for the authentik worker + enabled: false + # -- Labels to be added to the authentik worker pdb + labels: {} + # -- Annotations to be added to the authentik worker pdb + annotations: {} + # -- Number of pods that are available after eviction as number or percentage (eg.: 50%) + # @default -- `""` (defaults to 0 if not specified) + minAvailable: "" + # -- Number of pods that are unavailable after eviction as number or percentage (eg.: 50%) + ## Has higher precedence over `worker.pdb.minAvailable` + maxUnavailable: "" + + ## authentik worker image + ## This should match what is deployed in the server. Prefer using global.image + image: + # -- Repository to use to the authentik worker + # @default -- `""` (defaults to global.image.repository) + repository: "" # defaults to global.image.repository + # -- Tag to use to the authentik worker + # @default -- `""` (defaults to global.image.tag) + tag: "" # defaults to global.image.tag + # -- Digest to use to the authentik worker + # @default -- `""` (defaults to global.image.digest) + digest: "" # defaults to global.image.digest + # -- Image pull policy to use to the authentik worker + # @default -- `""` (defaults to global.image.pullPolicy) + pullPolicy: "" # defaults to global.image.pullPolicy + + # -- Secrets with credentials to pull images from a private registry + # @default -- `[]` (defaults to global.imagePullSecrets) + imagePullSecrets: [] + + # -- Environment variables to pass to the authentik worker. Does not apply to GeoIP + # See configuration options at https://goauthentik.io/docs/installation/configuration/ + # @default -- `[]` (See [values.yaml]) + env: [] + # - name: AUTHENTIK_VAR_NAME + # value: VALUE + # - name: AUTHENTIK_VAR_OTHER + # valueFrom: + # secretKeyRef: + # name: secret-name + # key: secret-key + # - name: AUTHENTIK_VAR_ANOTHER + # valueFrom: + # configMapKeyRef: + # name: config-map-name + # key: config-map-key + + # -- envFrom to pass to the authentik worker. Does not apply to GeoIP + # @default -- `[]` (See [values.yaml]) + envFrom: [] + # - configMapRef: + # name: config-map-name + # - secretRef: + # name: secret-name + + # -- Specify postStart and preStop lifecycle hooks for you authentik worker container + lifecycle: {} + + # -- Additional containers to be added to the authentik worker pod + ## Note: Supports use of custom Helm templates + extraContainers: [] + # - name: my-sidecar + # image: nginx:latest + + # -- Init containers to add to the authentik worker pod + ## Note: Supports use of custom Helm templates + initContainers: [] + # - name: download-tools + # image: alpine:3 + # command: [sh, -c] + # args: + # - echo init + + # -- Additional volumeMounts to the authentik worker main container + volumeMounts: [] + # - name: custom + # mountPath: /custom + + # -- Additional volumes to the authentik worker pod + volumes: [] + # - name: custom + # emptyDir: {} + + # -- Annotations to be added to the authentik worker Deployment + deploymentAnnotations: {} + + # -- Annotations to be added to the authentik worker pods + podAnnotations: {} + + # -- Labels to be added to the authentik worker pods + podLabels: {} + + # -- Resource limits and requests for the authentik worker + resources: {} + # requests: + # cpu: 100m + # memory: 512Mi + # limits: + # memory: 512Mi + + # authentik worker container ports + containerPorts: + # -- http container port + http: 9000 + # -- metrics container port + metrics: 9300 + + # -- Host Network for authentik worker pods + hostNetwork: false + + # -- [DNS configuration] + dnsConfig: {} + # -- Alternative DNS policy for authentik worker pods + dnsPolicy: "" + + # -- serviceAccount to use for authentik worker pods. If set, overrides the value used when serviceAccount.create is true + serviceAccountName: ~ + + # -- authentik worker pod-level security context + # @default -- `{}` (See [values.yaml]) + securityContext: {} + # runAsUser: 1000 + # runAsGroup: 1000 + # fsGroup: 1000 + + # -- authentik worker container-level security context + # @default -- See [values.yaml] + containerSecurityContext: {} + # Not all of the following has been tested. Use at your own risk. + # runAsNonRoot: true + # readOnlyRootFilesystem: true + # allowPrivilegeEscalation: false + # seccomProfile: + # type: RuntimeDefault + # capabilities: + # drop: + # - ALL + + livenessProbe: + # -- Minimum consecutive failures for the [probe] to be considered failed after having succeeded + failureThreshold: 3 + # -- Number of seconds after the container has started before [probe] is initiated + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the [probe] + periodSeconds: 10 + # -- Minimum consecutive successes for the [probe] to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the [probe] times out + timeoutSeconds: 3 + ## Probe configuration + exec: + command: + - ak + - healthcheck + + readinessProbe: + # -- Minimum consecutive failures for the [probe] to be considered failed after having succeeded + failureThreshold: 3 + # -- Number of seconds after the container has started before [probe] is initiated + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the [probe] + periodSeconds: 10 + # -- Minimum consecutive successes for the [probe] to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the [probe] times out + timeoutSeconds: 3 + ## Probe configuration + exec: + command: + - ak + - healthcheck + + startupProbe: + # -- Minimum consecutive failures for the [probe] to be considered failed after having succeeded + failureThreshold: 60 + # -- Number of seconds after the container has started before [probe] is initiated + initialDelaySeconds: 30 + # -- How often (in seconds) to perform the [probe] + periodSeconds: 10 + # -- Minimum consecutive successes for the [probe] to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the [probe] times out + timeoutSeconds: 3 + ## Probe configuration + exec: + command: + - ak + - healthcheck + + # -- terminationGracePeriodSeconds for container lifecycle hook + terminationGracePeriodSeconds: 30 + + # -- Prority class for the authentik worker pods + # @default -- `""` (defaults to global.priorityClassName) + priorityClassName: "" + + # -- [Node selector] + # @default -- `{}` (defaults to global.nodeSelector) + nodeSelector: {} + + # -- [Tolerations] for use with node taints + # @default -- `[]` (defaults to global.tolerations) + tolerations: [] + + # -- Assign custom [affinity] rules to the deployment + # @default -- `{}` (defaults to the global.affinity preset) + affinity: {} + + # -- Assign custom [TopologySpreadConstraints] rules to the authentik worker + # @default -- `[]` (defaults to global.topologySpreadConstraints) + ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## If labelSelector is left out, it will default to the labelSelector configuration of the deployment + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + + # -- Deployment strategy to be added to the authentik worker Deployment + # @default -- `{}` (defaults to global.deploymentStrategy) + deploymentStrategy: {} + # type: RollingUpdate + # rollingUpdate: + # maxSurge: 25% + # maxUnavailable: 25% + + ## authentik worker metrics service configuration + metrics: + # -- deploy metrics service + enabled: false + service: + # -- metrics service type + type: ClusterIP + # -- metrics service clusterIP. `None` makes a "headless service" (no virtual IP) + clusterIP: "" + # -- metrics service annotations + annotations: {} + # -- metrics service labels + labels: {} + # -- metrics service port + servicePort: 9300 + # -- metrics service port name + portName: metrics + serviceMonitor: + # -- enable a prometheus ServiceMonitor + enabled: false + # -- Prometheus ServiceMonitor interval + interval: 30s + # -- Prometheus ServiceMonitor scrape timeout + scrapeTimeout: 3s + # -- Prometheus [RelabelConfigs] to apply to samples before scraping + relabelings: [] + # -- Prometheus [MetricsRelabelConfigs] to apply to samples before ingestion + metricRelabelings: [] + # -- Prometheus ServiceMonitor selector + selector: {} + # prometheus: kube-prometheus + + # -- Prometheus ServiceMonitor scheme + scheme: "" + # -- Prometheus ServiceMonitor tlsConfig + tlsConfig: {} + # -- Prometheus ServiceMonitor namespace + namespace: "" + # -- Prometheus ServiceMonitor labels + labels: {} + # -- Prometheus ServiceMonitor annotations + annotations: {} + + +serviceAccount: + # -- Create service account. Needed for managed outposts + create: true + # -- additional service account annotations + annotations: {} + serviceAccountSecret: + # As we use the authentik-remote-cluster chart as subchart, and that chart + # creates a service account secret by default which we don't need here, + # disable its creation + enabled: false + fullnameOverride: authentik + + +geoip: + # -- enable GeoIP sidecars for the authentik server and worker pods + enabled: false + + editionIds: "GeoLite2-City GeoLite2-ASN" + # -- GeoIP update frequency, in hours + updateInterval: 8 + # -- sign up under https://www.maxmind.com/en/geolite2/signup + accountId: "" + # -- sign up under https://www.maxmind.com/en/geolite2/signup + licenseKey: "" + ## use existing secret instead of values above + existingSecret: + # -- name of an existing secret to use instead of values above + secretName: "" + # -- key in the secret containing the account ID + accountId: "account_id" + # -- key in the secret containing the license key + licenseKey: "license_key" + + image: + # -- If defined, a repository for GeoIP images + repository: ghcr.io/maxmind/geoipupdate + # -- If defined, a tag for GeoIP images + tag: v7.1.1 + # -- If defined, an image digest for GeoIP images + digest: "" + # -- If defined, an imagePullPolicy for GeoIP images + pullPolicy: IfNotPresent + + # -- Environment variables to pass to the GeoIP containers + # @default -- `[]` (See [values.yaml]) + env: [] + # - name: GEOIPUPDATE_VAR_NAME + # value: VALUE + # - name: GEOIPUPDATE_VAR_OTHER + # valueFrom: + # secretKeyRef: + # name: secret-name + # key: secret-key + # - name: GEOIPUPDATE_VAR_ANOTHER + # valueFrom: + # configMapKeyRef: + # name: config-map-name + # key: config-map-key + + # -- envFrom to pass to the GeoIP containers + # @default -- `[]` (See [values.yaml]) + envFrom: [] + # - configMapRef: + # name: config-map-name + # - secretRef: + # name: secret-name + + # -- Additional volumeMounts to the GeoIP containers. Make sure the volumes exists for the server and the worker. + volumeMounts: [] + # - name: custom + # mountPath: /custom + + # -- Resource limits and requests for GeoIP containers + resources: {} + # requests: + # cpu: 100m + # memory: 128Mi + # limits: + # memory: 128Mi + + # -- GeoIP container-level security context + # @default -- See [values.yaml] + containerSecurityContext: {} + # Not all of the following has been tested. Use at your own risk. + # runAsNonRoot: true + # readOnlyRootFilesystem: true + # allowPrivilegeEscalation: false + # seccomProfile: + # type: RuntimeDefault + # capabilities: + # drop: + # - ALL + + +prometheus: + rules: + enabled: false + # -- PrometheusRule namespace + namespace: "" + # -- PrometheusRule selector + selector: {} + # prometheus: kube-prometheus + + # -- PrometheusRule labels + labels: {} + # -- PrometheusRule annotations + annotations: {} + # -- PrometheusRuleGroup additional annotations + additionalRuleGroupAnnotations: {} + + +postgresql: + # -- enable the Bitnami PostgreSQL chart. Refer to https://github.com/bitnami/charts/blob/main/bitnami/postgresql/ for possible values. + enabled: false + image: + registry: docker.io + repository: library/postgres + tag: "17.6-bookworm" + auth: + username: authentik + database: authentik + # password: "" + primary: + args: + - -c + - config_file=/bitnami/postgresql/conf/postgresql.conf + - -c + - hba_file=/bitnami/postgresql/conf/pg_hba.conf + configuration: | + listen_addresses = '*' + port = '5432' + wal_level = 'replica' + fsync = 'on' + hot_standby = 'on' + log_connections = 'false' + log_disconnections = 'false' + log_hostname = 'false' + client_min_messages = 'error' + include_dir = 'conf.d' + pgHbaConfiguration: | + host all all 0.0.0.0/0 scram-sha-256 + host all all ::/0 scram-sha-256 + local all all scram-sha-256 + host all all 127.0.0.1/32 scram-sha-256 + host all all ::1/128 scram-sha-256 + extendedConfiguration: | + max_connections = 500 + extraEnvVars: + - name: POSTGRES_DB + value: '{{ (include "postgresql.v1.database" .) }}' + resourcesPreset: "none" + # persistence: + # enabled: true + # storageClass: + # accessModes: + # - ReadWriteOnce + containerSecurityContext: + readOnlyRootFilesystem: false + readReplicas: + resourcesPreset: "none" + backup: + resourcesPreset: "none" + passwordUpdateJob: + resourcesPreset: "none" + volumePermissions: + resourcesPreset: "none" + metrics: + resourcesPreset: "none" + + +redis: + # -- enable the Bitnami Redis chart. Refer to https://github.com/bitnami/charts/blob/main/bitnami/redis/ for possible values. + enabled: false + image: + registry: docker.io + repository: library/redis + tag: "8.2.1" + architecture: standalone + auth: + enabled: false + master: + resourcesPreset: "none" + replica: + resourcesPreset: "none" + sentinel: + resourcesPreset: "none" + metrics: + resourcesPreset: "none" + volumePermissions: + resourcesPreset: "none" + sysctl: + resourcesPreset: "none" + + +# -- additional resources to deploy. Those objects are templated. +additionalObjects: [] + diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..c8fbae2 --- /dev/null +++ b/values.yaml @@ -0,0 +1,246 @@ +global: + image: + digest: sha256:21000cebe8e51eca0620034096586d675cedec8925ac750f7f8966d86eeb0da0 + env: + - name: AUTHENTIK_STORAGE__MEDIA__BACKEND + value: s3 + - name: AUTHENTIK_STORAGE__MEDIA__S3__BUCKET_NAME + value: authentik + - name: AUTHENTIK_STORAGE__MEDIA__S3__ENDPOINT + value: https://s3.brunner.ninja/ + - name: AUTHENTIK_STORAGE__MEDIA__S3__CUSTOM_DOMAIN + value: s3.brunner.ninja/authentik + - name: AUTHENTIK_MEDIA_S3_REGION_NAME + value: my-store + - name: AUTHENTIK_EMAIL__HOST + value: smtp-relay.gmail.com + - name: AUTHENTIK_EMAIL__PORT + value: '587' + - name: AUTHENTIK_EMAIL__USE_TLS + value: 'true' + - name: AUTHENTIK_EMAIL__FROM + value: brunner.ninja Auth + - name: AUTHENTIK_ERROR_REPORTING__ENABLED + value: 'true' + - name: AUTHENTIK_POSTGRESQL__HOST + value: authentik-postgresql + - name: AUTHENTIK_POSTGRESQL__NAME + value: authentik + - name: AUTHENTIK_POSTGRESQL__USER + value: authentik + - name: AUTHENTIK_POSTGRESQL__PORT + value: '5432' + envFrom: + - secretRef: + name: authentik-runtime +authentik: + enabled: false +blueprints: + configMaps: + - authentik-blueprints +serviceAccount: + create: true + name: authentik +server: + env: + - name: AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS + value: 127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fe80::/10,::1/128 + - name: AUTHENTIK_WEB__BASE_URL + value: https://auth.brunner.ninja + - name: AUTHENTIK_WEB__MAX_REQUESTS + value: '0' + podAnnotations: + authentik.brunner.ninja/client-credentials-event-filter: 2026.8.0-1 + volumeMounts: + - name: client-credentials-event-filter + mountPath: /authentik/providers/oauth2/token/client_credentials.py + subPath: client_credentials.py + readOnly: true + - name: dev-shm + mountPath: /dev/shm + volumes: + - name: client-credentials-event-filter + configMap: + name: authentik-client-credentials-event-filter + - name: dev-shm + emptyDir: + medium: Memory + sizeLimit: 512Mi + ingress: + enabled: true + hosts: + - auth.brunner.ninja + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + livenessProbe: + timeoutSeconds: 10 + readinessProbe: + timeoutSeconds: 10 + startupProbe: + timeoutSeconds: 10 +worker: + enabled: true +geoip: + enabled: false +postgresql: + enabled: true + auth: + existingSecret: authentik-postgresql + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: password + primary: + persistence: + enabled: true + storageClass: rook-ceph-block + size: 20Gi +additionalObjects: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: authentik-blueprints + namespace: authentik + data: + argocd.yaml: | + version: 1 + metadata: + name: Argo CD OIDC + labels: + blueprints.goauthentik.io/description: Argo CD login through Authentik + entries: + - model: authentik_providers_oauth2.scopemapping + id: argocd-groups + state: present + identifiers: + name: Argo CD groups + attrs: + scope_name: groups + description: Authentik group names for Argo CD RBAC + expression: | + return {"groups": [group.name for group in request.user.ak_groups.all()]} + - model: authentik_providers_oauth2.oauth2provider + id: argocd-provider + state: present + identifiers: + name: Argo CD + attrs: + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + client_type: confidential + client_id: argocd + client_secret: !Env ARGOCD_OIDC_CLIENT_SECRET + issuer_mode: per_provider + include_claims_in_id_token: true + signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]] + redirect_uris: + - matching_mode: strict + url: https://cd.brunner.ninja/auth/callback + property_mappings: + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]] + - !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]] + - !KeyOf argocd-groups + - model: authentik_core.application + state: present + identifiers: + slug: argocd + attrs: + name: Argo CD + provider: !KeyOf argocd-provider + launch_url: https://cd.brunner.ninja + - apiVersion: v1 + kind: ConfigMap + metadata: + name: authentik-client-credentials-event-filter + namespace: authentik + data: + client_credentials.py: "from base64 import b64decode\nfrom datetime import datetime\n\ + \nfrom django.http import HttpRequest\nfrom django.utils import timezone\n\ + \nfrom authentik.common.oauth.constants import (\n CLIENT_ASSERTION,\n\ + \ CLIENT_ASSERTION_TYPE,\n CLIENT_ASSERTION_TYPE_JWT,\n)\nfrom authentik.core.models\ + \ import (\n USER_ATTRIBUTE_GENERATED,\n USER_PATH_SYSTEM_PREFIX,\n\ + \ USERNAME_MAX_LENGTH,\n Application,\n Token,\n TokenIntents,\n\ + \ User,\n UserTypes,\n)\nfrom authentik.events.middleware import audit_ignore\n\ + from authentik.events.models import Event, EventAction\nfrom authentik.flows.planner\ + \ import PLAN_CONTEXT_APPLICATION\nfrom authentik.providers.oauth2.errors\ + \ import TokenError\nfrom authentik.providers.oauth2.token.base_fed import\ + \ FederatedTokenRequest\nfrom authentik.root.middleware import ClientIPMiddleware\n\ + from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS\n\ + \n\ndef save_login_event_unless_loopback(request: HttpRequest, user: User,\ + \ **context):\n \"\"\"Do not persist internal loopback client-credentials\ + \ audit noise.\"\"\"\n if ClientIPMiddleware.get_client_ip(request) ==\ + \ \"::1\":\n return\n Event.new(action=EventAction.LOGIN, **context).from_http(request,\ + \ user=user)\n\n\nclass ClientCredentialsTokenRequest(FederatedTokenRequest):\n\ + \n def parse(self, request: HttpRequest) -> None:\n super().parse(request)\n\ + \ # client_credentials flow with client assertion\n if request.POST.get(CLIENT_ASSERTION_TYPE,\ + \ \"\") != \"\":\n return self.post_init_client_credentials_jwt(request)\n\ + \ # authentik-custom-ish client credentials flow\n if request.POST.get(\"\ + username\", \"\") != \"\":\n return self.post_init_client_credentials_creds(\n\ + \ request, request.POST.get(\"username\"), request.POST.get(\"\ + password\")\n )\n # Standard method which creates an automatic\ + \ user\n if self.client_secret == self.provider.client_secret:\n \ + \ return self.post_init_client_credentials_generated(request)\n \ + \ # Standard workaround method which stores username:password\n \ + \ # as client_secret\n try:\n user, _, password = b64decode(self.client_secret).decode(\"\ + utf-8\").partition(\":\")\n return self.post_init_client_credentials_creds(request,\ + \ user, password)\n except ValueError:\n raise TokenError(\"\ + invalid_grant\") from None\n\n def post_init_client_credentials_creds(\n\ + \ self, request: HttpRequest, username: str, password: str\n ):\n\ + \ # Authenticate user based on credentials\n user = User.objects.filter(username=username,\ + \ is_active=True).first()\n if not user:\n raise TokenError(\"\ + invalid_grant\")\n token: Token = Token.objects.filter(\n \ + \ key=password, intent=TokenIntents.INTENT_APP_PASSWORD, user=user\n \ + \ ).first()\n if not token or token.user.uid != user.uid:\n \ + \ raise TokenError(\"invalid_grant\")\n self.user = user\n\ + \ # Authorize user access\n app = Application.objects.filter(provider=self.provider).first()\n\ + \ if not app or not app.provider:\n raise TokenError(\"\ + invalid_grant\")\n self.check_policy_access(app, request)\n\n \ + \ save_login_event_unless_loopback(\n request,\n user,\n\ + \ **{\n PLAN_CONTEXT_METHOD: \"token\",\n \ + \ PLAN_CONTEXT_METHOD_ARGS: {\n \"identifier\"\ + : token.identifier,\n },\n PLAN_CONTEXT_APPLICATION:\ + \ app,\n },\n )\n\n def post_init_client_credentials_jwt(self,\ + \ request: HttpRequest):\n assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE,\ + \ \"\")\n if assertion_type != CLIENT_ASSERTION_TYPE_JWT:\n \ + \ self.logger.warning(\"Invalid assertion type\", assertion_type=assertion_type)\n\ + \ raise TokenError(\"invalid_grant\")\n\n client_secret\ + \ = request.POST.get(\"client_secret\", None)\n assertion = request.POST.get(CLIENT_ASSERTION,\ + \ client_secret)\n if not assertion:\n self.logger.warning(\"\ + Missing client assertion\")\n raise TokenError(\"invalid_grant\"\ + )\n\n federated_party = self.validate_jwt(assertion)\n\n if\ + \ not federated_party:\n self.logger.warning(\"No token could be\ + \ verified\")\n raise TokenError(\"invalid_grant\")\n\n \ + \ if \"exp\" in federated_party.parsed_token:\n exp = datetime.fromtimestamp(federated_party.parsed_token[\"\ + exp\"])\n # Non-timezone aware check since we assume `exp` is in\ + \ UTC\n if datetime.now() >= exp:\n self.logger.info(\"\ + JWT token expired\")\n raise TokenError(\"invalid_grant\")\n\ + \n app = Application.objects.filter(provider=self.provider).first()\n\ + \ if not app or not app.provider:\n self.logger.info(\"\ + client_credentials grant for provider without application\")\n \ + \ raise TokenError(\"invalid_grant\")\n\n if federated_party.user:\n\ + \ self.user = federated_party.user\n self.check_policy_access(app,\ + \ request, oauth_jwt=federated_party.parsed_token)\n if not federated_party.user:\n\ + \ self.user = self.create_user_from_jwt(federated_party, app, request)\n\ + \n method_args = {\n \"jwt\": federated_party.parsed_token,\n\ + \ federated_party.type: federated_party.party,\n }\n \ + \ save_login_event_unless_loopback(\n request,\n \ + \ self.user,\n **{\n PLAN_CONTEXT_METHOD: \"jwt\"\ + ,\n PLAN_CONTEXT_METHOD_ARGS: method_args,\n \ + \ PLAN_CONTEXT_APPLICATION: app,\n },\n )\n\n def post_init_client_credentials_generated(self,\ + \ request: HttpRequest):\n # Authorize user access\n app = Application.objects.filter(provider=self.provider).first()\n\ + \ if not app or not app.provider:\n raise TokenError(\"\ + invalid_grant\")\n with audit_ignore():\n self.user, _ =\ + \ User.objects.update_or_create(\n # trim username to ensure\ + \ the entire username is max 150 chars\n # (22 chars being\ + \ the length of the \"template\")\n username=f\"ak-{self.provider.name[:\ + \ USERNAME_MAX_LENGTH - 22]}-client_credentials\",\n defaults={\n\ + \ \"last_login\": timezone.now(),\n \ + \ \"name\": f\"Autogenerated user from application {app.name} (client credentials)\"\ + ,\n \"path\": f\"{USER_PATH_SYSTEM_PREFIX}/apps/{app.slug}\"\ + ,\n \"type\": UserTypes.SERVICE_ACCOUNT,\n \ + \ },\n )\n self.user.attributes[USER_ATTRIBUTE_GENERATED]\ + \ = True\n self.user.save()\n self.check_policy_access(app,\ + \ request)\n\n save_login_event_unless_loopback(\n request,\n\ + \ self.user,\n **{\n PLAN_CONTEXT_METHOD:\ + \ \"oauth_client_secret\",\n PLAN_CONTEXT_APPLICATION: app,\n\ + \ },\n )\n"