fix(gitea): OIDC Zitadel réellement fonctionnel (source OAuth2, app Web)
L'ancien câblage ne pouvait pas marcher : client_id littéral "gitea" (impossible, Zitadel génère les client_id) et oneshot qui échouait à chaque boot sur --enabled (flag inexistant en Gitea 1.27.x), avalé par `|| true`. - App Zitadel "Gitea" (projet marcpartensky) : appId 396570385799184684, clientId 396570385799250220, type Web, redirect https://git.marcpartensky.com/user/oauth2/zitadel/callback - oneshot gitea-oidc-init : add-or-update (id de login_source via psql), secret lu depuis sops à chaque exécution, idempotent même en dérive - [openid] d'app.ini : OpenID 2.0 legacy, désactivé (sans rapport avec OIDC, échouait en "rel=openid2.provider not found" avec une URL Zitadel) - [oauth2_client] : ENABLE_AUTO_REGISTRATION, USERNAME=email, ACCOUNT_LINKING=login (page de liaison unique pour le compte local marc) - ressource Pangolin : auth.sso-enabled=false (auth Gitea seule, une seule couche, clients git/API à token non bloqués par un redirect SSO) - secrets/gitea-oidc.yml : vrai oidc_client_secret (chiffré, clé tower)
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
oidc_client_secret: ENC[AES256_GCM,data:2TSgQrZww3yuwyuR1mG6qUp7KrBJuR1tOOE6BdSbGZXWaDDI+dyGSSXN5+rFQQ==,iv:qVuET4ocZaxIJfpaMsLuhDMuC/G1EYDStfnbLnh7rzo=,tag:VURs7XDrU+46/J3PyrGi+Q==,type:str]
|
||||
oidc_client_secret: ENC[AES256_GCM,data:XKAt2uy7rcBivcfS8d16h/SZVnyIJef6K4lskRCbMWg0Jw4ju4zLDwzONwFFURni/z7CoQ95UxLWeWo+/w5krQ==,iv:hHvNGDaECgr05jp6Od+SmFb49yBxiMrn7bRUU3/x4uw=,tag:vAJAqzoApk1OioRmHhEmOA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNWDdsbG4zK2ZMVnc0RGRr
|
||||
TVhXN1NZSEtqblp2OWtGaDBTajZsRzhrSVZ3Cm50L3owUlJ3SWlPZjU5VnlOQk94
|
||||
TGZlb3VVUjdTejBMQjA4aGRkV3l2TmcKLS0tIHVpWXc1UTd6OTJqd256T1Vqb3Fp
|
||||
U2NISUlUd21OWXNiclUvaTJEV0NJbVEKks34IfOTgEbF3szG0w9/4co8KlYweJIa
|
||||
QXj8QEkIB4BgavfXsERycDTywG9fBQBrIyZGL0/bHteszDtdTkNOLw==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoNE1jRjNHbVh5UDdIQUQx
|
||||
VmZNTUdOSDMvSTA1THhhcVlIaVZnd3JpZGtjClFwRFJ0MGJlTEdjbXNLK0FoSFlQ
|
||||
eTExQzNFRmhzM001K0FCVSttMXdqK00KLS0tIG44Q29Tb1AzZUFjRWFCMGh6a0d5
|
||||
TFl5WjVRaUUxT2JBZkxUSGNrNVFOKzQKZjDLuqbMGeBjJUyLNJ/ZQOVvZIA6aufK
|
||||
Tgjt3afnIsWinmLKyJvxbVWSdg1oN03CYLqsakdwPG7C/1d0pwUAxw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1usm3v0ugducrzcwud60dda7xfnj49fdhecj7kcsfm7dzwzrzeyqq2jc2hm
|
||||
lastmodified: "2026-09-27T21:55:00Z"
|
||||
mac: ENC[AES256_GCM,data:Cd18XRtmFHoUv6o9lzE0llWZaRjk0PYNOScb/tlxCiDIgf7wnD6u5u0IR//aSk4oyWvI3DOgdV5mPtypu/eLkHAOUwFM8yIMU5vvfLjkAuJzLWa+BrQjyZJvd98u1vNMEPhMAA4JLcBQi2EJChfMwdcN6RbMv6q2mEN2sHRjqck=,iv:UwNrKDDfhYO+A26AclSwxukF5mMhw5C3iPy+UI4+/Io=,tag:84OtSeSR4DMaEREXxt6QfA==,type:str]
|
||||
lastmodified: "2026-10-24T19:33:29Z"
|
||||
mac: ENC[AES256_GCM,data:/ml3AS+tvqTNe49fhGrGh5a3iraFyBBHw6bfkLiJaZMxrkLoQOkgvM97mJQ1qEq5C9TwmymfFn99fOr1G8ZRgOIRsZ44oJudPjovWfYb5t+krQ32oNMLOrPIEVWg3ohLu+RujA2gpeJ8Vw51QOtTDSPd5pFmlQaClk1izv31/fc=,iv:OvbJdk5vgjBfP/UgO/2//92LyPX9WAGyPY2FDRxHyB4=,tag:NpuGrMDrmCxLMtPPucQUIQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.3
|
||||
|
||||
+67
-30
@@ -2,10 +2,12 @@
|
||||
# Instance Gitea (forge git auto-hébergée) sur git.marcpartensky.com.
|
||||
#
|
||||
# Écoute en loopback (127.0.0.1:3001), exposée uniquement via le blueprint
|
||||
# Pangolin/newt (SSO devant, comme hermes-webui/hermes-dashboard). Base
|
||||
# postgres locale via socket unix (peer auth, pas de secret DB nécessaire :
|
||||
# services.gitea.database.createDatabase=true crée le rôle "gitea" et se
|
||||
# connecte par défaut sur /run/postgresql avec le user système "gitea").
|
||||
# Pangolin/newt, sans SSO Pangolin (auth.sso-enabled=false) : Gitea porte son
|
||||
# propre login (comptes locaux + OIDC Zitadel) et ses tokens d'API — une seule
|
||||
# couche d'auth, et les clients git/API à token ne sont pas redirigés vers une
|
||||
# page SSO. Base postgres locale via socket unix (peer auth, pas de secret DB
|
||||
# nécessaire : services.gitea.database.createDatabase=true crée le rôle "gitea"
|
||||
# et se connecte par défaut sur /run/postgresql avec le user système "gitea").
|
||||
#
|
||||
# Inscription publique désactivée (DISABLE_REGISTRATION) : le compte admin
|
||||
# "marc" est créé une fois par un oneshot idempotent (gitea-admin-init) via
|
||||
@@ -16,18 +18,22 @@
|
||||
# ne supporte pas le mode de ressource "ssh" de Pangolin (exige > 1.13). Clone
|
||||
# HTTPS uniquement pour l'instant (token d'accès personnel Gitea).
|
||||
#
|
||||
# OpenID Connect (OIDC) vers Zitadel (auth.marcpartensky.com) :
|
||||
# - configuration [openid] dans app.ini via settings.openid
|
||||
# - source d'auth OAuth2 "Zitadel" créée via gitea admin auth add-oauth
|
||||
# (oneshot gitea-oidc-init, idempotent).
|
||||
# OIDC vers Zitadel (auth.marcpartensky.com) : source d'auth OAuth2 "Zitadel"
|
||||
# (bouton sur la page de login), entretenue par le oneshot gitea-oidc-init
|
||||
# (idempotent, add-or-update). App Zitadel "Gitea" (projet marcpartensky,
|
||||
# appId 396570385799184684) de type Web : client_id ci-dessous, client_secret
|
||||
# dans secrets/gitea-oidc.yml. NB : la section [openid] d'app.ini n'est PAS de
|
||||
# l'OIDC (OpenID 2.0 legacy, incompatible avec Zitadel) : désactivée ci-dessous.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
# Secret OIDC client pour Zitadel (créé dans la console Zitadel)
|
||||
# Le client_id/secret sont stockés dans secrets/gitea.yml (sops)
|
||||
# App OIDC "Gitea" dans Zitadel (projet marcpartensky), créée le 24/10/2026.
|
||||
# Zitadel GÉNÈRE les client_id : "gitea" (l'ancienne valeur) n'a jamais pu
|
||||
# exister. Le client_secret vit hors du store nix (secrets/gitea-oidc.yml).
|
||||
oidcClientId = "396570385799250220";
|
||||
oidcClientSecret = config.sops.secrets."gitea/oidc_client_secret";
|
||||
in {
|
||||
sops.secrets."gitea/admin_password" = {
|
||||
@@ -59,11 +65,24 @@ in {
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
};
|
||||
# OpenID Connect (OIDC) pour Zitadel
|
||||
# OpenID 2.0 legacy (bouton « OpenID » de la page de login) : sans
|
||||
# rapport avec Zitadel qui parle OIDC. Désactivé pour ne pas induire en
|
||||
# erreur (avec une URL Zitadel il échoue en "LINK with
|
||||
# rel=openid2.provider not found"). Le SSO passe par la source OAuth2.
|
||||
openid = {
|
||||
ENABLE_OPENID_SIGNIN = true;
|
||||
ENABLE_OPENID_SIGNUP = true;
|
||||
WHITELISTED_URIS = "auth.marcpartensky.com";
|
||||
ENABLE_OPENID_SIGNIN = false;
|
||||
ENABLE_OPENID_SIGNUP = false;
|
||||
};
|
||||
# Source OAuth2 (Zitadel) : création automatique du compte local au
|
||||
# premier login OIDC. USERNAME=email → localpart de l'email Zitadel
|
||||
# (marc@marcpartensky.com → "marc"). Si le nom dérivé entre en collision
|
||||
# avec un compte existant (cas de marc), ACCOUNT_LINKING=login affiche
|
||||
# une page de liaison : on saisit son mot de passe local une fois, puis
|
||||
# l'identité OIDC est rattachée au compte existant.
|
||||
oauth2_client = {
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
USERNAME = "email";
|
||||
ACCOUNT_LINKING = "login";
|
||||
};
|
||||
# Pangolin/newt tunnelent en local : gitea verrait toute requête
|
||||
# publique comme venant de 127.0.0.1. Rien de sensible n'est protégé
|
||||
@@ -104,10 +123,11 @@ in {
|
||||
'';
|
||||
};
|
||||
|
||||
# Enregistre la source OAuth2 OIDC "Zitadel" dans Gitea (idempotent).
|
||||
# Nécessite un client OIDC déjà créé dans Zitadel (client_id, client_secret,
|
||||
# redirect_uri = https://git.marcpartensky.com/user/oauth2/zitadel/callback).
|
||||
# Le secret est lu depuis sops.
|
||||
# Enregistre/actualise la source OAuth2 OIDC "Zitadel" dans Gitea
|
||||
# (add-or-update idempotent). S'appuie sur l'app Zitadel "Gitea" :
|
||||
# client_id = oidcClientId, redirect_uri enregistrée côté Zitadel =
|
||||
# https://git.marcpartensky.com/user/oauth2/zitadel/callback.
|
||||
# Le client_secret est lu depuis sops à chaque exécution.
|
||||
systemd.services.gitea-oidc-init = {
|
||||
description = "Enregistre la source OAuth2 OIDC Zitadel dans Gitea (idempotent)";
|
||||
after = ["gitea.service" "gitea-admin-init.service" "postgresql.service"];
|
||||
@@ -126,15 +146,31 @@ in {
|
||||
Group = config.services.gitea.group;
|
||||
};
|
||||
script = ''
|
||||
${config.services.gitea.package}/bin/gitea admin auth add-oauth \
|
||||
--name zitadel \
|
||||
--provider openidConnect \
|
||||
--key "gitea" \
|
||||
--secret "$(cat ${oidcClientSecret.path})" \
|
||||
--auto-discover-url "https://auth.marcpartensky.com/.well-known/openid-configuration" \
|
||||
--scopes "openid email profile" \
|
||||
--enabled true \
|
||||
--allow-deactivate false || true
|
||||
set -eu
|
||||
DISCOVERY="https://auth.marcpartensky.com/.well-known/openid-configuration"
|
||||
SECRET="$(cat ${oidcClientSecret.path})"
|
||||
# Add-or-update : si la source existe (id en base), la mettre à jour
|
||||
# (toute dérive de client_id/secret est corrigée à chaque activation),
|
||||
# sinon la créer. En 1.27.x les sources créées par la CLI sont actives
|
||||
# par défaut (les flags --enabled/--allow-deactivate n'existent plus).
|
||||
PSQL="${config.services.postgresql.package}/bin/psql"
|
||||
SRC_ID="$("$PSQL" -U gitea -d gitea -Atc "select id from login_source where name = 'zitadel'")"
|
||||
if [ -n "$SRC_ID" ]; then
|
||||
${config.services.gitea.package}/bin/gitea admin auth update-oauth \
|
||||
--id "$SRC_ID" \
|
||||
--key "${oidcClientId}" \
|
||||
--secret "$SECRET" \
|
||||
--auto-discover-url "$DISCOVERY" \
|
||||
--scopes openid --scopes email --scopes profile
|
||||
else
|
||||
${config.services.gitea.package}/bin/gitea admin auth add-oauth \
|
||||
--name zitadel \
|
||||
--provider openidConnect \
|
||||
--key "${oidcClientId}" \
|
||||
--secret "$SECRET" \
|
||||
--auto-discover-url "$DISCOVERY" \
|
||||
--scopes openid --scopes email --scopes profile
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -142,12 +178,13 @@ in {
|
||||
# (tower) : `site` omis volontairement, cf. services/newt. Schéma
|
||||
# `proxy-resources` + `protocol` (celui vérifié fonctionnel côté hermes-webui
|
||||
# / hermes-dashboard sur ce contrôleur Pangolin, cf. selfhosted-remote-access
|
||||
# / references/pangolin-blueprint.md).
|
||||
# / references/pangolin-blueprint.md). Pas de SSO Pangolin : l'auth est
|
||||
# portée par Gitea lui-même (local + OIDC Zitadel), une seule couche.
|
||||
services.newt.blueprint.proxy-resources.gitea = {
|
||||
name = "gitea";
|
||||
protocol = "http";
|
||||
full-domain = "git.marcpartensky.com";
|
||||
auth.sso-enabled = true;
|
||||
auth.sso-enabled = false;
|
||||
targets = [
|
||||
{
|
||||
hostname = "127.0.0.1";
|
||||
@@ -178,4 +215,4 @@ in {
|
||||
message = "gitea : le port HTTP local ne doit pas être ouvert au firewall (exposition uniquement via Pangolin/newt).";
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user