Integrate Forgejo with Authentik OIDC

Refactor Authentik blueprints into common.yaml (shared admins group),
grafana.yaml (updated with !Find and groups scope), and forgejo.yaml
(new provider + application). Add forgejo-client-secret to ExternalSecret
and worker deployment. Configure Forgejo oauth2_client for auto-registration
with login-based account linking to safely preserve existing accounts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-02-20 16:10:01 -08:00
commit 23dd7c3c2b
8 changed files with 116 additions and 14 deletions

View file

@ -77,6 +77,12 @@ PASSWORD_HASH_ALGO = pbkdf2_hi
[oauth2] [oauth2]
JWT_SECRET = {{ forgejo_oauth2_jwt_secret }} JWT_SECRET = {{ forgejo_oauth2_jwt_secret }}
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
ACCOUNT_LINKING = login
USERNAME = nickname
REGISTER_EMAIL_CONFIRM = false
[actions] [actions]
ENABLED = {{ forgejo_actions_enabled | lower }} ENABLED = {{ forgejo_actions_enabled | lower }}
DEFAULT_ACTIONS_URL = {{ forgejo_actions_default_url }} DEFAULT_ACTIONS_URL = {{ forgejo_actions_default_url }}

View file

@ -5,14 +5,13 @@ metadata:
name: authentik-blueprints name: authentik-blueprints
namespace: authentik namespace: authentik
data: data:
grafana.yaml: | common.yaml: |
version: 1 version: 1
metadata: metadata:
name: BlumeOps Grafana SSO name: BlumeOps Common Identity
labels: labels:
blueprints.goauthentik.io/description: "Grafana OIDC provider and application" blueprints.goauthentik.io/description: "Shared groups and identity resources"
entries: entries:
# admins group — gates access to admin-only applications
- model: authentik_core.group - model: authentik_core.group
id: admins-group id: admins-group
identifiers: identifiers:
@ -20,6 +19,13 @@ data:
attrs: attrs:
name: admins name: admins
grafana.yaml: |
version: 1
metadata:
name: BlumeOps Grafana SSO
labels:
blueprints.goauthentik.io/description: "Grafana OIDC provider and application"
entries:
# OAuth2 provider for Grafana # OAuth2 provider for Grafana
- model: authentik_providers_oauth2.oauth2provider - model: authentik_providers_oauth2.oauth2provider
id: grafana-provider id: grafana-provider
@ -42,6 +48,7 @@ data:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]] - !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]] - !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]] - !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, groups]]
sub_mode: hashed_user_id sub_mode: hashed_user_id
include_claims_in_id_token: true include_claims_in_id_token: true
@ -62,10 +69,67 @@ data:
identifiers: identifiers:
order: 0 order: 0
target: !KeyOf grafana-app target: !KeyOf grafana-app
group: !KeyOf admins-group group: !Find [authentik_core.group, [name, admins]]
attrs: attrs:
target: !KeyOf grafana-app target: !KeyOf grafana-app
group: !KeyOf admins-group group: !Find [authentik_core.group, [name, admins]]
order: 0
enabled: true
negate: false
timeout: 30
forgejo.yaml: |
version: 1
metadata:
name: BlumeOps Forgejo SSO
labels:
blueprints.goauthentik.io/description: "Forgejo OIDC provider and application"
entries:
# OAuth2 provider for Forgejo
- model: authentik_providers_oauth2.oauth2provider
id: forgejo-provider
identifiers:
name: Forgejo
attrs:
name: Forgejo
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: forgejo
client_secret: !Env AUTHENTIK_FORGEJO_CLIENT_SECRET
redirect_uris:
- matching_mode: strict
url: https://forge.ops.eblu.me/user/oauth2/authentik/callback
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, groups]]
sub_mode: hashed_user_id
include_claims_in_id_token: true
# Forgejo application — linked to the OAuth2 provider
- model: authentik_core.application
id: forgejo-app
identifiers:
slug: forgejo
attrs:
name: Forgejo
slug: forgejo
provider: !KeyOf forgejo-provider
meta_launch_url: https://forge.ops.eblu.me
policy_engine_mode: any
# Policy binding — restrict Forgejo to admins group
- model: authentik_policies.policybinding
identifiers:
order: 0
target: !KeyOf forgejo-app
group: !Find [authentik_core.group, [name, admins]]
attrs:
target: !KeyOf forgejo-app
group: !Find [authentik_core.group, [name, admins]]
order: 0 order: 0
enabled: true enabled: true
negate: false negate: false

View file

@ -58,6 +58,11 @@ spec:
secretKeyRef: secretKeyRef:
name: authentik-config name: authentik-config
key: grafana-client-secret key: grafana-client-secret
- name: AUTHENTIK_FORGEJO_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: authentik-config
key: forgejo-client-secret
volumeMounts: volumeMounts:
- name: blueprints - name: blueprints
mountPath: /blueprints/custom mountPath: /blueprints/custom

View file

@ -41,3 +41,7 @@ spec:
remoteRef: remoteRef:
key: "Authentik (blumeops)" key: "Authentik (blumeops)"
property: grafana-client-secret property: grafana-client-secret
- secretKey: forgejo-client-secret
remoteRef:
key: "Authentik (blumeops)"
property: forgejo-client-secret

View file

@ -0,0 +1 @@
Integrate Forgejo with Authentik OIDC for single sign-on with group-based admin propagation.

View file

@ -62,9 +62,16 @@ Authentik runs on [[ringtail]]'s k3s cluster while most services run on indri's
No k8s-internal DNS crosses cluster boundaries. Everything uses the `*.ops.eblu.me` domain. No k8s-internal DNS crosses cluster boundaries. Everything uses the `*.ops.eblu.me` domain.
## Forgejo
[[forgejo]] authenticates against Authentik using the same OIDC flow as Grafana. The auth source is created via CLI (`forgejo admin auth add-oauth`) rather than config file — it lives in Forgejo's SQLite database.
Account linking is configured with `ACCOUNT_LINKING = login`: when an Authentik user's email matches an existing local account, Forgejo prompts for the local password to confirm the link. This safely preserves the existing `eblume` account with all its API tokens, SSH keys, and repository ownership.
The `admins` group in Authentik maps to Forgejo admin status, enabling centralized admin management.
## Future Work ## Future Work
- **Forgejo OIDC:** Make Forgejo an OIDC client of Authentik (deferred — existing `eblume` account needs careful migration)
- **Additional services:** ArgoCD, Miniflux, Immich, Zot (see [[harden-zot-registry]]) - **Additional services:** ArgoCD, Miniflux, Immich, Zot (see [[harden-zot-registry]])
## Related ## Related

View file

@ -39,11 +39,13 @@ Uses the shared CNPG `blumeops-pg` cluster on [[indri]], accessed cross-cluster
## Blueprints ## Blueprints
Authentik configuration is managed via Blueprints (YAML) stored as a ConfigMap mounted into the worker at `/blueprints/custom/`. Current blueprints define: Authentik configuration is managed via Blueprints (YAML) stored as a ConfigMap mounted into the worker at `/blueprints/custom/`. Current blueprints:
- `admins` group - **`common.yaml`** — shared identity resources (`admins` group)
- Grafana OAuth2 provider (client ID: `grafana`) - **`grafana.yaml`** — Grafana OAuth2 provider, application, and policy binding
- Grafana application with group-based policy binding - **`forgejo.yaml`** — Forgejo OAuth2 provider, application, and policy binding
All providers include the `groups` scope mapping for group-based admin propagation.
Blueprint file: `argocd/manifests/authentik/configmap-blueprint.yaml` Blueprint file: `argocd/manifests/authentik/configmap-blueprint.yaml`
@ -52,8 +54,9 @@ Blueprint file: `argocd/manifests/authentik/configmap-blueprint.yaml`
| Client | Status | | Client | Status |
|--------|--------| |--------|--------|
| [[grafana]] | Active | | [[grafana]] | Active |
| [[forgejo]] | Active |
Future clients: [[forgejo]], [[argocd]], [[miniflux]], [[zot]] Future clients: [[argocd]], [[miniflux]], [[zot]]
## Secrets ## Secrets
@ -64,6 +67,7 @@ Injected via [[external-secrets]] from the "Authentik (blumeops)" 1Password item
| `secret-key` | Authentik secret key | | `secret-key` | Authentik secret key |
| `db-password` | PostgreSQL password | | `db-password` | PostgreSQL password |
| `grafana-client-secret` | OIDC client secret for Grafana | | `grafana-client-secret` | OIDC client secret for Grafana |
| `forgejo-client-secret` | OIDC client secret for Forgejo |
| `api-token` | Authentik API token | | `api-token` | Authentik API token |
## Container Image ## Container Image

View file

@ -1,6 +1,6 @@
--- ---
title: Forgejo title: Forgejo
modified: 2026-02-19 modified: 2026-02-20
tags: tags:
- service - service
- git - git
@ -79,7 +79,18 @@ This is a bootstrapping requirement - the PAT enables IaC for all other secrets.
## Identity Provider ## Identity Provider
[[authentik]] is the BlumeOps OIDC identity provider and source of truth for user identity. Forgejo will eventually authenticate against Authentik as an OIDC client, with user provisioning managed in Authentik. This migration is deferred — the existing `eblume` account has extensive automations that need careful migration. [[authentik]] is the BlumeOps OIDC identity provider and source of truth for user identity. Forgejo authenticates against Authentik as an OIDC client.
**Configuration:**
- OAuth2 provider and application defined in Authentik blueprints (`argocd/manifests/authentik/configmap-blueprint.yaml`)
- Auth source created via `forgejo admin auth add-oauth` (lives in Forgejo's SQLite database, not app.ini)
- `[oauth2_client]` section in `app.ini.j2` controls auto-registration and account linking behavior
**Account linking:** `ACCOUNT_LINKING = login` — when an Authentik user's email matches an existing local account, Forgejo prompts for the local password to confirm the link. This preserves existing accounts, API tokens, SSH keys, and repository ownership.
**Group-based admin:** The `admins` group in Authentik maps to Forgejo admin status via `--admin-group admins` on the auth source. Manage admin access in Authentik, not Forgejo.
**Break-glass:** Local password login always works. Authentik SSO is additive — if Authentik is down, log in with local credentials.
## Future: Public Access ## Future: Public Access