Add Burrow Authentik admin directory sync

This commit is contained in:
Conrad Kramer 2026-04-01 11:39:29 -07:00
parent 1ff8270a01
commit bb05bd9014
5 changed files with 484 additions and 11 deletions

View file

@ -91,6 +91,22 @@
headscaleClientSecretFile = config.age.secrets.burrowHeadscaleOidcClientSecret.path;
googleClientIDFile = config.age.secrets.burrowAuthentikGoogleClientId.path;
googleClientSecretFile = config.age.secrets.burrowAuthentikGoogleClientSecret.path;
bootstrapUsers = [
{
username = "contact";
name = "Burrow";
email = "contact@burrow.net";
sourceEmail = "net.burrow@gmail.com";
isAdmin = true;
}
{
username = "conrad";
name = "Conrad Kramer";
email = "conrad@burrow.net";
sourceEmail = "ckrames1234@gmail.com";
isAdmin = true;
}
];
};
services.burrow.headscale = {

View file

@ -8,6 +8,7 @@ let
blueprintFile = "${blueprintDir}/burrow-authentik.yaml";
postgresVolume = "burrow-authentik-postgresql:/var/lib/postgresql/data";
dataVolume = "burrow-authentik-data:/data";
directorySyncScript = ../../Scripts/authentik-sync-burrow-directory.sh;
forgejoOidcSyncScript = ../../Scripts/authentik-sync-forgejo-oidc.sh;
googleSourceSyncScript = ../../Scripts/authentik-sync-google-source.sh;
authentikBlueprint = pkgs.writeText "burrow-authentik-blueprint.yaml" ''
@ -31,6 +32,19 @@ let
"email_verified": True,
}
- model: authentik_providers_oauth2.scopemapping
id: burrow-oidc-groups
identifiers:
name: Burrow OIDC Groups
attrs:
name: Burrow OIDC Groups
scope_name: groups
description: Group membership mapping for Burrow
expression: |
return {
"groups": [group.name for group in request.user.ak_groups.all()],
}
- model: authentik_providers_oauth2.oauth2provider
id: burrow-oidc-provider-ts
identifiers:
@ -50,6 +64,7 @@ let
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
- !KeyOf burrow-oidc-email
- !KeyOf burrow-oidc-groups
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
@ -159,6 +174,54 @@ in
default = "redirect";
description = "Identification-stage behavior for the Google Authentik source.";
};
userGroupName = lib.mkOption {
type = lib.types.str;
default = "burrow-users";
description = "Authentik group granted baseline Burrow access.";
};
adminGroupName = lib.mkOption {
type = lib.types.str;
default = "burrow-admins";
description = "Authentik group granted Burrow administrator access.";
};
bootstrapUsers = lib.mkOption {
type = with lib.types; listOf (submodule {
options = {
username = lib.mkOption {
type = str;
description = "Authentik username.";
};
name = lib.mkOption {
type = str;
description = "Display name for the user.";
};
email = lib.mkOption {
type = str;
description = "Canonical email stored in Authentik.";
};
sourceEmail = lib.mkOption {
type = nullOr str;
default = null;
description = "External Google account email that should map onto this Authentik user.";
};
groups = lib.mkOption {
type = listOf str;
default = [ ];
description = "Additional Authentik groups for this user.";
};
isAdmin = lib.mkOption {
type = bool;
default = false;
description = "Whether this user should be in the Burrow admin group.";
};
};
});
default = [ ];
description = "Declarative Burrow users to create in Authentik.";
};
};
config = lib.mkIf cfg.enable {
@ -295,6 +358,16 @@ EOF
];
};
systemd.services.podman-burrow-authentik-server.restartTriggers = [
blueprintFile
envFile
];
systemd.services.podman-burrow-authentik-worker.restartTriggers = [
blueprintFile
envFile
];
systemd.services.burrow-authentik-ready = {
description = "Wait for Burrow Authentik to become ready";
after = [ "podman-burrow-authentik-server.service" ];
@ -366,11 +439,66 @@ EOF
export AUTHENTIK_GOOGLE_USER_MATCHING_MODE=email_link
export AUTHENTIK_GOOGLE_CLIENT_ID="$(tr -d '\r\n' < ${lib.escapeShellArg cfg.googleClientIDFile})"
export AUTHENTIK_GOOGLE_CLIENT_SECRET="$(tr -d '\r\n' < ${lib.escapeShellArg cfg.googleClientSecretFile})"
export AUTHENTIK_GOOGLE_ACCOUNT_MAP_JSON='${builtins.toJSON (map (user: {
source_email = user.sourceEmail;
username = user.username;
email = user.email;
name = user.name;
}) (lib.filter (user: user.sourceEmail != null) cfg.bootstrapUsers))}'
${pkgs.bash}/bin/bash ${googleSourceSyncScript}
'';
};
systemd.services.burrow-authentik-directory = lib.mkIf (cfg.bootstrapUsers != [ ]) {
description = "Reconcile Burrow Authentik users and groups";
after =
[
"burrow-authentik-ready.service"
"network-online.target"
]
++ lib.optionals (cfg.forgejoClientSecretFile != null) [ "burrow-authentik-forgejo-oidc.service" ];
wants =
[
"burrow-authentik-ready.service"
"network-online.target"
]
++ lib.optionals (cfg.forgejoClientSecretFile != null) [ "burrow-authentik-forgejo-oidc.service" ];
wantedBy = [ "multi-user.target" ];
restartTriggers = [
directorySyncScript
cfg.envFile
];
path = [
pkgs.bash
pkgs.coreutils
pkgs.curl
pkgs.jq
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
};
script = ''
set -euo pipefail
set -a
source ${lib.escapeShellArg cfg.envFile}
set +a
export AUTHENTIK_URL=https://${cfg.domain}
export AUTHENTIK_BURROW_USERS_GROUP=${lib.escapeShellArg cfg.userGroupName}
export AUTHENTIK_BURROW_ADMINS_GROUP=${lib.escapeShellArg cfg.adminGroupName}
export AUTHENTIK_FORGEJO_APPLICATION_SLUG=${lib.escapeShellArg cfg.forgejoProviderSlug}
export AUTHENTIK_BURROW_DIRECTORY_JSON='${builtins.toJSON (map (user: {
inherit (user) username name email isAdmin;
groups = user.groups;
}) cfg.bootstrapUsers)}'
${pkgs.bash}/bin/bash ${directorySyncScript}
'';
};
systemd.services.burrow-authentik-forgejo-oidc = lib.mkIf (cfg.forgejoClientSecretFile != null) {
description = "Reconcile the Burrow Authentik Forgejo OIDC application";
after = [

View file

@ -92,6 +92,35 @@ in
description = "OpenID Connect discovery URL for the Forgejo login source.";
};
oidcScopes = lib.mkOption {
type = with lib.types; listOf str;
default = [
"openid"
"profile"
"email"
"groups"
];
description = "OIDC scopes requested from Authentik.";
};
oidcGroupClaimName = lib.mkOption {
type = lib.types.str;
default = "groups";
description = "OIDC claim name that carries group membership.";
};
oidcAdminGroup = lib.mkOption {
type = lib.types.str;
default = "burrow-admins";
description = "OIDC group that should grant Forgejo admin access.";
};
oidcRestrictedGroup = lib.mkOption {
type = lib.types.str;
default = "burrow-users";
description = "OIDC group that is required to log into Forgejo.";
};
authorizedKeys = lib.mkOption {
type = with lib.types; listOf str;
default = [ ];
@ -339,6 +368,10 @@ in
--arg client_id ${lib.escapeShellArg cfg.oidcClientId} \
--arg client_secret "$oidc_secret" \
--arg discovery_url ${lib.escapeShellArg cfg.oidcDiscoveryUrl} \
--argjson scopes '${builtins.toJSON cfg.oidcScopes}' \
--arg group_claim_name ${lib.escapeShellArg cfg.oidcGroupClaimName} \
--arg admin_group ${lib.escapeShellArg cfg.oidcAdminGroup} \
--arg restricted_group ${lib.escapeShellArg cfg.oidcRestrictedGroup} \
'{
Provider: "openidConnect",
ClientID: $client_id,
@ -346,15 +379,15 @@ in
OpenIDConnectAutoDiscoveryURL: $discovery_url,
CustomURLMapping: null,
IconURL: "",
Scopes: ["openid", "profile", "email"],
Scopes: $scopes,
AttributeSSHPublicKey: "",
RequiredClaimName: "",
RequiredClaimValue: "",
GroupClaimName: "",
AdminGroup: "",
GroupClaimName: $group_claim_name,
AdminGroup: $admin_group,
GroupTeamMap: "",
GroupTeamMapRemoval: false,
RestrictedGroup: ""
RestrictedGroup: $restricted_group
}')"
${pkgs.postgresql}/bin/psql -v ON_ERROR_STOP=1 \