From 334c0b54cc4d13dd5f8b3902cecc28e9e37a67fd Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 27 Oct 2025 07:41:12 +0000 Subject: [PATCH 1/4] ops(secrets): removed secret "email/info@amarth.cloud" from machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 6add209..3fa58fa 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -1,5 +1,4 @@ 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] info_amarth_cloud: ENC[AES256_GCM,data:/x7aAFAxXYYf79tB08VQmmuTIy2TvdSTFfAzIWdIr+I=,iv:plNxS6oOin+oEql+1xsePOsUfLJkf+ZPBviPRTbIghE=,tag:hjtK3rysd2NNBA2mWdv8cw==,type:str] zitadel: @@ -24,7 +23,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-10-23T14:25:59Z" - mac: ENC[AES256_GCM,data:p3A1ZSr6S21SUjEZbL4V0uh3HVqcRhFi1N93IeUKs2yVbBYAXzWJ+2ejSxfM+W9MSCAYxx27i0ZoBPjQJu/xQzwmW8HWn4rRfCsa2TGqOw25PLvkHgnBUc70X759cKxvR0Pm7ha22JCnzJVrzvUMlBVs61wxHT57x0El9Gan8eY=,iv:SKN+R4wsN/L2pZW/s5ocEtCXXZB5wK4tgFIYWGWtRPA=,tag:CNLl4lVO06gAcsSCfU2KjA==,type:str] + lastmodified: "2025-10-27T07:41:09Z" + mac: ENC[AES256_GCM,data:jc/hbXqdsLHkOldzmk68Uj9FnToLgfbF4YDzLv5SqPEBt1lihkOjeBD8tGq1w0LIJnWZTHv4yC1IEsJkB3r1a5E9OtukdNpdpDKfo5mf9+tACJ/d27RyYrLfmo/HUfAuk2WEbhQ3pqP8z+JhZ2R32+tfUi0hrmBlgtSJ7w53vpM=,iv:C/5HpoyVO9lDJBmBTROVGux74c0ZIP6N93urzk+kZ2E=,tag:LwTWbBToKkKEPyzBKvtr3A==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From e92f2cf82c7a4bb662cdcc15cc85d38c8b8af3d9 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 27 Oct 2025 11:34:11 +0100 Subject: [PATCH 2/4] add some commands to read secret values --- .just/vars.just | 4 ++++ .justfile | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.just/vars.just b/.just/vars.just index 46bb5fd..167144a 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -1,5 +1,6 @@ base_path := invocation_directory() / "systems/x86_64-linux" sops := "nix shell nixpkgs#sops --command sops" +yq := "nix shell nixpkgs#yq --command yq" @_default: just --list @@ -19,6 +20,9 @@ list machine: echo "Done" +@get machine key: + {{ sops }} decrypt {{ base_path }}/{{ machine }}/secrets.yml | {{ yq }} ".$(echo "{{ key }}" | sed -E 's/\//./g')" + @remove machine key: {{ sops }} unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" diff --git a/.justfile b/.justfile index 1c9fc03..2788376 100644 --- a/.justfile +++ b/.justfile @@ -15,4 +15,8 @@ mod machine '.just/machine.just' @update: nix flake update git commit -m 'chore: update dependencies' -- ./flake.lock > /dev/null - echo "Done" \ No newline at end of file + echo "Done" + +[doc('Introspection on flake output')] +@select key: + nix eval --json .#{{ key }} | jq . \ No newline at end of file From 6c9667831a54f5097ef276a8317d3fb5f3ebe43a Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 27 Oct 2025 13:11:42 +0000 Subject: [PATCH 3/4] ops(secrets): set secret "zitadel/masterKey" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 3fa58fa..f9e4a82 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -2,7 +2,7 @@ email: 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] 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: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -23,7 +23,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-10-27T07:41:09Z" - mac: ENC[AES256_GCM,data:jc/hbXqdsLHkOldzmk68Uj9FnToLgfbF4YDzLv5SqPEBt1lihkOjeBD8tGq1w0LIJnWZTHv4yC1IEsJkB3r1a5E9OtukdNpdpDKfo5mf9+tACJ/d27RyYrLfmo/HUfAuk2WEbhQ3pqP8z+JhZ2R32+tfUi0hrmBlgtSJ7w53vpM=,iv:C/5HpoyVO9lDJBmBTROVGux74c0ZIP6N93urzk+kZ2E=,tag:LwTWbBToKkKEPyzBKvtr3A==,type:str] + lastmodified: "2025-10-27T13:11:41Z" + mac: ENC[AES256_GCM,data:0LS7xQlkfIZRVwAZPE33KmPA19CpnXj/t4hpDrVW+BbESpnBku2oxPB/Cvp0dY5MGnDFgU4Htp0JoppHCgKvkaSBhvjxjW2DT1Nkk5PBmAtuzZLW4qc25ZVlqiKgzj1LE3XPTbqUJyp+X3U23BnU1ViTGgHuBcdEV7TFNHjmnwk=,iv:HpVIDAU1FbrUKXW8klWq0Kn9ZtKcgwR1jKXLkGtDd5A=,tag:50P0UZtj77npD92zxCaZHw==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 84cc5ff5c4586136e091c3c087b787c9326fd869 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 27 Oct 2025 17:07:51 +0100 Subject: [PATCH 4/4] feat(zitadel): expand terranix resources 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 --- .just/machine.just | 2 +- .../authentication/zitadel/default.nix | 184 +++++++++++++----- systems/x86_64-linux/ulmo/default.nix | 19 +- 3 files changed, 149 insertions(+), 56 deletions(-) diff --git a/.just/machine.just b/.just/machine.just index 65d1a7b..6dabbc0 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -6,4 +6,4 @@ [doc('Update the target machine')] update machine: - nixos-rebuild switch --use-remote-sudo --target-host {{ machine }} --flake .#{{ machine }} \ No newline at end of file + nixos-rebuild switch --use-remote-sudo --target-host {{ machine }} --flake ..#{{ machine }} \ No newline at end of file diff --git a/modules/nixos/services/authentication/zitadel/default.nix b/modules/nixos/services/authentication/zitadel/default.nix index 59abcf3..eaa3c60 100644 --- a/modules/nixos/services/authentication/zitadel/default.nix +++ b/modules/nixos/services/authentication/zitadel/default.nix @@ -12,8 +12,12 @@ in enable = mkEnableOption "Zitadel"; organization = mkOption { - type = types.attrsOf (types.submodule { - options = { + type = types.attrsOf (types.submodule ({ name, ... }: { + options = + let + org = name; + in + { isDefault = mkOption { type = types.bool; 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 - 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}"; mapValue = type: value: ({ @@ -128,6 +201,7 @@ in withName = name: attrs: attrs // { inherit name; }; withRef = type: name: attrs: attrs // (mapRef type name); + withDefaults = defaults: attrs: defaults // attrs; select = keys: callback: set: if (length keys) == 0 then @@ -156,6 +230,7 @@ in }; resource = { + # Organizations zitadel_org = cfg.organization |> select [] (name: value: value |> getAttrs [ "isDefault" ] @@ -163,6 +238,7 @@ in |> toResource name ); + # Projects per organization zitadel_project = cfg.organization |> select [ "project" ] (org: name: value: value |> getAttrs [ "hasProjectCheck" "privateLabelingSetting" "projectRoleAssertion" "projectRoleCheck" ] @@ -171,6 +247,7 @@ in |> toResource name ); + # Each OIDC app per project zitadel_application_oidc = cfg.organization |> select [ "project" "application" ] (org: project: name: value: value |> getAttrs [ "redirectUris" "grantTypes" "responseTypes" ] @@ -180,14 +257,52 @@ in |> 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 = { sender_address = "chris@kruining.eu"; sender_name = "no-reply (Zitadel)"; tls = true; - host = "black-mail.nl"; + host = "black-mail.nl:587"; 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 = [ "d /tmp/zitadelApplyTerraform 0755 zitadel zitadel -" + "d /var/lib/zitadel/clients 0755 zitadel zitadel -" ]; systemd.services.zitadelApplyTerraform = { @@ -214,6 +330,11 @@ in script = '' #!/usr/bin/env bash + if [ "$(systemctl is-active zitadel)" != "active" ]; then + echo "Zitadel is not running" + exit 1 + fi + # Copy infra code into workspace cp -f ${terraformConfiguration} config.tf.json @@ -237,8 +358,7 @@ in zitadel = { enable = true; openFirewall = true; - # masterKeyFile = config.sops.secrets."zitadel/masterKey".path; - masterKeyFile = "/var/lib/zitadel/master_key"; + masterKeyFile = config.sops.secrets."zitadel/masterKey".path; tlsMode = "external"; settings = { Port = 9092; @@ -256,31 +376,6 @@ in 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 = { Host = "localhost"; # Zitadel will report error if port is not set @@ -335,9 +430,9 @@ in }; }; }; - extraStepsPaths = [ - config.sops.templates."secrets.yaml".path - ]; + # extraStepsPaths = [ + # config.sops.templates."secrets.yaml".path + # ]; }; postgresql = { @@ -386,23 +481,6 @@ in 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"; - }; }; }; } diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 4845e73..e776927 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -43,9 +43,18 @@ enable = true; organization = { - thisIsMyAwesomeOrg = {}; - nix = { + user = { + chris = { + email = "chris@kruining.eu"; + firstName = "Chris"; + lastName = "Kruining"; + + roles = [ "ORG_OWNER" ]; + instanceRoles = [ "IAM_OWNER" ]; + }; + }; + project = { ulmo = { application = { @@ -60,6 +69,12 @@ grantTypes = [ "authorizationCode" ]; responseTypes = [ "code" ]; }; + + matrix = { + redirectUris = [ "https://matrix.kruining.eu/_synapse/client/oidc/callback" ]; + grantTypes = [ "authorizationCode" ]; + responseTypes = [ "code" ]; + }; }; }; };