[READ-ONLY] Mirror of https://github.com/tiborpilz/infrastructure. Personal Infrastructure managed via terraform & k8s
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: add harbor registry with authentik oidc

Harbor 2.15 (chart 1.19.1) as a services-group app: CNPG-backed external
postgres, internal redis, hcloud-volumes persistence, exposed through the
public gateway at harbor.tibor.sh.

All chart-generated component secrets are pinned via existingSecret
references (sops-encrypted, age-only recipients) so ArgoCD renders stay
deterministic. OIDC is wired both ways: an authentik blueprint creates
the provider/application (client secret via authentik-oidc-clients env
secret), and a post-deploy job PUTs the matching oidc_auth config into
Harbor's API. tibor joins the new harbor-admins group for oidc admin.

Tibor Pilz (Jul 1, 2026, 10:57 PM UTC) 9170dd62 e87f2933

+385
+9
applications/identity/authentik/blueprints/users.yaml
··· 62 62 - model: authentik_core.group 63 63 state: present 64 64 identifiers: 65 + name: harbor-admins 66 + attrs: 67 + is_superuser: false 68 + users: 69 + - !Find [authentik_core.user, [username, tibor]] 70 + 71 + - model: authentik_core.group 72 + state: present 73 + identifiers: 65 74 name: authentik-superusers 66 75 attrs: 67 76 is_superuser: true
+1
applications/identity/authentik/kustomization.yaml
··· 13 13 - argo-app.yaml 14 14 - users.enc.yaml 15 15 - secrets.enc.yaml 16 + - oidc-clients.enc.yaml
+43
applications/identity/authentik/oidc-clients.enc.yaml
··· 1 + apiVersion: isindir.github.com/v1alpha3 2 + kind: SopsSecret 3 + metadata: 4 + name: authentik-oidc-clients 5 + namespace: authentik 6 + spec: 7 + secretTemplates: 8 + - name: authentik-oidc-clients 9 + stringData: 10 + HARBOR_OIDC_CLIENT_SECRET: ENC[AES256_GCM,data:jkq9wo5B/IIk8XBL869DXwNZhnkOlr42FIPbdXOY7AWq77+bL7WRWra1nea3NH7L,iv:S1Bikx52hFMxeS0CbBFuaO0LXjNzW7pQQW5zwLLtCrY=,tag:ieeHyp7MJErhCzZ2rctDRA==,type:str] 11 + sops: 12 + age: 13 + - recipient: age1nhsx40cf26syk9jt8hkd0grdrc9qc45sk52cl5vy0d78fxl2f4hs4h7laa 14 + enc: | 15 + -----BEGIN AGE ENCRYPTED FILE----- 16 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBocFRFeGRRQkpwV05uYlAv 17 + Y1ZBM1hjdzREZWhrTUV1YTRCWG90R2MrT0dFCk8xeU1ra2NJK1BTRVNjTG9pWGJU 18 + eDM3dGk2cWdEQnJSVElRWFh1ekpoalkKLS0tIGpjUGFQMHBFbmdQMlFlRHpIdTJY 19 + NER3Uzc2Rm5wUDJ2dnNpaFJiSkJwVEEKIuVtxnD+HJpjm1XBqZgfVQx1tV6PyWvB 20 + j70e1RSzza7KF3FMMFocbDSKKwIfFsi+WD5Hs9bJfOtHUVEmroGSKg== 21 + -----END AGE ENCRYPTED FILE----- 22 + - recipient: age1gzf883y8p0wfy5yjn5l3l2axnqxdgtw2zdcp8jqsrk65m7gf6y6qzgkf63 23 + enc: | 24 + -----BEGIN AGE ENCRYPTED FILE----- 25 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlcHE0MEdTM3dUUGJGclho 26 + NEJFNGY0SFRDVDJwY3l5dURBYmNUVUxQT0dRCllCV0JFekFRVHpHZUk0em1lSWtZ 27 + YmVUU1pBaVFMUjlVMjB0TVFkZnlZUEUKLS0tIDkxZVluWWRjOTcrMVJ0ZnAxS3Rj 28 + Z3RwSzZpNEtDc3VtVE54V1lYendkY1UK1N7a+yG6o3t5lF7mOdQyMj4XfZHcmNK8 29 + QtVy1URmEWQXOKWBN2BqtaPXNzCsMFrDz5JfexggQt31wah2XF3UlA== 30 + -----END AGE ENCRYPTED FILE----- 31 + - recipient: age1eagn023gem8ekv6q8gx7rc37x2rsxfscpyt2znd4j5l8ryehksdqzjek59 32 + enc: | 33 + -----BEGIN AGE ENCRYPTED FILE----- 34 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Vk1jRktYVEpuQmVzQnY5 35 + L3BLcWZ5aFhKaDcvY1o4Q1ZaekRXRnFVZ0JNCmJpcjVMdHlRMDZFUjNDdEw5VkNY 36 + cWZxTVBBWk1lcHFDdkc2ZVpuNGlCRWcKLS0tIEJWak03M2Y2dDZQSmZiY0NhQ0VC 37 + ZDdLRnl4RXdaVjJ2dmV6VEE3c3pobHcKooDJoJPXrUqnMET7ZUk3Ld9Vwc2JIKpR 38 + Vd95/8bwx2sqOfPQa9x7t55NgmoeIXwg3lOKSr0J4qQnlAIgu8UJmQ== 39 + -----END AGE ENCRYPTED FILE----- 40 + lastmodified: "2026-07-01T22:54:48Z" 41 + mac: ENC[AES256_GCM,data:+olYSG20TuFKakNI+k/rIOziuw3GJY/cjdHMatBxdAfbHQcRZfoN4YyVVnEBvyzuy8oT6BBpt/wbskeW6Lwg/k+lT99d1YxcYdrC5RP6z87N7INA5uRPDRlIoNHPmSZewhX0npdbHvwveW/PR6uS9rAYM6MQyBlcC9sM7sxa9tM=,iv:lT9m2LuLbpJ1Y9q3FCsxWef/+RmsnLZ/7llzKa9spKA=,tag:JRCanGX06pLxjp5VczJOTA==,type:str] 42 + encrypted_regex: ^(data|stringData)$ 43 + version: 3.12.2
+98
applications/services/harbor/argo-app.yaml
··· 1 + apiVersion: argoproj.io/v1alpha1 2 + kind: Application 3 + metadata: 4 + name: harbor 5 + namespace: argocd 6 + spec: 7 + project: platform 8 + source: 9 + repoURL: https://helm.goharbor.io 10 + chart: harbor 11 + targetRevision: 1.19.1 12 + helm: 13 + releaseName: harbor 14 + valuesObject: 15 + externalURL: https://harbor.tibor.sh 16 + 17 + expose: 18 + type: clusterIP 19 + tls: 20 + enabled: false 21 + clusterIP: 22 + name: harbor 23 + ports: 24 + httpPort: 80 25 + 26 + ipFamily: 27 + ipv6: 28 + enabled: false 29 + 30 + # RWO volumes deadlock on RollingUpdate (multi-attach). 31 + updateStrategy: 32 + type: Recreate 33 + 34 + persistence: 35 + enabled: true 36 + resourcePolicy: keep 37 + persistentVolumeClaim: 38 + registry: 39 + storageClass: hcloud-volumes 40 + size: 30Gi 41 + jobservice: 42 + jobLog: 43 + storageClass: hcloud-volumes 44 + size: 10Gi 45 + redis: 46 + storageClass: hcloud-volumes 47 + size: 10Gi 48 + 49 + # All component secrets pinned via existingSecret so ArgoCD renders 50 + # are deterministic; the chart would otherwise regenerate them on 51 + # every sync. 52 + existingSecretAdminPassword: harbor-admin 53 + existingSecretSecretKey: harbor-encryption 54 + 55 + core: 56 + existingSecret: harbor-core 57 + existingXsrfSecret: harbor-core 58 + 59 + jobservice: 60 + existingSecret: harbor-jobservice 61 + 62 + registry: 63 + existingSecret: harbor-registry 64 + credentials: 65 + existingSecret: harbor-registry 66 + 67 + database: 68 + type: external 69 + external: 70 + host: harbor-db-rw 71 + port: "5432" 72 + username: harbor 73 + coreDatabase: registry 74 + existingSecret: harbor-db-app 75 + 76 + redis: 77 + type: internal 78 + 79 + trivy: 80 + enabled: false 81 + 82 + metrics: 83 + enabled: false 84 + destination: 85 + server: https://kubernetes.default.svc 86 + namespace: harbor 87 + syncPolicy: 88 + automated: 89 + prune: true 90 + selfHeal: true 91 + syncOptions: 92 + - ServerSideApply=true 93 + - CreateNamespace=false 94 + ignoreDifferences: 95 + - group: apps 96 + kind: Deployment 97 + jsonPointers: 98 + - /status/terminatingReplicas
+33
applications/services/harbor/blueprints/oidc.yaml
··· 1 + version: 1 2 + metadata: 3 + name: harbor-oidc 4 + labels: 5 + blueprints.goauthentik.io/instantiate: "true" 6 + entries: 7 + - model: authentik_providers_oauth2.oauth2provider 8 + state: present 9 + identifiers: 10 + name: harbor 11 + attrs: 12 + client_id: harbor 13 + client_secret: !Env HARBOR_OIDC_CLIENT_SECRET 14 + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] 15 + authentication_flow: !Find [authentik_flows.flow, [slug, default-authentication-flow]] 16 + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] 17 + signing_key: !Find [authentik_crypto.certificatekeypair, [name, "authentik Self-signed Certificate"]] 18 + redirect_uris: 19 + - matching_mode: strict 20 + url: https://harbor.tibor.sh/c/oidc/callback 21 + property_mappings: 22 + - !Find [authentik_providers_oauth2.scopemapping, [name, "authentik default OAuth Mapping: OpenID 'openid'"]] 23 + - !Find [authentik_providers_oauth2.scopemapping, [name, "authentik default OAuth Mapping: OpenID 'profile'"]] 24 + - !Find [authentik_providers_oauth2.scopemapping, [name, "authentik default OAuth Mapping: OpenID 'email'"]] 25 + sub_mode: hashed_user_id 26 + - model: authentik_core.application 27 + state: present 28 + identifiers: 29 + slug: harbor 30 + attrs: 31 + name: Harbor 32 + provider: !Find [authentik_providers_oauth2.oauth2provider, [name, harbor]] 33 + meta_launch_url: https://harbor.tibor.sh
+17
applications/services/harbor/database.yaml
··· 1 + apiVersion: postgresql.cnpg.io/v1 2 + kind: Cluster 3 + metadata: 4 + name: harbor-db 5 + namespace: harbor 6 + spec: 7 + instances: 1 8 + imageName: ghcr.io/cloudnative-pg/postgresql:17.2 9 + storage: 10 + size: 10Gi 11 + storageClass: hcloud-volumes 12 + bootstrap: 13 + initdb: 14 + database: registry 15 + owner: harbor 16 + monitoring: 17 + enablePodMonitor: false
+22
applications/services/harbor/httproute.yaml
··· 1 + apiVersion: gateway.networking.k8s.io/v1 2 + kind: HTTPRoute 3 + metadata: 4 + name: harbor 5 + namespace: harbor 6 + spec: 7 + parentRefs: 8 + - group: gateway.networking.k8s.io 9 + kind: Gateway 10 + name: public 11 + namespace: gateway-system 12 + sectionName: https 13 + hostnames: 14 + - harbor.tibor.sh 15 + rules: 16 + - matches: 17 + - path: 18 + type: PathPrefix 19 + value: / 20 + backendRefs: 21 + - name: harbor 22 + port: 80
+10
applications/services/harbor/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - namespace.yaml 6 + - secrets.enc.yaml 7 + - database.yaml 8 + - httproute.yaml 9 + - oidc-config.yaml 10 + - argo-app.yaml
+6
applications/services/harbor/namespace.yaml
··· 1 + apiVersion: v1 2 + kind: Namespace 3 + metadata: 4 + name: harbor 5 + labels: 6 + managed-by: argocd
+83
applications/services/harbor/oidc-config.yaml
··· 1 + apiVersion: batch/v1 2 + kind: Job 3 + metadata: 4 + name: harbor-oidc-config 5 + namespace: harbor 6 + spec: 7 + backoffLimit: 20 8 + ttlSecondsAfterFinished: 600 9 + template: 10 + spec: 11 + restartPolicy: OnFailure 12 + securityContext: 13 + runAsNonRoot: true 14 + runAsUser: 1000 15 + fsGroup: 1000 16 + seccompProfile: 17 + type: RuntimeDefault 18 + containers: 19 + - name: configure 20 + image: alpine/k8s:1.30.4 21 + securityContext: 22 + allowPrivilegeEscalation: false 23 + capabilities: 24 + drop: ["ALL"] 25 + env: 26 + - name: HARBOR_URL 27 + value: http://harbor 28 + - name: EXTERNAL_URL 29 + value: https://harbor.tibor.sh 30 + - name: OIDC_ENDPOINT 31 + value: https://auth.tibor.sh/application/o/harbor/ 32 + - name: HARBOR_ADMIN_PASSWORD 33 + valueFrom: 34 + secretKeyRef: 35 + name: harbor-admin 36 + key: HARBOR_ADMIN_PASSWORD 37 + - name: OIDC_CLIENT_SECRET 38 + valueFrom: 39 + secretKeyRef: 40 + name: harbor-oidc 41 + key: secret 42 + command: ["/bin/sh", "-eu", "-c"] 43 + args: 44 + - | 45 + echo "Waiting for Harbor API at $HARBOR_URL ..." 46 + for i in $(seq 1 60); do 47 + code=$(curl -s -o /dev/null -w '%{http_code}' "$HARBOR_URL/api/v2.0/systeminfo" || true) 48 + [ "$code" = "200" ] && break 49 + sleep 10 50 + done 51 + if [ "$code" != "200" ]; then 52 + echo "Harbor API not ready (last status: $code)" >&2 53 + exit 1 54 + fi 55 + 56 + BODY=$(jq -n '{ 57 + auth_mode: "oidc_auth", 58 + oidc_name: "authentik", 59 + oidc_endpoint: env.OIDC_ENDPOINT, 60 + oidc_client_id: "harbor", 61 + oidc_client_secret: env.OIDC_CLIENT_SECRET, 62 + oidc_scope: "openid,profile,email", 63 + oidc_groups_claim: "groups", 64 + oidc_admin_group: "harbor-admins", 65 + oidc_auto_onboard: true, 66 + oidc_user_claim: "preferred_username", 67 + oidc_verify_cert: true 68 + }') 69 + 70 + RAW=$(curl -s -u "admin:$HARBOR_ADMIN_PASSWORD" \ 71 + -X PUT -H 'Content-Type: application/json' \ 72 + -w '\n%{http_code}' -d "$BODY" \ 73 + "$HARBOR_URL/api/v2.0/configurations") 74 + HTTP=$(printf '%s' "$RAW" | tail -n1) 75 + RESP=$(printf '%s' "$RAW" | sed '$d') 76 + if [ "$HTTP" != "200" ]; then 77 + echo "Failed to apply OIDC configuration (HTTP $HTTP): $RESP" >&2 78 + exit 1 79 + fi 80 + 81 + MODE=$(curl -s -u "admin:$HARBOR_ADMIN_PASSWORD" \ 82 + "$HARBOR_URL/api/v2.0/configurations" | jq -r '.auth_mode.value') 83 + echo "OIDC configuration applied; auth_mode=$MODE"
+62
applications/services/harbor/secrets.enc.yaml
··· 1 + apiVersion: isindir.github.com/v1alpha3 2 + kind: SopsSecret 3 + metadata: 4 + name: harbor-secrets 5 + namespace: harbor 6 + spec: 7 + secretTemplates: 8 + - name: harbor-admin 9 + stringData: 10 + HARBOR_ADMIN_PASSWORD: ENC[AES256_GCM,data:b/yy96K3i9+WjqtCPuPjhFYF0C6w+/kSNvXshdAWDBI=,iv:ZxjDSW4M9KI4peTW66vTRJdsUujtF+wdD+ui9o6q5ps=,tag:vPP5Qb3THa4uqbIYiqryhQ==,type:str] 11 + - name: harbor-core 12 + stringData: 13 + secret: ENC[AES256_GCM,data:6HDPcV5dGescDJ4xRGNTTw==,iv:vqDrHolf8F12J/Y2dmi1A2pwvg5knCOqSwwJaXTSg2Q=,tag:/drGMV9wPdp3J1brIlEZIw==,type:str] 14 + CSRF_KEY: ENC[AES256_GCM,data:jl5ohN+3QU/bIWho9CE4wek7+yx/7b3IxTqc7QCQHWw=,iv:gRayT/DQa1B3HNisI+8umFRnddvhuPMJXgWaMBkREqw=,tag:9O2tizeomwMZj6I96e5wxw==,type:str] 15 + - name: harbor-jobservice 16 + stringData: 17 + JOBSERVICE_SECRET: ENC[AES256_GCM,data:mpmtRHiXJmO+DnRk+Yhohw==,iv:n1qLg4tRR3HoFdKf12HunZWAZVLfd6bo4VbdP6Xbo0Y=,tag:Gdn9YrcHl7P5Ta5xnY+RYg==,type:str] 18 + - name: harbor-registry 19 + stringData: 20 + REGISTRY_HTTP_SECRET: ENC[AES256_GCM,data:/iDNtp/tcr/U06IIxw3axw==,iv:Pvocvye1jQoA3bC/K+HAqfsPROmB/oPp58+mYxc5FQI=,tag:C5C9jmN6QTwWCcOpzey9Kw==,type:str] 21 + REGISTRY_PASSWD: ENC[AES256_GCM,data:eXpNDgeyx8VMk5tnEujlS0Fl4+Ra7BTN,iv:NpltYanUkNZKGa16jLGWWnoBDhtzK38A83KGSIkKHPQ=,tag:DKvmwGLL0fuczP386/PqnA==,type:str] 22 + REGISTRY_HTPASSWD: ENC[AES256_GCM,data:jksRxEdjeSXRcMOv9H5yJtgWoPjGCdnEjadLUg2S1YZh+QyJNSIvJrg8FHu9UgoqclMTHLCd1qwen+6KLrY3rfKVL+RD5LjD34Bip1wzFbTH,iv:OoB5y4yhDNsiA5HmuSgSN2jHFRMcQQm3dmbSAReyLCc=,tag:YOxbGR+BdGa/Vg8N/RqunQ==,type:str] 23 + - name: harbor-encryption 24 + stringData: 25 + secretKey: ENC[AES256_GCM,data:Dw1oCud9pTIWvZLFQXZ6Qw==,iv:K1WvouA04LUCnAcorhLHwXod8j4qdN2HsBesfGez1B4=,tag:5n6Bx98dhaoTLf83CCbYfw==,type:str] 26 + - name: harbor-oidc 27 + stringData: 28 + key: ENC[AES256_GCM,data:pomNyKcO,iv:EiPmJ6J9/+fys12605g0WwSCFez/2m6vh9df8T1fnMk=,tag:VFy6+8aCLSzY0AYe9fdRgg==,type:str] 29 + secret: ENC[AES256_GCM,data:aj0a6TSxJ5U4nXp3PhCWxkxsTeNtBuzXfABhcjRHwLrP+MIwTW6KrZAbKPvnUjKT,iv:rG9xkaDmryiw1TA3Hy5kk4kFjoMXuZOpgHXMMFDHPc4=,tag:N88zaFEMPAQZrgKRfQJozA==,type:str] 30 + sops: 31 + age: 32 + - recipient: age1nhsx40cf26syk9jt8hkd0grdrc9qc45sk52cl5vy0d78fxl2f4hs4h7laa 33 + enc: | 34 + -----BEGIN AGE ENCRYPTED FILE----- 35 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4cnJyL0g4QlpvcmQwRFUx 36 + aVZhR3ZPZjRhdjBkRDA2bkdNR3FlVzl5aFFjCjltSklST3FSZmVQVUJyRTc1VGZR 37 + b29hdFk1Vng0Z2Y3SHdMUXlBTkhyR1UKLS0tIDVIZHZKZm43K0FhS3N6Tkx2Qklr 38 + cTUzVDVoRFRFMXVkMEk1SWk1MzRJYkkKEWxTEoIPo23AroLIDti61Pe8jtzBXIMb 39 + Xl9f8yzanpF7NYUSF/Wfqls3Bg8djdoO98irYll0F6m1KfZ1stwbJg== 40 + -----END AGE ENCRYPTED FILE----- 41 + - recipient: age1gzf883y8p0wfy5yjn5l3l2axnqxdgtw2zdcp8jqsrk65m7gf6y6qzgkf63 42 + enc: | 43 + -----BEGIN AGE ENCRYPTED FILE----- 44 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBZlUzZkpSamsrWTByWW1Z 45 + Mm40U1pQdVQyMkR5OVBydzl5eEtOY0oxT1RFClBLZ2JpNlgwbk50dmdsckNtL2Zx 46 + TjQwK3pGaUpLYmt1UFF6UDc1Y2ZhZTQKLS0tIFlFV0x6TnRNSjZQSTVWenpzN3cv 47 + MlIxVnJJejVpOG5pTzhFNmsvbjc1S00KQ0+/1hrpgeiXgYbR99GYwgYCiHKz0/8F 48 + YRR+ZxuOoxKQ/xrukx3FsR+zMeX0GcOa1mOXdBLO7F0Fsxdx5khGyQ== 49 + -----END AGE ENCRYPTED FILE----- 50 + - recipient: age1eagn023gem8ekv6q8gx7rc37x2rsxfscpyt2znd4j5l8ryehksdqzjek59 51 + enc: | 52 + -----BEGIN AGE ENCRYPTED FILE----- 53 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQeWVXMzl6eC9ZLzRkUmRR 54 + NWFJYmRLNlZIcThvbW5PQk0wVTAweEliMlJRCjZpZGlTejlPejI1aUNZdUkrYzRq 55 + eFRPRktXcmJLQzJrUW1KanZhOWZTaGsKLS0tIERwNk9yTUUwcHV5eTFJL3pNL3V3 56 + K2lrazJXemwvbzVzTGxKVE43cWJ6RVEKECPiPR2OFOIpmz1UuNxmfdR1ZHMBio4U 57 + D0QGrpAAQg6a3XTHxowUc1Tm1BJcbvp0z86aORAPxFTkAoVjFlW7NQ== 58 + -----END AGE ENCRYPTED FILE----- 59 + lastmodified: "2026-07-01T22:54:48Z" 60 + mac: ENC[AES256_GCM,data:Onr5plhkiuody4PHY7mHUgEVKsfSNdwhuARCar9n8PurBWNTuo2Nh5Diu8fF+6uIxB490DnFdKRadOhgioSAODJSz4OKd4i4rJnxSMFa3F/OB9FV/2WDbbv1OURn2kcX1P3qt4Ea7YUzDFXJgWp3bpgoZYDgH85kPbOj9dJtDE4=,iv:LQPiJGvGnQVdupKYQHmD9W+q9ufHZTb3w9HrIBfMYWg=,tag:Qb+0Z4+r9+zJHF3uQ1HRDg==,type:str] 61 + encrypted_regex: ^(data|stringData)$ 62 + version: 3.12.2
+1
applications/services/kustomization.yaml
··· 4 4 resources: 5 5 - gitops-canary.yaml 6 6 - forgejo 7 + - harbor 7 8 - woodpecker 8 9 - tangled/argo-app.yaml 9 10 - pds/argo-app.yaml