372 lines
11 KiB
Nix
372 lines
11 KiB
Nix
{
|
|
lib,
|
|
ardaLib,
|
|
self,
|
|
pkgs,
|
|
settings,
|
|
...
|
|
}: let
|
|
createTerranixModule = {
|
|
users,
|
|
email_password,
|
|
key_file,
|
|
...
|
|
}: terra: let
|
|
inherit (lib) toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs concatMapStringsSep filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames;
|
|
inherit (ardaLib) toSnakeCase;
|
|
inherit (terra.lib) tfRef;
|
|
|
|
_refTypeMap = {
|
|
org = {type = "org";};
|
|
project = {type = "project";};
|
|
user = {
|
|
type = "user";
|
|
tfType = "human_user";
|
|
};
|
|
};
|
|
|
|
mapRef' = {
|
|
type,
|
|
tfType ? type,
|
|
}: name: {"${type}Id" = "\${ resource.zitadel_${tfType}.${toSnakeCase name}.id }";};
|
|
mapRef = type: name: mapRef' (_refTypeMap.${type}) name;
|
|
mapEnum = prefix: value: "${prefix}_${value |> toSnakeCase |> toUpper}";
|
|
|
|
mapValue = type: value: ({
|
|
appType = mapEnum "OIDC_APP_TYPE" value;
|
|
grantTypes = map (t: mapEnum "OIDC_GRANT_TYPE" t) value;
|
|
responseTypes = map (t: mapEnum "OIDC_RESPONSE_TYPE" t) value;
|
|
authMethodType = mapEnum "OIDC_AUTH_METHOD_TYPE" value;
|
|
|
|
flowType = mapEnum "FLOW_TYPE" value;
|
|
triggerType = mapEnum "TRIGGER_TYPE" value;
|
|
accessTokenType = mapEnum "OIDC_TOKEN_TYPE" value;
|
|
}."${type}" or value);
|
|
|
|
toResource = name: value:
|
|
nameValuePair
|
|
(toSnakeCase name)
|
|
(lib.mapAttrs' (k: v: nameValuePair (toSnakeCase k) (mapValue k v)) value);
|
|
|
|
withRef = type: name: attrs: attrs // (mapRef type name);
|
|
|
|
select = keys: callback: set:
|
|
if (length keys) == 0
|
|
then mapAttrs' callback set
|
|
else let
|
|
key = head keys;
|
|
in
|
|
concatMapAttrs (k: v: select (drop 1 keys) (callback k) (v.${key} or {})) set;
|
|
|
|
append = attrList: set: set // (listToAttrs attrList);
|
|
|
|
forEach = src: key: set: let
|
|
_key = concatMapStringsSep "_" (k: "\${item.${k}}") key;
|
|
in
|
|
{
|
|
forEach = tfRef '' {
|
|
for item in ${src} :
|
|
"''${item.org}_''${item.name}" => item
|
|
}'';
|
|
}
|
|
// set;
|
|
in {
|
|
terraform.required_providers.zitadel = {
|
|
source = "zitadel/zitadel";
|
|
version = "2.2.0";
|
|
};
|
|
|
|
provider.zitadel = {
|
|
domain = "auth.kruining.eu";
|
|
insecure = false;
|
|
|
|
system_api = {
|
|
user = "infra";
|
|
inherit key_file;
|
|
};
|
|
};
|
|
|
|
locals = {
|
|
extra_users = tfRef "
|
|
flatten([ for org, users in jsondecode(file(\"${users}\")): [
|
|
for name, details in users: {
|
|
org = org
|
|
name = name
|
|
email = details.email
|
|
firstName = details.firstName
|
|
lastName = details.lastName
|
|
}
|
|
] ])
|
|
";
|
|
orgs = settings.organization |> mapAttrs (org: _: tfRef "resource.zitadel_org.${org}.id");
|
|
};
|
|
|
|
resource = {
|
|
# Organizations
|
|
zitadel_org =
|
|
settings.organization
|
|
|> select [] (
|
|
name: {isDefault, ...}:
|
|
{inherit name isDefault;}
|
|
|> toResource name
|
|
);
|
|
|
|
# Projects per organization
|
|
zitadel_project =
|
|
settings.organization
|
|
|> select ["project"] (
|
|
org: name: {
|
|
hasProjectCheck,
|
|
privateLabelingSetting,
|
|
projectRoleAssertion,
|
|
projectRoleCheck,
|
|
...
|
|
}:
|
|
{
|
|
inherit name hasProjectCheck privateLabelingSetting projectRoleAssertion projectRoleCheck;
|
|
}
|
|
|> withRef "org" org
|
|
|> toResource "${org}_${name}"
|
|
);
|
|
|
|
# Each OIDC app per project
|
|
zitadel_application_oidc =
|
|
settings.organization
|
|
|> select ["project" "application"] (
|
|
org: project: name: {
|
|
redirectUris,
|
|
grantTypes,
|
|
responseTypes,
|
|
...
|
|
}:
|
|
{
|
|
inherit name redirectUris grantTypes responseTypes;
|
|
|
|
accessTokenRoleAssertion = true;
|
|
idTokenRoleAssertion = true;
|
|
accessTokenType = "JWT";
|
|
}
|
|
|> withRef "org" org
|
|
|> withRef "project" "${org}_${project}"
|
|
|> toResource "${org}_${project}_${name}"
|
|
);
|
|
|
|
# Each project role
|
|
zitadel_project_role =
|
|
settings.organization
|
|
|> select ["project" "role"] (
|
|
org: project: name: value:
|
|
{
|
|
inherit (value) displayName group;
|
|
roleKey = name;
|
|
}
|
|
|> withRef "org" org
|
|
|> withRef "project" "${org}_${project}"
|
|
|> toResource "${org}_${project}_${name}"
|
|
);
|
|
|
|
# Each project role assignment
|
|
zitadel_user_grant =
|
|
settings.organization
|
|
|> select ["project" "assign"] (
|
|
org: project: user: roles:
|
|
{roleKeys = roles;}
|
|
|> withRef "org" org
|
|
|> withRef "project" "${org}_${project}"
|
|
|> withRef "user" "${org}_${user}"
|
|
|> toResource "${org}_${project}_${user}"
|
|
);
|
|
|
|
# Users
|
|
zitadel_human_user =
|
|
settings.organization
|
|
|> select ["user"] (
|
|
org: name: {
|
|
email,
|
|
userName,
|
|
firstName,
|
|
lastName,
|
|
...
|
|
}:
|
|
{
|
|
inherit email userName firstName lastName;
|
|
|
|
isEmailVerified = true;
|
|
lifecycle = {
|
|
ignore_changes = ["first_name" "last_name" "user_name"];
|
|
};
|
|
}
|
|
|> withRef "org" org
|
|
|> toResource "${org}_${name}"
|
|
)
|
|
|> append [
|
|
(forEach "local.extra_users" ["org" "name"] {
|
|
orgId = tfRef "local.orgs[each.value.org]";
|
|
userName = tfRef "each.value.name";
|
|
email = tfRef "each.value.email";
|
|
firstName = tfRef "each.value.firstName";
|
|
lastName = tfRef "each.value.lastName";
|
|
|
|
isEmailVerified = true;
|
|
}
|
|
|> toResource "extraUsers")
|
|
];
|
|
|
|
# Global user roles
|
|
zitadel_instance_member =
|
|
settings.organization
|
|
|> filterAttrsRecursive (n: v: !(v ? "instanceRoles" && (length v.instanceRoles) == 0))
|
|
|> select ["user"] (
|
|
org: name: {instanceRoles, ...}:
|
|
{roles = instanceRoles;}
|
|
|> withRef "user" "${org}_${name}"
|
|
|> toResource "${org}_${name}"
|
|
);
|
|
|
|
# Organazation specific roles
|
|
zitadel_org_member =
|
|
settings.organization
|
|
|> filterAttrsRecursive (n: v: !(v ? "roles" && (length v.roles) == 0))
|
|
|> select ["user"] (
|
|
org: name: {roles, ...}:
|
|
{inherit roles;}
|
|
|> withRef "org" org
|
|
|> withRef "user" "${org}_${name}"
|
|
|> toResource "${org}_${name}"
|
|
);
|
|
|
|
# Organazation's actions
|
|
zitadel_action =
|
|
settings.organization
|
|
|> select ["action"] (
|
|
org: name: {
|
|
timeout,
|
|
allowedToFail,
|
|
script,
|
|
...
|
|
}:
|
|
{
|
|
inherit allowedToFail name;
|
|
timeout = "${toString timeout}s";
|
|
script = "const ${name} = ${script}";
|
|
}
|
|
|> withRef "org" org
|
|
|> toResource "${org}_${name}"
|
|
);
|
|
|
|
# Organazation's action assignments
|
|
zitadel_trigger_actions =
|
|
settings.organization
|
|
|> concatMapAttrs (
|
|
org: {triggers, ...}:
|
|
triggers
|
|
|> imap0 (i: {
|
|
flowType,
|
|
triggerType,
|
|
actions,
|
|
...
|
|
}: (
|
|
let
|
|
name = "trigger_${toString i}";
|
|
in
|
|
{
|
|
inherit flowType triggerType;
|
|
|
|
actionIds =
|
|
actions
|
|
|> map (action: (tfRef "zitadel_action.${org}_${toSnakeCase action}.id"));
|
|
}
|
|
|> withRef "org" org
|
|
|> toResource "${org}_${name}"
|
|
))
|
|
|> listToAttrs
|
|
);
|
|
|
|
# SMTP config
|
|
zitadel_smtp_config.default = {
|
|
sender_address = "chris@kruining.eu";
|
|
sender_name = "no-reply (Zitadel)";
|
|
tls = true;
|
|
host = "black-mail.nl:587";
|
|
user = "chris@kruining.eu";
|
|
password = tfRef "file(\"${email_password}\")";
|
|
set_active = true;
|
|
};
|
|
|
|
# Client credentials per app
|
|
local_sensitive_file =
|
|
settings.organization
|
|
|> select ["project" "application"] (
|
|
org: project: name: {exportMap, ...}:
|
|
nameValuePair "${org}_${project}_${name}" {
|
|
content = ''
|
|
${
|
|
if exportMap.client_id != null
|
|
then exportMap.client_id
|
|
else "CLIENT_ID"
|
|
}=${tfRef "resource.zitadel_application_oidc.${org}_${project}_${name}.client_id"}
|
|
${
|
|
if exportMap.client_secret != null
|
|
then exportMap.client_secret
|
|
else "CLIENT_SECRET"
|
|
}=${tfRef "resource.zitadel_application_oidc.${org}_${project}_${name}.client_secret"}
|
|
'';
|
|
filename = "/var/lib/zitadel/clients/${org}_${project}_${name}";
|
|
}
|
|
);
|
|
};
|
|
};
|
|
in {
|
|
createInfra = args @ {...}: let
|
|
tofu = "${lib.getExe pkgs.opentofu} -input=false";
|
|
terraformConfiguration = self.inputs.terranix.lib.terranixConfiguration {
|
|
system = pkgs.stdenv.hostPlatform.system;
|
|
modules = [
|
|
(createTerranixModule args)
|
|
];
|
|
};
|
|
in {
|
|
systemd.services."infra-zitadel" = {
|
|
description = "Infra for Zitadel";
|
|
|
|
wantedBy = ["multi-user.target"];
|
|
wants = ["zitadel.service"];
|
|
after = ["zitadel.service"];
|
|
|
|
preStart = ''
|
|
install -d -m 0770 -o zitadel -g media /var/lib/infra-zitadel
|
|
'';
|
|
|
|
script = ''
|
|
# Sleep for a bit to give the service a chance to start up
|
|
sleep 5s
|
|
|
|
if [ "$(systemctl is-active zitadel)" != "active" ]; then
|
|
echo "zitadel is not running"
|
|
exit 1
|
|
fi
|
|
|
|
# Print the path to the source for easier debugging
|
|
echo "config location: ${terraformConfiguration}"
|
|
|
|
# Copy infra code into workspace
|
|
cp -f ${terraformConfiguration} config.tf.json
|
|
|
|
# Initialize OpenTofu
|
|
${tofu} init
|
|
|
|
# Run the infrastructure code
|
|
${tofu} plan -out=tfplan
|
|
${tofu} apply -json -auto-approve tfplan
|
|
'';
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
User = "zitadel";
|
|
Group = "zitadel";
|
|
|
|
StateDirectory = "/var/lib/infra-zitadel";
|
|
};
|
|
};
|
|
};
|
|
}
|