Compare commits

...

4 commits

Author SHA1 Message Date
Chris Kruining
84cc5ff5c4
feat(zitadel): expand terranix resources
Some checks failed
Test action / kaas (push) Failing after 1s
WOOP WOOP, it all works!
now the next, big, huge, giant, hurdle to overcome is the chicken and egg problem of needing zitadel to generate values that I need inside the nix config of synapse, forgejo, and jellyfin
2025-10-27 17:07:51 +01:00
6c9667831a ops(secrets): set secret "zitadel/masterKey" for machine "ulmo" 2025-10-27 13:11:42 +00:00
Chris Kruining
e92f2cf82c
add some commands to read secret
values
2025-10-27 11:34:11 +01:00
334c0b54cc ops(secrets): removed secret "email/info@amarth.cloud" from machine "ulmo" 2025-10-27 07:41:12 +00:00
6 changed files with 161 additions and 61 deletions

View file

@ -6,4 +6,4 @@
[doc('Update the target machine')] [doc('Update the target machine')]
update machine: update machine:
nixos-rebuild switch --use-remote-sudo --target-host {{ machine }} --flake .#{{ machine }} nixos-rebuild switch --use-remote-sudo --target-host {{ machine }} --flake ..#{{ machine }}

View file

@ -1,5 +1,6 @@
base_path := invocation_directory() / "systems/x86_64-linux" base_path := invocation_directory() / "systems/x86_64-linux"
sops := "nix shell nixpkgs#sops --command sops" sops := "nix shell nixpkgs#sops --command sops"
yq := "nix shell nixpkgs#yq --command yq"
@_default: @_default:
just --list just --list
@ -19,6 +20,9 @@ list machine:
echo "Done" echo "Done"
@get machine key:
{{ sops }} decrypt {{ base_path }}/{{ machine }}/secrets.yml | {{ yq }} ".$(echo "{{ key }}" | sed -E 's/\//./g')"
@remove machine key: @remove machine key:
{{ sops }} unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" {{ sops }} unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')"

View file

@ -16,3 +16,7 @@ mod machine '.just/machine.just'
nix flake update nix flake update
git commit -m 'chore: update dependencies' -- ./flake.lock > /dev/null git commit -m 'chore: update dependencies' -- ./flake.lock > /dev/null
echo "Done" echo "Done"
[doc('Introspection on flake output')]
@select key:
nix eval --json .#{{ key }} | jq .

View file

@ -12,8 +12,12 @@ in
enable = mkEnableOption "Zitadel"; enable = mkEnableOption "Zitadel";
organization = mkOption { organization = mkOption {
type = types.attrsOf (types.submodule { type = types.attrsOf (types.submodule ({ name, ... }: {
options = { options =
let
org = name;
in
{
isDefault = mkOption { isDefault = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
@ -108,13 +112,82 @@ in
}; };
}); });
}; };
user = mkOption {
default = {};
type = types.attrsOf (types.submodule ({ name, ... }: {
options =
let
username = name;
in
{
email = mkOption {
type = types.str;
example = "someone@some.domain";
description = ''
Username.
'';
}; };
});
userName = mkOption {
type = types.nullOr types.str;
default = cfg.organization.${org}.user.${username}.email;
example = "someone@some.domain";
description = ''
Username. Default value is the user's email, you can overwrite that by setting this option
'';
};
firstName = mkOption {
type = types.str;
example = "John";
description = ''
First name of the user.
'';
};
lastName = mkOption {
type = types.str;
example = "Doe";
description = ''
Last name of the user.
'';
};
roles = mkOption {
type = types.listOf types.str;
default = [];
example = "[ \"ORG_OWNER\" ]";
description = ''
List of roles granted to organisation.
'';
};
instanceRoles = mkOption {
type = types.listOf types.str;
default = [];
example = "[ \"IAM_OWNER\" ]";
description = ''
List of roles granted to instance.
'';
};
};
}));
};
};
}));
}; };
}; };
config = let config = let
mapRef = type: name: { "${type}Id" = "\${ resource.zitadel_${type}.${toSnakeCase name}.id }"; }; _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}"; mapEnum = prefix: value: "${prefix}_${value |> toSnakeCase |> toUpper}";
mapValue = type: value: ({ mapValue = type: value: ({
@ -128,6 +201,7 @@ in
withName = name: attrs: attrs // { inherit name; }; withName = name: attrs: attrs // { inherit name; };
withRef = type: name: attrs: attrs // (mapRef type name); withRef = type: name: attrs: attrs // (mapRef type name);
withDefaults = defaults: attrs: defaults // attrs;
select = keys: callback: set: select = keys: callback: set:
if (length keys) == 0 then if (length keys) == 0 then
@ -156,6 +230,7 @@ in
}; };
resource = { resource = {
# Organizations
zitadel_org = cfg.organization |> select [] (name: value: zitadel_org = cfg.organization |> select [] (name: value:
value value
|> getAttrs [ "isDefault" ] |> getAttrs [ "isDefault" ]
@ -163,6 +238,7 @@ in
|> toResource name |> toResource name
); );
# Projects per organization
zitadel_project = cfg.organization |> select [ "project" ] (org: name: value: zitadel_project = cfg.organization |> select [ "project" ] (org: name: value:
value value
|> getAttrs [ "hasProjectCheck" "privateLabelingSetting" "projectRoleAssertion" "projectRoleCheck" ] |> getAttrs [ "hasProjectCheck" "privateLabelingSetting" "projectRoleAssertion" "projectRoleCheck" ]
@ -171,6 +247,7 @@ in
|> toResource name |> toResource name
); );
# Each OIDC app per project
zitadel_application_oidc = cfg.organization |> select [ "project" "application" ] (org: project: name: value: zitadel_application_oidc = cfg.organization |> select [ "project" "application" ] (org: project: name: value:
value value
|> getAttrs [ "redirectUris" "grantTypes" "responseTypes" ] |> getAttrs [ "redirectUris" "grantTypes" "responseTypes" ]
@ -180,14 +257,52 @@ in
|> toResource name |> toResource name
); );
# Users
zitadel_human_user = cfg.organization |> select [ "user" ] (org: name: value:
value
|> getAttrs [ "email" "userName" "firstName" "lastName" ]
|> withRef "org" org
|> withDefaults { isEmailVerified = true; }
|> toResource name
);
# Global user roles
zitadel_instance_member = cfg.organization |> select [ "user" ] (org: name: value:
{ roles = value.instanceRoles; }
|> withRef "user" name
|> toResource name
);
# Organazation specific roles
zitadel_org_member = cfg.organization |> select [ "user" ] (org: name: value:
value
|> getAttrs [ "roles" ]
|> withRef "org" org
|> withRef "user" name
|> toResource name
);
# SMTP config
zitadel_smtp_config.default = { zitadel_smtp_config.default = {
sender_address = "chris@kruining.eu"; sender_address = "chris@kruining.eu";
sender_name = "no-reply (Zitadel)"; sender_name = "no-reply (Zitadel)";
tls = true; tls = true;
host = "black-mail.nl"; host = "black-mail.nl:587";
user = "chris@kruining.eu"; user = "chris@kruining.eu";
password = "\${file(\"${config'.sops.templates."kaas".path}\")}"; password = lib.tfRef "file(\"${config'.sops.secrets."email/chris_kruining_eu".path}\")";
set_active = true;
}; };
# Client credentials per app
local_sensitive_file = cfg.organization |> select [ "project" "application" ] (org: project: name: value:
nameValuePair name {
content = ''
CLIENT_ID=${lib.tfRef "resource.zitadel_application_oidc.${name}.client_id"}
CLIENT_SECRET=${lib.tfRef "resource.zitadel_application_oidc.${name}.client_secret"}
'';
filename = "/var/lib/zitadel/clients/${name}";
}
);
}; };
}; };
}) })
@ -203,6 +318,7 @@ in
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d /tmp/zitadelApplyTerraform 0755 zitadel zitadel -" "d /tmp/zitadelApplyTerraform 0755 zitadel zitadel -"
"d /var/lib/zitadel/clients 0755 zitadel zitadel -"
]; ];
systemd.services.zitadelApplyTerraform = { systemd.services.zitadelApplyTerraform = {
@ -214,6 +330,11 @@ in
script = '' script = ''
#!/usr/bin/env bash #!/usr/bin/env bash
if [ "$(systemctl is-active zitadel)" != "active" ]; then
echo "Zitadel is not running"
exit 1
fi
# Copy infra code into workspace # Copy infra code into workspace
cp -f ${terraformConfiguration} config.tf.json cp -f ${terraformConfiguration} config.tf.json
@ -237,8 +358,7 @@ in
zitadel = { zitadel = {
enable = true; enable = true;
openFirewall = true; openFirewall = true;
# masterKeyFile = config.sops.secrets."zitadel/masterKey".path; masterKeyFile = config.sops.secrets."zitadel/masterKey".path;
masterKeyFile = "/var/lib/zitadel/master_key";
tlsMode = "external"; tlsMode = "external";
settings = { settings = {
Port = 9092; Port = 9092;
@ -256,31 +376,6 @@ in
SecretHasher.Hasher.Algorithm = "argon2id"; SecretHasher.Hasher.Algorithm = "argon2id";
}; };
DefaultInstance = {
# PasswordComplexityPolicy = {
# MinLength = 0;
# HasLowercase = false;
# HasUppercase = false;
# HasNumber = false;
# HasSymbol = false;
# };
# LoginPolicy = {
# AllowRegister = false;
# ForceMFA = true;
# };
# LockoutPolicy = {
# MaxPasswordAttempts = 5;
# MaxOTPAttempts = 10;
# };
SMTPConfiguration = {
SMTP = {
Host = "black-mail.nl:587";
User = "chris@kruining.eu";
};
FromName = "Amarth Zitadel";
};
};
Database.postgres = { Database.postgres = {
Host = "localhost"; Host = "localhost";
# Zitadel will report error if port is not set # Zitadel will report error if port is not set
@ -335,9 +430,9 @@ in
}; };
}; };
}; };
extraStepsPaths = [ # extraStepsPaths = [
config.sops.templates."secrets.yaml".path # config.sops.templates."secrets.yaml".path
]; # ];
}; };
postgresql = { postgresql = {
@ -386,23 +481,6 @@ in
restartUnits = [ "zitadel.service" ]; restartUnits = [ "zitadel.service" ];
}; };
}; };
templates."secrets.yaml" = {
owner = "zitadel";
group = "zitadel";
content = ''
DefaultInstance:
SMTPConfiguration:
SMTP:
Password: ${config.sops.placeholder."email/chris_kruining_eu"}
'';
};
templates."kaas" = {
owner = "zitadel";
group = "zitadel";
content = config.sops.placeholder."email/chris_kruining_eu";
};
}; };
}; };
} }

View file

@ -43,9 +43,18 @@
enable = true; enable = true;
organization = { organization = {
thisIsMyAwesomeOrg = {};
nix = { nix = {
user = {
chris = {
email = "chris@kruining.eu";
firstName = "Chris";
lastName = "Kruining";
roles = [ "ORG_OWNER" ];
instanceRoles = [ "IAM_OWNER" ];
};
};
project = { project = {
ulmo = { ulmo = {
application = { application = {
@ -60,6 +69,12 @@
grantTypes = [ "authorizationCode" ]; grantTypes = [ "authorizationCode" ];
responseTypes = [ "code" ]; responseTypes = [ "code" ];
}; };
matrix = {
redirectUris = [ "https://matrix.kruining.eu/_synapse/client/oidc/callback" ];
grantTypes = [ "authorizationCode" ];
responseTypes = [ "code" ];
};
}; };
}; };
}; };

View file

@ -1,9 +1,8 @@
email: email:
info@amarth.cloud: ENC[AES256_GCM,data:xwR3XS/zxr85e8wQLqIJfc8b3CaRlMqts3kWQpQTy6c=,iv:6N48IIRhFvgPtzP7/w6ZQM80mHCZ7ZHAsvv2tHFP9mE=,tag:FK2OboYbnmgq6eJp5Oyjng==,type:str]
chris_kruining_eu: ENC[AES256_GCM,data:/JS+dQ6ABlkdjRZP+sGeUY3js30swS4=,iv:d5CcoY6DD3DJ/e3t0OU/KUULccJpTN0uBQPQzl/3R0s=,tag:aTN7RdzXkIpci9tEBjevSA==,type:str] chris_kruining_eu: ENC[AES256_GCM,data:/JS+dQ6ABlkdjRZP+sGeUY3js30swS4=,iv:d5CcoY6DD3DJ/e3t0OU/KUULccJpTN0uBQPQzl/3R0s=,tag:aTN7RdzXkIpci9tEBjevSA==,type:str]
info_amarth_cloud: ENC[AES256_GCM,data:/x7aAFAxXYYf79tB08VQmmuTIy2TvdSTFfAzIWdIr+I=,iv:plNxS6oOin+oEql+1xsePOsUfLJkf+ZPBviPRTbIghE=,tag:hjtK3rysd2NNBA2mWdv8cw==,type:str] info_amarth_cloud: ENC[AES256_GCM,data:/x7aAFAxXYYf79tB08VQmmuTIy2TvdSTFfAzIWdIr+I=,iv:plNxS6oOin+oEql+1xsePOsUfLJkf+ZPBviPRTbIghE=,tag:hjtK3rysd2NNBA2mWdv8cw==,type:str]
zitadel: zitadel:
masterKey: ENC[AES256_GCM,data:DyBNWV+4HmPa1mA4I3TERWmrIEn/c4/XYlgfmel7Ag==,iv:CjS5kAHH8j0ExCNFZf3dnyBsDPnAShRt55onPcUfkwU=,tag:CeINNaH5hOprAxm/DZFDPA==,type:str] masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str]
sops: sops:
age: age:
- recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq
@ -24,7 +23,7 @@ sops:
TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb
Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-10-23T14:25:59Z" lastmodified: "2025-10-27T13:11:41Z"
mac: ENC[AES256_GCM,data:p3A1ZSr6S21SUjEZbL4V0uh3HVqcRhFi1N93IeUKs2yVbBYAXzWJ+2ejSxfM+W9MSCAYxx27i0ZoBPjQJu/xQzwmW8HWn4rRfCsa2TGqOw25PLvkHgnBUc70X759cKxvR0Pm7ha22JCnzJVrzvUMlBVs61wxHT57x0El9Gan8eY=,iv:SKN+R4wsN/L2pZW/s5ocEtCXXZB5wK4tgFIYWGWtRPA=,tag:CNLl4lVO06gAcsSCfU2KjA==,type:str] mac: ENC[AES256_GCM,data:0LS7xQlkfIZRVwAZPE33KmPA19CpnXj/t4hpDrVW+BbESpnBku2oxPB/Cvp0dY5MGnDFgU4Htp0JoppHCgKvkaSBhvjxjW2DT1Nkk5PBmAtuzZLW4qc25ZVlqiKgzj1LE3XPTbqUJyp+X3U23BnU1ViTGgHuBcdEV7TFNHjmnwk=,iv:HpVIDAU1FbrUKXW8klWq0Kn9ZtKcgwR1jKXLkGtDd5A=,tag:50P0UZtj77npD92zxCaZHw==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0