Integrate Forgejo with Authentik OIDC (#228)
## Summary
- Refactor Authentik blueprints: extract shared `admins` group into `common.yaml`, add `groups` scope mapping to all providers for group-based admin propagation
- Add Forgejo OAuth2 provider and application blueprint (`forgejo.yaml`)
- Add `forgejo-client-secret` to ExternalSecret and worker deployment env
- Configure Forgejo `[oauth2_client]` with `ACCOUNT_LINKING=login` to safely link existing accounts
- Update documentation (forgejo.md, authentik.md, federated-login.md)
## Deployment and Testing
After merge, deployment requires these steps in order:
1. **Authentik (ArgoCD):**
- `argocd app set authentik --revision feature/forgejo-authentik-oidc && argocd app sync authentik`
- Verify: Forgejo app/provider visible in Authentik admin UI
- Verify: Grafana SSO still works (blueprint refactor)
2. **Forgejo app.ini (Ansible):**
- `mise run provision-indri -- --tags forgejo --check --diff` (dry run)
- `mise run provision-indri -- --tags forgejo` (apply, restarts Forgejo)
3. **Create Forgejo auth source (CLI on indri):**
```
ssh indri 'sudo -u forgejo /opt/homebrew/bin/forgejo admin auth add-oauth \
--name authentik \
--provider openidConnect \
--key forgejo \
--secret "$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/Authentik (blumeops)/forgejo-client-secret")" \
--auto-discover-url https://authentik.ops.eblu.me/application/o/forgejo/.well-known/openid-configuration \
--scopes "openid email profile groups" \
--group-claim-name groups \
--admin-group admins'
```
4. **Link eblume account:** Sign in with Authentik on Forgejo, confirm link with local password
5. **Verify:** `tea repo list`, Forgejo Actions, local password break-glass
After merge: `argocd app set authentik --revision main && argocd app sync authentik`
Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/228
This commit is contained in:
parent
e0c6b7df99
commit
cd50c1454a
8 changed files with 143 additions and 14 deletions
|
|
@ -77,6 +77,12 @@ PASSWORD_HASH_ALGO = pbkdf2_hi
|
|||
[oauth2]
|
||||
JWT_SECRET = {{ forgejo_oauth2_jwt_secret }}
|
||||
|
||||
[oauth2_client]
|
||||
ENABLE_AUTO_REGISTRATION = true
|
||||
ACCOUNT_LINKING = login
|
||||
USERNAME = nickname
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
|
||||
[actions]
|
||||
ENABLED = {{ forgejo_actions_enabled | lower }}
|
||||
DEFAULT_ACTIONS_URL = {{ forgejo_actions_default_url }}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ metadata:
|
|||
name: authentik-blueprints
|
||||
namespace: authentik
|
||||
data:
|
||||
grafana.yaml: |
|
||||
common.yaml: |
|
||||
version: 1
|
||||
metadata:
|
||||
name: BlumeOps Grafana SSO
|
||||
name: BlumeOps Common Identity
|
||||
labels:
|
||||
blueprints.goauthentik.io/description: "Grafana OIDC provider and application"
|
||||
blueprints.goauthentik.io/description: "Shared groups and identity resources"
|
||||
entries:
|
||||
# admins group — gates access to admin-only applications
|
||||
- model: authentik_core.group
|
||||
|
|
@ -20,6 +20,34 @@ data:
|
|||
attrs:
|
||||
name: admins
|
||||
|
||||
mfa.yaml: |
|
||||
version: 1
|
||||
metadata:
|
||||
name: BlumeOps MFA Enforcement
|
||||
labels:
|
||||
blueprints.goauthentik.io/description: "Require MFA on default authentication flow"
|
||||
entries:
|
||||
# Require MFA — force_setup prompts users without MFA to enroll.
|
||||
- model: authentik_stages_authenticator_validate.authenticatorvalidatestage
|
||||
identifiers:
|
||||
name: default-authentication-mfa-validation
|
||||
attrs:
|
||||
not_configured_action: configure
|
||||
device_classes:
|
||||
- totp
|
||||
- webauthn
|
||||
- static
|
||||
configuration_stages:
|
||||
- !Find [authentik_stages_authenticator_totp.authenticatortotpstage, [name, default-authenticator-totp-setup]]
|
||||
- !Find [authentik_stages_authenticator_static.authenticatorstaticstage, [name, default-authenticator-static-setup]]
|
||||
|
||||
grafana.yaml: |
|
||||
version: 1
|
||||
metadata:
|
||||
name: BlumeOps Grafana SSO
|
||||
labels:
|
||||
blueprints.goauthentik.io/description: "Grafana OIDC provider and application"
|
||||
entries:
|
||||
# OAuth2 provider for Grafana
|
||||
- model: authentik_providers_oauth2.oauth2provider
|
||||
id: grafana-provider
|
||||
|
|
@ -62,10 +90,66 @@ data:
|
|||
identifiers:
|
||||
order: 0
|
||||
target: !KeyOf grafana-app
|
||||
group: !KeyOf admins-group
|
||||
group: !Find [authentik_core.group, [name, admins]]
|
||||
attrs:
|
||||
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]]
|
||||
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
|
||||
enabled: true
|
||||
negate: false
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ spec:
|
|||
secretKeyRef:
|
||||
name: authentik-config
|
||||
key: grafana-client-secret
|
||||
- name: AUTHENTIK_FORGEJO_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-config
|
||||
key: forgejo-client-secret
|
||||
volumeMounts:
|
||||
- name: blueprints
|
||||
mountPath: /blueprints/custom
|
||||
|
|
|
|||
|
|
@ -41,3 +41,7 @@ spec:
|
|||
remoteRef:
|
||||
key: "Authentik (blumeops)"
|
||||
property: grafana-client-secret
|
||||
- secretKey: forgejo-client-secret
|
||||
remoteRef:
|
||||
key: "Authentik (blumeops)"
|
||||
property: forgejo-client-secret
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Integrate Forgejo with Authentik OIDC for single sign-on with group-based admin propagation. Enforce TOTP MFA on Authentik authentication flow.
|
||||
|
|
@ -62,9 +62,20 @@ 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.
|
||||
|
||||
## 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.
|
||||
|
||||
### MFA
|
||||
|
||||
Authentik enforces TOTP MFA on its default authentication flow (`not_configured_action: configure`). Forgejo's auth source has `SkipLocalTwoFA: true`, so SSO logins bypass Forgejo's local 2FA — Authentik has already verified the second factor. Local password logins (break-glass) still require Forgejo's own TOTP.
|
||||
|
||||
## 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]])
|
||||
|
||||
## Related
|
||||
|
|
|
|||
|
|
@ -39,11 +39,14 @@ Uses the shared CNPG `blumeops-pg` cluster on [[indri]], accessed cross-cluster
|
|||
|
||||
## 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
|
||||
- Grafana OAuth2 provider (client ID: `grafana`)
|
||||
- Grafana application with group-based policy binding
|
||||
- **`common.yaml`** — shared identity resources (`admins` group)
|
||||
- **`mfa.yaml`** — MFA enforcement on the default authentication flow (`not_configured_action: configure`)
|
||||
- **`grafana.yaml`** — Grafana OAuth2 provider, application, and policy binding
|
||||
- **`forgejo.yaml`** — Forgejo OAuth2 provider, application, and policy binding
|
||||
|
||||
Group membership is included in the `profile` scope claim (Authentik built-in). Services use `--group-claim-name groups` to read it.
|
||||
|
||||
Blueprint file: `argocd/manifests/authentik/configmap-blueprint.yaml`
|
||||
|
||||
|
|
@ -52,8 +55,9 @@ Blueprint file: `argocd/manifests/authentik/configmap-blueprint.yaml`
|
|||
| Client | Status |
|
||||
|--------|--------|
|
||||
| [[grafana]] | Active |
|
||||
| [[forgejo]] | Active |
|
||||
|
||||
Future clients: [[forgejo]], [[argocd]], [[miniflux]], [[zot]]
|
||||
Future clients: [[argocd]], [[miniflux]], [[zot]]
|
||||
|
||||
## Secrets
|
||||
|
||||
|
|
@ -64,6 +68,7 @@ Injected via [[external-secrets]] from the "Authentik (blumeops)" 1Password item
|
|||
| `secret-key` | Authentik secret key |
|
||||
| `db-password` | PostgreSQL password |
|
||||
| `grafana-client-secret` | OIDC client secret for Grafana |
|
||||
| `forgejo-client-secret` | OIDC client secret for Forgejo |
|
||||
| `api-token` | Authentik API token |
|
||||
|
||||
## Container Image
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Forgejo
|
||||
modified: 2026-02-19
|
||||
modified: 2026-02-20
|
||||
tags:
|
||||
- service
|
||||
- git
|
||||
|
|
@ -79,7 +79,20 @@ This is a bootstrapping requirement - the PAT enables IaC for all other secrets.
|
|||
|
||||
## 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` with `--skip-local-2fa` (lives in Forgejo's SQLite database, not app.ini)
|
||||
- `[oauth2_client]` section in `app.ini.j2` controls auto-registration and account linking behavior
|
||||
|
||||
**MFA:** SSO logins skip Forgejo's local 2FA (`--skip-local-2fa` on the auth source) — Authentik enforces MFA instead. Local password logins still require Forgejo's own TOTP. Note: the `--skip-local-2fa` CLI flag has a [known bug](https://codeberg.org/forgejo/forgejo/issues/5366) where it doesn't persist via `update-oauth`; it was set directly in the `login_source.cfg` JSON (`SkipLocalTwoFA: true`).
|
||||
|
||||
**Account linking:** `ACCOUNT_LINKING = login` — when an Authentik user's email matches an existing local account, Forgejo prompts for the local password (and local MFA) to confirm the link. This is a one-time operation that 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 (with local MFA). Authentik SSO is additive — if Authentik is down, log in with local credentials.
|
||||
|
||||
## Future: Public Access
|
||||
|
||||
|
|
@ -91,7 +104,7 @@ Forgejo can be exposed publicly at `forge.eblu.me` via [[flyio-proxy]]. Since Fo
|
|||
|
||||
Exposing a dynamic, authenticated service like Forgejo requires a full security review before going live:
|
||||
|
||||
- Disable open user registration (require invites or admin approval)
|
||||
- Disable all local registration — only allow login via [[authentik]] (`DISABLE_REGISTRATION = true`, `ALLOW_ONLY_EXTERNAL_REGISTRATION = true`)
|
||||
- Configure fail2ban on indri with a filter for Forgejo's log format
|
||||
- Ensure Forgejo logs the forwarded client IP (`X-Real-IP`) rather than the proxy's Tailscale IP
|
||||
- Audit repository visibility defaults and permissions
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue