Files
feedc0de 19f5e330a2
Validate and deploy Authentik / validate (push) Successful in 16s
Validate and deploy Authentik / deploy (push) Successful in 21s
Fix argocd
2026-09-05 16:46:23 +02:00

250 lines
13 KiB
YAML

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 <auth@brunner.ninja>
- 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
grant_types:
- authorization_code
- refresh_token
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"