initial implementation of terranix for zitadel. SUPER HAPPY, SUPER COOL!!!
Some checks failed
Test action / kaas (push) Failing after 1s
Some checks failed
Test action / kaas (push) Failing after 1s
This commit is contained in:
parent
81e1574023
commit
1873bb7170
5 changed files with 368 additions and 31 deletions
59
flake.lock
generated
59
flake.lock
generated
|
|
@ -265,6 +265,27 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-parts_3": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"terranix",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1736143030,
|
||||||
|
"narHash": "sha256-+hu54pAoLDEZT9pjHlqL9DNzWz0NbUn8NEAHP7PQPzU=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "b905f6fc23a9051a6e1b741e1438dbfc0634c6de",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
|
|
@ -906,6 +927,7 @@
|
||||||
"snowfall-lib": "snowfall-lib",
|
"snowfall-lib": "snowfall-lib",
|
||||||
"sops-nix": "sops-nix",
|
"sops-nix": "sops-nix",
|
||||||
"stylix": "stylix",
|
"stylix": "stylix",
|
||||||
|
"terranix": "terranix",
|
||||||
"zen-browser": "zen-browser"
|
"zen-browser": "zen-browser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1109,6 +1131,43 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"systems_7": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"terranix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts_3",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": "systems_7"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1757278723,
|
||||||
|
"narHash": "sha256-hTMi6oGU+6VRnW9SZZ+muFcbfMEf2ajjOp7Z2KM5MMY=",
|
||||||
|
"owner": "terranix",
|
||||||
|
"repo": "terranix",
|
||||||
|
"rev": "924573fa6587ac57b0d15037fbd2d3f0fcdf17fb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "terranix",
|
||||||
|
"repo": "terranix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tinted-foot": {
|
"tinted-foot": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,11 @@
|
||||||
flake-compat.follows = "";
|
flake-compat.follows = "";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
terranix = {
|
||||||
|
url = "github:terranix/terranix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs: inputs.snowfall-lib.mkFlake {
|
outputs = inputs: inputs.snowfall-lib.mkFlake {
|
||||||
|
|
|
||||||
17
lib/strings/default.nix
Normal file
17
lib/strings/default.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{ lib, ...}:
|
||||||
|
let
|
||||||
|
inherit (builtins) isString typeOf;
|
||||||
|
inherit (lib) throwIfNot concatStringsSep splitStringBy toLower map;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
strings = {
|
||||||
|
toSnakeCase =
|
||||||
|
str:
|
||||||
|
throwIfNot (isString str) "toSnakeCase only accepts string values, but got ${typeOf str}" (
|
||||||
|
str
|
||||||
|
|> splitStringBy (prev: curr: builtins.match "[a-z]" prev != null && builtins.match "[A-Z]" curr != null) true
|
||||||
|
|> map (p: toLower p)
|
||||||
|
|> concatStringsSep "_"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{ config, lib, pkgs, namespace, ... }:
|
{ config, lib, pkgs, namespace, system, inputs, ... }:
|
||||||
let
|
let
|
||||||
inherit (lib) mkIf mkEnableOption;
|
inherit (lib) mkIf mkEnableOption mkOption types toUpper nameValuePair;
|
||||||
|
inherit (lib.${namespace}.strings) toSnakeCase;
|
||||||
|
|
||||||
cfg = config.${namespace}.services.authentication.zitadel;
|
cfg = config.${namespace}.services.authentication.zitadel;
|
||||||
|
|
||||||
|
|
@ -9,15 +10,223 @@ in
|
||||||
{
|
{
|
||||||
options.${namespace}.services.authentication.zitadel = {
|
options.${namespace}.services.authentication.zitadel = {
|
||||||
enable = mkEnableOption "Zitadel";
|
enable = mkEnableOption "Zitadel";
|
||||||
|
|
||||||
|
organization = mkOption {
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
isDefault = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
example = "true";
|
||||||
|
description = ''
|
||||||
|
True sets the org as default org for the instance. Only one org can be default org.
|
||||||
|
Nothing happens if you set it to false until you set another org as default org.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
project = mkOption {
|
||||||
|
default = {};
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
hasProjectCheck = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
example = "true";
|
||||||
|
description = ''
|
||||||
|
ZITADEL checks if the org of the user has permission to this project.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
privateLabelingSetting = mkOption {
|
||||||
|
type = types.nullOr (types.enum [ "unspecified" "enforceProjectResourceOwnerPolicy" "allowLoginUserResourceOwnerPolicy" ]);
|
||||||
|
default = null;
|
||||||
|
example = "enforceProjectResourceOwnerPolicy";
|
||||||
|
description = ''
|
||||||
|
Defines from where the private labeling should be triggered,
|
||||||
|
|
||||||
|
supported values:
|
||||||
|
- unspecified
|
||||||
|
- enforceProjectResourceOwnerPolicy
|
||||||
|
- allowLoginUserResourceOwnerPolicy
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
projectRoleAssertion = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
example = "true";
|
||||||
|
description = ''
|
||||||
|
Describes if roles of user should be added in token.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
projectRoleCheck = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
example = "true";
|
||||||
|
description = ''
|
||||||
|
ZITADEL checks if the user has at least one on this project.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
application = mkOption {
|
||||||
|
default = {};
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
redirectUris = mkOption {
|
||||||
|
type = types.nonEmptyListOf types.str;
|
||||||
|
example = ''
|
||||||
|
[ "https://example.com/redirect/url" ]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
grantTypes = mkOption {
|
||||||
|
type = types.nonEmptyListOf (types.enum [ "authorizationCode" "implicit" "refreshToken" "deviceCode" "tokenExchange" ]);
|
||||||
|
example = ''
|
||||||
|
[ "authorizationCode" ]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
responseTypes = mkOption {
|
||||||
|
type = types.nonEmptyListOf (types.enum [ "code" "idToken" "idTokenToken" ]);
|
||||||
|
example = ''
|
||||||
|
[ "code" ]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = let
|
||||||
|
mapRef = type: name: { "${type}Id" = "\${ resource.zitadel_${type}.${toSnakeCase name}.id }"; };
|
||||||
|
mapEnum = prefix: value: "${prefix}_${value |> toSnakeCase |> toUpper}";
|
||||||
|
|
||||||
|
mapValue = type: value: ({
|
||||||
|
grantTypes = map (t: mapEnum "OIDC_GRANT_TYPE" t) value;
|
||||||
|
responseTypes = map (t: mapEnum "OIDC_RESPONSE_TYPE" t) value;
|
||||||
|
}."${type}" or value);
|
||||||
|
|
||||||
|
toResource = name: value: nameValuePair
|
||||||
|
(toSnakeCase name)
|
||||||
|
(lib.mapAttrs' (k: v: nameValuePair (toSnakeCase k) (mapValue k v)) value);
|
||||||
|
|
||||||
|
withName = name: attrs: attrs // { inherit name; };
|
||||||
|
withRef = type: name: attrs: attrs // (mapRef type name);
|
||||||
|
|
||||||
|
# this is a nix package, the generated json file to be exact
|
||||||
|
terraformConfiguration = inputs.terranix.lib.terranixConfiguration {
|
||||||
|
inherit system;
|
||||||
|
|
||||||
|
modules =
|
||||||
|
let
|
||||||
|
inherit (lib) mapAttrs' concatMapAttrs nameValuePair getAttrs getAttr hasAttr typeOf head drop length;
|
||||||
|
|
||||||
|
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;
|
||||||
|
in
|
||||||
|
[
|
||||||
|
({ config, lib, ... }: {
|
||||||
|
config = {
|
||||||
|
terraform.required_providers.zitadel = {
|
||||||
|
source = "zitadel/zitadel";
|
||||||
|
version = "2.2.0";
|
||||||
|
};
|
||||||
|
|
||||||
|
provider.zitadel = {
|
||||||
|
domain = "auth.kruining.eu";
|
||||||
|
insecure = "false";
|
||||||
|
jwt_profile_file = "/var/lib/zitadel/machine-key.json";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource = {
|
||||||
|
zitadel_org = cfg.organization |> select [] (name: value:
|
||||||
|
value
|
||||||
|
|> getAttrs [ "isDefault" ]
|
||||||
|
|> withName name
|
||||||
|
|> toResource name
|
||||||
|
);
|
||||||
|
|
||||||
|
zitadel_project = cfg.organization |> select [ "project" ] (org: name: value:
|
||||||
|
value
|
||||||
|
|> getAttrs [ "hasProjectCheck" "privateLabelingSetting" "projectRoleAssertion" "projectRoleCheck" ]
|
||||||
|
|> withName name
|
||||||
|
|> withRef "org" org
|
||||||
|
|> toResource name
|
||||||
|
);
|
||||||
|
|
||||||
|
zitadel_application_oidc = cfg.organization |> select [ "project" "application" ] (org: project: name: value:
|
||||||
|
value
|
||||||
|
|> getAttrs [ "redirectUris" "grantTypes" "responseTypes" ]
|
||||||
|
|> withName name
|
||||||
|
|> withRef "org" org
|
||||||
|
|> withRef "project" project
|
||||||
|
|> toResource name
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
mkIf cfg.enable {
|
||||||
${namespace}.services.persistance.postgresql.enable = true;
|
${namespace}.services.persistance.postgresql.enable = true;
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
zitadel
|
zitadel
|
||||||
];
|
];
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /tmp/zitadelApplyTerraform 0755 zitadel zitadel -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.zitadelApplyTerraform = {
|
||||||
|
description = "Zitadel terraform apply";
|
||||||
|
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
wants = [ "zitadel.service" ];
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copy infra code into workspace
|
||||||
|
cp -f ${terraformConfiguration} config.tf.json
|
||||||
|
|
||||||
|
# Initialize OpenTofu
|
||||||
|
${lib.getExe pkgs.opentofu} init
|
||||||
|
|
||||||
|
# Run the infrastructure code
|
||||||
|
# ${lib.getExe pkgs.opentofu} plan
|
||||||
|
${lib.getExe pkgs.opentofu} apply -auto-approve
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "zitadel";
|
||||||
|
Group = "zitadel";
|
||||||
|
|
||||||
|
WorkingDirectory = "/tmp/zitadelApplyTerraform";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
zitadel = {
|
zitadel = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -41,31 +250,31 @@ in
|
||||||
SecretHasher.Hasher.Algorithm = "argon2id";
|
SecretHasher.Hasher.Algorithm = "argon2id";
|
||||||
};
|
};
|
||||||
|
|
||||||
DefaultInstance = {
|
# DefaultInstance = {
|
||||||
PasswordComplexityPolicy = {
|
# # PasswordComplexityPolicy = {
|
||||||
MinLength = 20;
|
# # MinLength = 0;
|
||||||
HasLowercase = false;
|
# # HasLowercase = false;
|
||||||
HasUppercase = false;
|
# # HasUppercase = false;
|
||||||
HasNumber = false;
|
# # HasNumber = false;
|
||||||
HasSymbol = false;
|
# # HasSymbol = false;
|
||||||
};
|
# # };
|
||||||
LoginPolicy = {
|
# LoginPolicy = {
|
||||||
AllowRegister = false;
|
# AllowRegister = false;
|
||||||
ForceMFA = true;
|
# ForceMFA = true;
|
||||||
};
|
# };
|
||||||
LockoutPolicy = {
|
# LockoutPolicy = {
|
||||||
MaxPasswordAttempts = 5;
|
# MaxPasswordAttempts = 5;
|
||||||
MaxOTPAttempts = 10;
|
# MaxOTPAttempts = 10;
|
||||||
};
|
# };
|
||||||
SMTPConfiguration = {
|
# # SMTPConfiguration = {
|
||||||
SMTP = {
|
# # SMTP = {
|
||||||
Host = "black-mail.nl:587";
|
# # Host = "black-mail.nl:587";
|
||||||
User = "chris@kruining.eu";
|
# # User = "chris@kruining.eu";
|
||||||
Password = "__TODO_USE_SOPS__";
|
# # Password = "__TODO_USE_SOPS__";
|
||||||
};
|
# # };
|
||||||
FromName = "Amarth Zitadel";
|
# # FromName = "Amarth Zitadel";
|
||||||
};
|
# # };
|
||||||
};
|
# };
|
||||||
|
|
||||||
Database.postgres = {
|
Database.postgres = {
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
|
|
@ -84,9 +293,16 @@ in
|
||||||
};
|
};
|
||||||
steps = {
|
steps = {
|
||||||
FirstInstance = {
|
FirstInstance = {
|
||||||
InstanceName = "auth.kruining.eu";
|
# Not sure, this option seems to be mostly irrelevant
|
||||||
|
InstanceName = "eu";
|
||||||
|
|
||||||
|
MachineKeyPath = "/var/lib/zitadel/machine-key.json";
|
||||||
|
# PatPath = "/var/lib/zitadel/machine-key.pat";
|
||||||
|
# LoginClientPatPath = "/var/lib/zitadel/machine-key.json";
|
||||||
|
|
||||||
Org = {
|
Org = {
|
||||||
Name = "Amarth";
|
Name = "kruining";
|
||||||
|
|
||||||
Human = {
|
Human = {
|
||||||
UserName = "chris";
|
UserName = "chris";
|
||||||
FirstName = "Chris";
|
FirstName = "Chris";
|
||||||
|
|
@ -97,6 +313,20 @@ in
|
||||||
};
|
};
|
||||||
Password = "KaasIsAwesome1!";
|
Password = "KaasIsAwesome1!";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Machine = {
|
||||||
|
Machine = {
|
||||||
|
Username = "terraform-service-user";
|
||||||
|
Name = "Terraform";
|
||||||
|
};
|
||||||
|
MachineKey = { ExpirationDate = "2026-01-01T00:00:00Z"; Type = 1; };
|
||||||
|
# Pat = { ExpirationDate = "2026-01-01T00:00:00Z"; };
|
||||||
|
};
|
||||||
|
|
||||||
|
# LoginClient.Machine = {
|
||||||
|
# Username = "terraform-service-user";
|
||||||
|
# Name = "Terraform";
|
||||||
|
# };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,33 @@
|
||||||
sneeuwvlok = {
|
sneeuwvlok = {
|
||||||
services = {
|
services = {
|
||||||
# authentication.authelia.enable = true;
|
# authentication.authelia.enable = true;
|
||||||
authentication.zitadel.enable = true;
|
authentication.zitadel = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
organization = {
|
||||||
|
thisIsMyAwesomeOrg = {};
|
||||||
|
|
||||||
|
nix = {
|
||||||
|
project = {
|
||||||
|
ulmo = {
|
||||||
|
application = {
|
||||||
|
jellyfin = {
|
||||||
|
redirectUris = [ "https://jellyfin.kruining.eu/sso/OID/redirect/zitadel" ];
|
||||||
|
grantTypes = [ "authorizationCode" ];
|
||||||
|
responseTypes = [ "code" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
forgejo = {
|
||||||
|
redirectUris = [ "https://git.amarth.cloud/user/oauth2/zitadel/callback" ];
|
||||||
|
grantTypes = [ "authorizationCode" ];
|
||||||
|
responseTypes = [ "code" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
communication.matrix.enable = true;
|
communication.matrix.enable = true;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue