Add Authentik Argo CD application
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Authentik homelab deployment
|
||||
|
||||
This directory contains only the homelab values and cluster integrations for
|
||||
the maintained upstream Authentik chart. The chart owns the Authentik server,
|
||||
worker, PostgreSQL, Services, Ingress, RBAC, probes, and persistent volume
|
||||
claim. The local resources retain the existing media route, embedded-outpost
|
||||
path, Ceph object user, and an alert for the three Authentik-managed outposts.
|
||||
|
||||
## Secrets and recovery boundary
|
||||
|
||||
The public repository never contains credentials. The deployment references
|
||||
the existing `authentik/authentik-runtime` and
|
||||
`authentik/authentik-postgresql` Secrets. The existing `argocd/argocd-oidc`
|
||||
Secret is also retained outside Git and must contain the same OIDC client
|
||||
secret as `ARGOCD_OIDC_CLIENT_SECRET` in `authentik-runtime`.
|
||||
|
||||
Restore those Secrets before synchronizing Authentik into an empty cluster.
|
||||
PostgreSQL password rotation must update the database role and both Secret
|
||||
references together during a maintenance window.
|
||||
|
||||
## Managed outposts
|
||||
|
||||
Authentik, not Argo CD, creates the `ak-outpost-ldap`,
|
||||
`ak-outpost-radius`, and `ak-outpost-rac` Deployments. Their database
|
||||
`container_image` overrides are intentionally null so Authentik uses its
|
||||
upstream `ghcr.io/goauthentik/%(type)s:%(version)s` template. That keeps each
|
||||
outpost on the same version as the server after a chart upgrade without a
|
||||
second version list in Git.
|
||||
|
||||
After every Authentik upgrade, verify that all three Deployments use the chart
|
||||
application version and report one updated, ready, and available replica. The
|
||||
`AuthentikManagedOutpostsUnavailable` alert detects missing or unavailable
|
||||
generated outposts, which are outside Argo CD's direct resource inventory.
|
||||
Run `apps/authentik/verify-outposts.sh` for the version, database-pin, image,
|
||||
and replica checks; it derives the expected version from the Argo Application.
|
||||
|
||||
The Kubernetes Dashboard provider is attached to the embedded outpost in the
|
||||
Authentik database. This avoids a blueprint replacing the embedded outpost's
|
||||
complete provider list, because other applications also own providers there.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: ceph.rook.io/v1
|
||||
kind: CephObjectStoreUser
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: rook-ceph
|
||||
spec:
|
||||
store: my-store
|
||||
@@ -0,0 +1,32 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: authentik-outposts
|
||||
namespace: authentik
|
||||
labels:
|
||||
release: prometheus
|
||||
spec:
|
||||
groups:
|
||||
- name: authentik-outposts
|
||||
rules:
|
||||
- alert: AuthentikManagedOutpostsUnavailable
|
||||
expr: |-
|
||||
sum(
|
||||
kube_deployment_status_replicas_available{
|
||||
namespace="authentik",
|
||||
deployment=~"ak-outpost-(ldap|radius|rac)"
|
||||
}
|
||||
) != 3
|
||||
or
|
||||
absent(
|
||||
kube_deployment_status_replicas_available{
|
||||
namespace="authentik",
|
||||
deployment=~"ak-outpost-(ldap|radius|rac)"
|
||||
}
|
||||
)
|
||||
for: 10m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: Authentik managed outposts are unavailable
|
||||
description: Expected one available LDAP, RADIUS, and RAC outpost replica.
|
||||
@@ -0,0 +1,311 @@
|
||||
global:
|
||||
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:
|
||||
# Keep authentication available during a pod or worker-node failure. The
|
||||
# chart's default soft pod anti-affinity spreads these replicas when the
|
||||
# cluster has capacity on more than one node.
|
||||
replicas: 2
|
||||
pdb:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
# Deliberately leave CPU unlimited so login bursts can use idle cores.
|
||||
memory: 2Gi
|
||||
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: "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
|
||||
livenessProbe:
|
||||
timeoutSeconds: 10
|
||||
readinessProbe:
|
||||
timeoutSeconds: 10
|
||||
startupProbe:
|
||||
timeoutSeconds: 10
|
||||
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
|
||||
kubernetes-dashboard.yaml: |
|
||||
version: 1
|
||||
metadata:
|
||||
name: Kubernetes Dashboard forward authentication
|
||||
labels:
|
||||
blueprints.goauthentik.io/description: Admin-only access to Kubernetes Dashboard
|
||||
entries:
|
||||
- model: authentik_providers_proxy.proxyprovider
|
||||
id: kubernetes-dashboard-provider
|
||||
state: present
|
||||
identifiers:
|
||||
name: Kubernetes Dashboard
|
||||
attrs:
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-explicit-consent]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
client_type: confidential
|
||||
grant_types:
|
||||
- authorization_code
|
||||
- refresh_token
|
||||
mode: forward_single
|
||||
internal_host: ""
|
||||
external_host: https://kubernetes.brunner.ninja
|
||||
property_mappings:
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/proxy/scope-proxy]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
|
||||
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
|
||||
- model: authentik_core.application
|
||||
id: kubernetes-dashboard-application
|
||||
state: present
|
||||
identifiers:
|
||||
slug: kubernetes-dashboard
|
||||
attrs:
|
||||
name: Kubernetes Dashboard
|
||||
provider: !KeyOf kubernetes-dashboard-provider
|
||||
launch_url: https://kubernetes.brunner.ninja
|
||||
- model: authentik_policies.policybinding
|
||||
state: present
|
||||
identifiers:
|
||||
target: !KeyOf kubernetes-dashboard-application
|
||||
group: !Find [authentik_core.group, [name, authentik Admins]]
|
||||
order: 0
|
||||
attrs:
|
||||
enabled: true
|
||||
- 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"
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||
version=$(awk '
|
||||
$1 == "chart:" && $2 == "authentik" { chart = 1; next }
|
||||
chart && $1 == "targetRevision:" { print $2; exit }
|
||||
' "$project_dir/bootstrap/authentik.yaml")
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "Could not determine the Authentik chart version." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pins=$(kubectl --namespace authentik exec deployment/authentik-worker -- \
|
||||
ak shell --command \
|
||||
'from authentik.outposts.models import Outpost; [print(f"{o.name}={o.config.container_image}") for o in Outpost.objects.filter(name__in=["ldap", "radius", "rac"]).order_by("name")]')
|
||||
printf '%s\n' "$pins"
|
||||
if printf '%s\n' "$pins" | grep -Ev '^(ldap|rac|radius)=None$' >/dev/null; then
|
||||
echo "A managed outpost still has an explicit database image override." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for name in ldap radius rac; do
|
||||
expected="ghcr.io/goauthentik/$name:$version"
|
||||
actual=$(kubectl --namespace authentik get "deployment/ak-outpost-$name" \
|
||||
--output='jsonpath={.spec.template.spec.containers[0].image}')
|
||||
replicas=$(kubectl --namespace authentik get "deployment/ak-outpost-$name" \
|
||||
--output='jsonpath={.status.updatedReplicas}/{.status.readyReplicas}/{.status.availableReplicas}')
|
||||
if [ "$actual" != "$expected" ] || [ "$replicas" != "1/1/1" ]; then
|
||||
echo "ak-outpost-$name: expected $expected at 1/1/1, found $actual at $replicas" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "ak-outpost-$name: $actual, $replicas"
|
||||
done
|
||||
@@ -0,0 +1,53 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: argocd
|
||||
spec:
|
||||
description: Authentik identity provider and homelab integrations
|
||||
sourceRepos:
|
||||
- https://charts.goauthentik.io
|
||||
- https://code.brunner.ninja/feedc0de/homelab-gitops.git
|
||||
destinations:
|
||||
- namespace: authentik
|
||||
server: https://kubernetes.default.svc
|
||||
- namespace: rook-ceph
|
||||
server: https://kubernetes.default.svc
|
||||
clusterResourceWhitelist:
|
||||
- group: ""
|
||||
kind: Namespace
|
||||
- group: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
- group: rbac.authorization.k8s.io
|
||||
kind: ClusterRoleBinding
|
||||
namespaceResourceWhitelist:
|
||||
- group: ""
|
||||
kind: ConfigMap
|
||||
- group: ""
|
||||
kind: PersistentVolumeClaim
|
||||
- group: ""
|
||||
kind: Service
|
||||
- group: ""
|
||||
kind: ServiceAccount
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
- group: apps
|
||||
kind: StatefulSet
|
||||
- group: ceph.rook.io
|
||||
kind: CephObjectStoreUser
|
||||
- group: monitoring.coreos.com
|
||||
kind: PrometheusRule
|
||||
- group: networking.k8s.io
|
||||
kind: Ingress
|
||||
- group: networking.k8s.io
|
||||
kind: NetworkPolicy
|
||||
- group: policy
|
||||
kind: PodDisruptionBudget
|
||||
- group: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
- group: rbac.authorization.k8s.io
|
||||
kind: RoleBinding
|
||||
- group: traefik.io
|
||||
kind: IngressRoute
|
||||
- group: traefik.io
|
||||
kind: Middleware
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: authentik
|
||||
sources:
|
||||
- repoURL: https://charts.goauthentik.io
|
||||
chart: authentik
|
||||
targetRevision: 2026.8.4
|
||||
helm:
|
||||
releaseName: authentik
|
||||
valueFiles:
|
||||
- $values/apps/authentik/values.yaml
|
||||
- repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: https://code.brunner.ninja/feedc0de/homelab-gitops.git
|
||||
targetRevision: main
|
||||
path: apps/authentik/resources
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: authentik
|
||||
syncPolicy:
|
||||
automated:
|
||||
enabled: true
|
||||
prune: false
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -9,6 +9,8 @@ resources:
|
||||
- immich-sync.yaml
|
||||
- immichframe.yaml
|
||||
- anubis.yaml
|
||||
- authentik-project.yaml
|
||||
- authentik.yaml
|
||||
- metallb-project.yaml
|
||||
- metallb.yaml
|
||||
- brgkepler-com.yaml
|
||||
|
||||
Reference in New Issue
Block a user