diff --git a/nix/modules/nixos/hetzner/default.nix b/nix/modules/nixos/hetzner/default.nix index a3c45cb..51beb57 100644 --- a/nix/modules/nixos/hetzner/default.nix +++ b/nix/modules/nixos/hetzner/default.nix @@ -24,10 +24,12 @@ in }; config = lib.mkIf cfg.enable { - disko = lib.khscodes.disko-root-lvm-uefi { - device = "/dev/sda"; - diskName = cfg.diskName; - }; + disko = lib.mkDefault ( + lib.khscodes.disko-root-lvm-uefi { + device = "/dev/sda"; + diskName = cfg.diskName; + } + ); boot.tmp.cleanOnBoot = lib.mkDefault true; boot.initrd.kernelModules = lib.mkIf (system == "aarch64-linux") [ "virtio_gpu" ]; diff --git a/nix/modules/nixos/infrastructure/hetzner-instance/default.nix b/nix/modules/nixos/infrastructure/hetzner-instance/default.nix index eb984c9..a6c9ec8 100644 --- a/nix/modules/nixos/infrastructure/hetzner-instance/default.nix +++ b/nix/modules/nixos/infrastructure/hetzner-instance/default.nix @@ -124,7 +124,6 @@ in description = "The server type to create"; default = null; }; - extraFirewallRules = lib.mkOption { type = lib.types.listOf lib.types.attrs; description = "Extra firewall rules added to the instance"; diff --git a/nix/modules/nixos/infrastructure/kanidm-client-application/default.nix b/nix/modules/nixos/infrastructure/kanidm-client-application/default.nix new file mode 100644 index 0000000..a1c167b --- /dev/null +++ b/nix/modules/nixos/infrastructure/kanidm-client-application/default.nix @@ -0,0 +1,53 @@ +{ config, lib, ... }: +let + cfg = config.khscodes.infrastructure.kanidm-client-application; +in +{ + options.khscodes.infrastructure.kanidm-client-application = { + enable = lib.mkEnableOption "Enables securing client credentials for oauth2 application in kanidm"; + appName = lib.mkOption { + type = lib.types.str; + }; + secretFile = lib.mkOption { + type = lib.types.str; + default = "/run/secret/kanidm/${cfg.appName}"; + }; + secretOwner = lib.mkOption { + type = lib.types.str; + }; + secretGroup = lib.mkOption { + type = lib.types.str; + default = cfg.secretOwner; + }; + reloadOrRestartUnits = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + restartUnits = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + }; + + config = lib.mkIf cfg.enable { + khscodes.services.vault-agent.templates = [ + { + inherit (cfg) reloadOrRestartUnits restartUnits; + contents = '' + {{- with secret "kanidm/data/apps/${cfg.appName}" -}} + {{ .Data.data.basic_secret }} + {{- end -}} + ''; + destination = cfg.secretFile; + owner = cfg.secretOwner; + group = cfg.secretGroup; + perms = "0600"; + } + ]; + khscodes.infrastructure.vault-server-approle.policy = { + "kanidm/data/apps/${cfg.appName}" = { + capabilities = [ "read" ]; + }; + }; + }; +} diff --git a/nix/modules/nixos/infrastructure/mailserver/default.nix b/nix/modules/nixos/infrastructure/mailserver/default.nix index ec83cab..19642ba 100644 --- a/nix/modules/nixos/infrastructure/mailserver/default.nix +++ b/nix/modules/nixos/infrastructure/mailserver/default.nix @@ -60,18 +60,11 @@ in }; khscodes.infrastructure.vault-prometheus-sender.exporters.enabled = [ "postfix" ]; services.fail2ban.jails = { - postfix-sasl = { - settings = { - filter = "postfix[mode=auth]"; - port = "smtp,submission,imap,imaps,pop3,pop3s"; - findtime = 600; - maxretry = 5; - }; - }; postfix = { settings = { enabled = true; mode = "aggressive"; + port = "smtp,submission,imap,imaps,pop3,pop3s"; findtime = 600; bantime = "1d"; maxretry = 3; @@ -86,7 +79,7 @@ in maxretry = 3; }; }; - roundcube-atuh = { + roundcube-auth = { settings = { enabled = true; findtime = 600; diff --git a/nix/packages/nixos-install/default.nix b/nix/packages/nixos-install/default.nix index ac2ac5b..9cf68d0 100644 --- a/nix/packages/nixos-install/default.nix +++ b/nix/packages/nixos-install/default.nix @@ -25,6 +25,7 @@ pkgs.writeShellApplication { echo "No preprovisioning needed" exit 0 fi + echo -n "tempkey" | ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$username@$host" -- "cat >/tmp/tempkey" nixos-anywhere --flake "${inputs.self}#$hostname" --target-host "$username@$host" ''; } diff --git a/nix/systems/aarch64-linux/kas.codes/forgejo/oauth.nix b/nix/systems/aarch64-linux/kas.codes/forgejo/oauth.nix index 9b111bf..64fdea0 100644 --- a/nix/systems/aarch64-linux/kas.codes/forgejo/oauth.nix +++ b/nix/systems/aarch64-linux/kas.codes/forgejo/oauth.nix @@ -5,7 +5,6 @@ ... }: let - oauthSecretIdFile = "/run/forgejo/oauth_secret_id"; setApp = pkgs.writeShellApplication { name = "forgejo-set-oauth-app"; runtimeInputs = [ @@ -14,7 +13,7 @@ let ]; text = '' config="${config.services.forgejo.stateDir}/custom/conf/app.ini" - secret="$(cat ${oauthSecretIdFile})" + secret="$(cat ${config.khscodes.infrastructure.kanidm-client-application.secretFile})" options=( "--name" "Kanidm" \ @@ -43,26 +42,11 @@ let }; in { - khscodes.services.vault-agent.templates = [ - { - contents = '' - {{- with secret "kanidm/data/apps/forgejo" -}} - {{ .Data.data.basic_secret }} - {{- end -}} - ''; - destination = oauthSecretIdFile; - perms = "0600"; - owner = "git"; - group = "git"; - restartUnits = [ - "forgejo-setup-oauth.service" - ]; - } - ]; - khscodes.infrastructure.vault-server-approle.policy = { - "kanidm/data/apps/forgejo" = { - capabilities = [ "read" ]; - }; + khscodes.infrastructure.kanidm-client-application = { + enable = true; + appName = "forgejo"; + secretOwner = "git"; + restartUnits = [ "forgejo-setup-oauth.service" ]; }; systemd.services.forgejo-setup-oauth = { enable = true; @@ -73,7 +57,7 @@ in "vault-agent-openbao.service" ]; unitConfig = { - ConditionPathExists = [ oauthSecretIdFile ]; + ConditionPathExists = [ config.infrastructure.kanidm-client-application.secretFile ]; }; serviceConfig = { Type = "oneshot"; diff --git a/nix/systems/aarch64-linux/mail.kaareskovgaard.net/default.nix b/nix/systems/aarch64-linux/mail.kaareskovgaard.net/default.nix deleted file mode 100644 index eece7df..0000000 --- a/nix/systems/aarch64-linux/mail.kaareskovgaard.net/default.nix +++ /dev/null @@ -1,64 +0,0 @@ -{ - inputs, - config, - ... -}: -{ - imports = [ - "${inputs.self}/nix/profiles/nixos/hetzner-server.nix" - ./testuser.nix - ]; - khscodes.infrastructure.provisioning.pre.modules = [ - { - khscodes.vault = { - enable = true; - mount."mail.kaareskovgaard.net" = { - path = "mail.kaareskovgaard.net"; - type = "kv"; - options = { - version = "2"; - }; - description = "Secrets used for mail.kaareskovgaard.net"; - }; - }; - } - ]; - khscodes.infrastructure = { - hetzner-instance = { - enable = true; - mapRdns = true; - server_type = "cax11"; - }; - mailserver = { - enable = true; - domains = [ - "agerlin-skovgaard.dk" - "agerlinskovgaard.dk" - ]; - dkim = { - vault = { - mount = "mail.kaareskovgaard.net"; - prefixPath = "dkim"; - }; - }; - }; - }; - services.roundcube = { - enable = true; - hostName = "mail.kaareskovgaard.net"; - configureNginx = true; - extraConfig = '' - # starttls needed for authentication, so the fqdn required to match - # the certificate - $config['smtp_host'] = "tls://${config.mailserver.fqdn}"; - $config['smtp_user'] = "%u"; - $config['smtp_pass'] = "%p"; - ''; - }; - khscodes.services.nginx = { - enable = true; - virtualHosts."mail.kaareskovgaard.net" = { }; - }; - khscodes.networking.fqdn = "mail.kaareskovgaard.net"; - system.stateVersion = "25.05"; -} diff --git a/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix new file mode 100644 index 0000000..5904b8b --- /dev/null +++ b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/default.nix @@ -0,0 +1,71 @@ +{ + inputs, + ... +}: +{ + imports = [ + "${inputs.self}/nix/profiles/nixos/hetzner-server.nix" + # ./testuser.nix + ]; + khscodes.infrastructure.provisioning.pre.modules = [ + ( + { config, ... }: + { + khscodes.vault = { + enable = true; + mount."mx.kaareskovgaard.net" = { + path = "mx.kaareskovgaard.net"; + type = "kv"; + options = { + version = "2"; + }; + description = "Secrets used for mx.kaareskovgaard.net"; + }; + }; + resource.hcloud_volume.mail_disk = { + name = "mx.kaareskovgaard.net-mail1"; + size = 20; + server_id = config.khscodes.output.server.compute.id; + }; + } + ) + ]; + khscodes.infrastructure = { + hetzner-instance = { + enable = true; + mapRdns = true; + server_type = "cax11"; + }; + mailserver = { + enable = true; + domains = [ + "agerlin-skovgaard.dk" + "agerlinskovgaard.dk" + ]; + dkim = { + vault = { + mount = "mx.kaareskovgaard.net"; + prefixPath = "dkim"; + }; + }; + }; + }; + # services.roundcube = { + # enable = true; + # hostName = "mail.kaareskovgaard.net"; + # configureNginx = true; + # extraConfig = '' + # # starttls needed for authentication, so the fqdn required to match + # # the certificate + # $config['smtp_host'] = "tls://${config.mailserver.fqdn}"; + # $config['smtp_user'] = "%u"; + # $config['smtp_pass'] = "%p"; + # ''; + # }; + khscodes.services.nginx = { + enable = true; + # virtualHosts."mail.kaareskovgaard.net" = { }; + }; + khscodes.networking.fqdn = "mx.kaareskovgaard.net"; + system.stateVersion = "25.05"; +} diff --git a/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix new file mode 100644 index 0000000..3c8f7db --- /dev/null +++ b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix @@ -0,0 +1,168 @@ +{ pkgs, lib, ... }: +let + diskName = "nixos"; + espSize = "500M"; + bootPartName = "ESP"; + rootPartName = "primary"; + volumeGroupName = "mainpool"; + rootLvName = "root"; + zrootKey = "/run/secret/zroot.key"; + zfsLoadKeyScript = pkgs.writeShellApplication { + name = "load-zfs-key"; + runtimeInputs = [ pkgs.zfs ]; + text = '' + if ! zfs load-key -L ${zrootKey} zroot; then + echo -n "tempkey" /tmp/tempkey + zfs load-key -L /temp/tempkey zroot + zfs change-key -o keylocation=${zrootKey} zroot + fi + ''; + }; +in +{ + khscodes.services.vault-agent.templates = [ + { + contents = '' + {{- with pkiCert "mx.kaareskovgaard.net/data/zroot_encryption" -}} + {{ .Data.data.key }} + {{- end -}} + ''; + destination = zrootKey; + owner = "root"; + group = "root"; + perms = "0600"; + exec = lib.getExe zfsLoadKeyScript; + restartUnits = [ + "postfix.service" + "dovecot.service" + "rspamd.service" + ]; + } + ]; + khscodes.infrastructure.provisioning.pre.modules = [ + { + resource.random_password.zroot_encryption_key = { + length = 48; + numeric = true; + lower = true; + upper = true; + special = false; + }; + resource.vault_kv_secret_v2.test = { + mount = "mx.kaareskovgaard.net"; + name = "zroot_encryption"; + data_json = '' + { + "key": ''${ jsonencode(resource.random_password.zroot_encryption_key.result) } + } + ''; + }; + } + ]; + khscodes.infrastructure.vault-server-approle.policy = { + "mx.kaareskovgaard.net/data/zroot_encryption" = { + capabilities = [ "read" ]; + }; + }; + disko.devices.disk = { + "${diskName}" = { + device = "/dev/sda"; + type = "disk"; + content = { + type = "gpt"; + partitions = { + "${bootPartName}" = { + size = espSize; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + "${rootPartName}" = { + size = "100%"; + content = { + type = "lvm_pv"; + vg = volumeGroupName; + }; + }; + }; + }; + }; + zroot1 = { + device = "/dev/sdb"; + type = "disk"; + content = { + type = "gpt"; + partitions = { + zfs = { + size = "100%"; + content = { + type = "zfs"; + pool = "zroot"; + }; + }; + }; + }; + }; + }; + devices.lvm_vg = { + "${volumeGroupName}" = { + type = "lvm_vg"; + lvs = { + "${rootLvName}" = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + mountOptions = [ "defaults" ]; + }; + }; + }; + }; + }; + zpool = { + zroot = { + type = "zpool"; + rootFsOptions = { + mountpoint = "none"; + compression = "zstd"; + acltype = "posixacl"; + xattr = "sa"; + "com.sun:auto-snapshot" = "true"; + }; + options.ashift = "12"; + datasets = { + "mailserver" = { + type = "zfs_fs"; + options = { + encryption = "aes-256-gcm"; + keyformat = "passphrase"; + keylocation = "file:///tmp/tempkey"; + }; + }; + "mailserver/vmail" = { + type = "zfs_fs"; + mountpoint = "/var/mailserver/vmail"; + }; + "mailserver/indices" = { + type = "zfs_fs"; + mountpoint = "/var/mailserver/indices"; + }; + }; + mode = { + topology = { + type = "topology"; + vdev = [ + { + members = [ "zroot1" ]; + } + ]; + }; + }; + }; + }; +} diff --git a/nix/systems/aarch64-linux/mail.kaareskovgaard.net/testuser.nix b/nix/systems/aarch64-linux/mx.kaareskovgaard.net/testuser.nix similarity index 100% rename from nix/systems/aarch64-linux/mail.kaareskovgaard.net/testuser.nix rename to nix/systems/aarch64-linux/mx.kaareskovgaard.net/testuser.nix diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix index e70ec15..8e45c37 100644 --- a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix @@ -1,22 +1,11 @@ { config, pkgs, - lib, ... }: let domain = "login.kaareskovgaard.net"; bootstrapping = config.khscodes."security.kaareskovgaard.net".bootstrap.enable; - openbaoAppBasicSecretFile = "/run/kanidm/openbao_basic_secret"; - openbaoCliAppBasicSecretFile = "/run/kanidm/openbao_cli_basic_secret"; - monitoringAppBasicSecretFile = "/run/kanidm/monitoring_basic_secret"; - forgejoAppBasicSecretFile = "/run/kanidm/forgejo_basic_secret"; - secretFiles = [ - openbaoAppBasicSecretFile - openbaoCliAppBasicSecretFile - monitoringAppBasicSecretFile - forgejoAppBasicSecretFile - ]; openbaoDomain = config.khscodes.infrastructure.openbao.domain; openbaoAllowedRedirectUrls = [ "https://${openbaoDomain}/ui/vault/auth/kanidm/oidc/callback" @@ -51,424 +40,285 @@ let }; in { - services.kanidm = { - enableServer = true; - # Verify upgrade will be successful by running `kanidmd domain upgrade-check` before bumping package version. - package = pkgs.kanidmWithSecretProvisioning_1_6; - serverSettings = { - inherit domain; - tls_chain = "${config.security.acme.certs.${domain}.directory}/fullchain.pem"; - tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem"; - origin = "https://${domain}"; - trust_x_forward_for = true; - }; - provision = { - enable = true; - idmAdminPasswordFile = null; - adminPasswordFile = null; - acceptInvalidCerts = true; - persons.khs = { - present = true; - mailAddresses = [ - "kaare@kaareskovgaard.net" - "kaare@agerlin-skovgaard.dk" - "kaare@agerlinskovgaard.dk" - ]; - legalName = "Kaare Skovgaard"; - displayName = "khs"; - }; - groups.openbao_admin = { - present = true; - members = [ "khs" ]; - }; - groups.forgejo_user = { - present = true; - members = [ "khs" ]; - }; - groups.forgejo_comitter = { - present = true; - members = [ "khs" ]; - }; - groups.forgejo_admin = { - present = true; - members = [ "khs" ]; - }; - # We cannot add oauth2 apps before the secrets for them are generated. - systems.oauth2 = lib.mkIf (!bootstrapping) { - openbao = { - present = true; - public = false; - preferShortUsername = true; - basicSecretFile = lib.mkIf (!bootstrapping) openbaoAppBasicSecretFile; - originUrl = openbaoAllowedRedirectUrls; - originLanding = "https://${openbaoDomain}"; - displayName = "OpenBAO"; - scopeMaps = { - "openbao_admin" = [ - "profile" - "email" - "openid" - ]; - }; - claimMaps.groups = { - joinType = "array"; - valuesByGroup = { - "openbao_admin" = [ - "openbao_writer" - "openbao_cli_writer" - ]; - }; - }; + imports = [ ./kanidm_application.nix ]; + config = { + khscodes.security.kanidm.applications = { + openbao = { + allowedRedirectUris = openbaoAllowedRedirectUrls; + landingUri = "https://${openbaoDomain}"; + displayName = "OpenBAO"; + scopeMaps = { + "openbao_admin" = [ + "profile" + "email" + "openid" + ]; }; - openbao-cli = { - present = true; - public = false; - preferShortUsername = true; - basicSecretFile = lib.mkIf (!bootstrapping) openbaoCliAppBasicSecretFile; - originUrl = [ "http://localhost:8250/oidc/callback" ]; - originLanding = "http://localhost:8250"; - displayName = "OpenBAO CLI"; - scopeMaps = { + claimMaps.groups = { + joinType = "array"; + valuesByGroup = { "openbao_admin" = [ - "profile" - "email" - "openid" - ]; - }; - claimMaps.groups = { - joinType = "array"; - valuesByGroup = { - "openbao_admin" = [ - "openbao_writer" - ]; - }; - }; - }; - monitoring = { - present = true; - public = false; - preferShortUsername = true; - basicSecretFile = lib.mkIf (!bootstrapping) monitoringAppBasicSecretFile; - originUrl = [ "https://monitoring.kaareskovgaard.net/login/generic_oauth" ]; - originLanding = "http://monitoring.kaareskovgaard.net"; - displayName = "Monitoring"; - scopeMaps = { - "openbao_admin" = [ - "profile" - "email" - "openid" + "openbao_writer" + "openbao_cli_writer" ]; }; }; - forgejo = { - present = true; - public = false; - preferShortUsername = true; - basicSecretFile = lib.mkIf (!bootstrapping) forgejoAppBasicSecretFile; - originUrl = [ "https://kas.codes/user/oauth2/Kanidm/callback" ]; - originLanding = "http://kas.codes"; - displayName = "KAS: Codes"; - scopeMaps = { - "forgejo_user" = [ - "profile" - "email" - "openid" + }; + openbao-cli = { + terranixName = "openbao_cli"; + allowedRedirectUris = [ "http://localhost:8250/oidc/callback" ]; + landingUri = "http://localhost:8250"; + displayName = "OpenBAO CLI"; + scopeMaps = { + "openbao_admin" = [ + "profile" + "email" + "openid" + ]; + }; + claimMaps.groups = { + joinType = "array"; + valuesByGroup = { + "openbao_admin" = [ + "openbao_writer" ]; }; - claimMaps.groups = { - joinType = "array"; - valuesByGroup = { - "forgejo_comitter" = [ - "comitter" - ]; - "forgejo_admin" = [ - "admin" - ]; - }; + }; + }; + monitoring = { + allowedRedirectUris = [ "https://monitoring.kaareskovgaard.net/login/generic_oauth" ]; + landingUri = "http://monitoring.kaareskovgaard.net"; + displayName = "Monitoring"; + scopeMaps = { + "openbao_admin" = [ + "profile" + "email" + "openid" + ]; + }; + }; + forgejo = { + allowedRedirectUris = [ "https://kas.codes/user/oauth2/Kanidm/callback" ]; + landingUri = "http://kas.codes"; + displayName = "KAS: Codes"; + scopeMaps = { + "forgejo_user" = [ + "profile" + "email" + "openid" + ]; + }; + claimMaps.groups = { + joinType = "array"; + valuesByGroup = { + "forgejo_comitter" = [ + "comitter" + ]; + "forgejo_admin" = [ + "admin" + ]; }; }; }; }; - }; - # Don't add dependencies from bootstrapping when not bootstrapping. - systemd.services.kanidm = lib.mkIf (!bootstrapping) { - unitConfig = { - ConditionPathExists = secretFiles; + services.kanidm = { + enableServer = true; + # Verify upgrade will be successful by running `kanidmd domain upgrade-check` before bumping package version. + package = pkgs.kanidmWithSecretProvisioning_1_6; + serverSettings = { + inherit domain; + tls_chain = "${config.security.acme.certs.${domain}.directory}/fullchain.pem"; + tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem"; + origin = "https://${domain}"; + trust_x_forward_for = true; + }; + provision = { + enable = true; + idmAdminPasswordFile = null; + adminPasswordFile = null; + acceptInvalidCerts = true; + persons.khs = { + present = true; + mailAddresses = [ + "kaare@kaareskovgaard.net" + "kaare@agerlin-skovgaard.dk" + "kaare@agerlinskovgaard.dk" + ]; + legalName = "Kaare Skovgaard"; + displayName = "khs"; + }; + groups.openbao_admin = { + present = true; + members = [ "khs" ]; + }; + groups.forgejo_user = { + present = true; + members = [ "khs" ]; + }; + groups.forgejo_comitter = { + present = true; + members = [ "khs" ]; + }; + groups.forgejo_admin = { + present = true; + members = [ "khs" ]; + }; + }; }; - }; - # Allow the server to read the secrets for its own apps - khscodes.infrastructure.vault-server-approle.policy."kanidm/data/apps/*" = { - capabilities = [ "read" ]; - }; - - khscodes.infrastructure.provisioning.post.modules = [ - # Creates mount for kanidm apps and generates random passwords for the apps - ( - { config, ... }: - { - terraform.required_providers.random = { - source = "hashicorp/random"; - version = "3.7.2"; - }; - provider.random = { }; - # NOTE: This gets stored in the state file - resource.random_password.openbao_secret = { - length = 48; - numeric = true; - lower = true; - upper = true; - special = false; - }; - khscodes.vault = { - enable = true; - mount.kanidm = { - path = "kanidm"; - type = "kv"; - options = { - version = "2"; - }; - description = "Secrets used for kanidm"; - }; - }; - resource.vault_kv_secret_v2.openbao_secret = { - mount = config.khscodes.vault.output.mount.kanidm.path; - name = "apps/openbao"; - data_json = '' - { "basic_secret": "''${ resource.random_password.openbao_secret.result }" } - ''; - }; - resource.random_password.openbao_cli_secret = { - length = 48; - numeric = true; - lower = true; - upper = true; - special = false; - }; - resource.vault_kv_secret_v2.openbao_cli_secret = { - mount = config.khscodes.vault.output.mount.kanidm.path; - name = "apps/openbao_cli"; - data_json = '' - { "basic_secret": "''${ resource.random_password.openbao_cli_secret.result }" } - ''; - }; - resource.random_password.monitoring = { - length = 48; - numeric = true; - lower = true; - upper = true; - special = false; - }; - resource.vault_kv_secret_v2.monitoring_secret = { - mount = config.khscodes.vault.output.mount.kanidm.path; - name = "apps/monitoring"; - data_json = '' - { "basic_secret": "''${ resource.random_password.monitoring.result }" } - ''; - }; - resource.random_password.forgejo = { - length = 48; - numeric = true; - lower = true; - upper = true; - special = false; - }; - resource.vault_kv_secret_v2.forgejo_secret = { - mount = config.khscodes.vault.output.mount.kanidm.path; - name = "apps/forgejo"; - data_json = '' - { "basic_secret": "''${ resource.random_password.forgejo.result }" } - ''; - }; - } - ) - # Sets up OIDC authentication within OpenBAO. - # OpenBAO queries the openid url for its configuration when adding it, so it is not possible, - # to add this before - ( - if bootstrapping then - { } - else + khscodes.infrastructure.provisioning.post.modules = [ + ( + { ... }: { - resource.vault_jwt_auth_backend.kanidm_cli = { - description = "Kanidm cli auth backend"; - path = "oidc"; - type = "oidc"; - oidc_discovery_url = "https://${domain}/oauth2/openid/openbao-cli"; - oidc_client_id = "openbao-cli"; - oidc_client_secret = "\${ resource.random_password.openbao_cli_secret.result }"; - default_role = "kanidm_cli_writer"; - jwt_supported_algs = [ - "ES256" - ]; - tune = [ - { - listing_visibility = "hidden"; - default_lease_ttl = "2h"; - max_lease_ttl = "2h"; - token_type = "default-service"; - passthrough_request_headers = [ ]; - allowed_response_headers = [ ]; - audit_non_hmac_request_keys = [ ]; - audit_non_hmac_response_keys = [ ]; - } - ]; - }; - resource.vault_jwt_auth_backend.kanidm = { - description = "Kanidm auth backend"; - path = "kanidm"; - type = "oidc"; - oidc_discovery_url = "https://${domain}/oauth2/openid/openbao"; - oidc_client_id = "openbao"; - oidc_client_secret = "\${ resource.random_password.openbao_secret.result }"; - default_role = "kanidm_writer"; - jwt_supported_algs = [ - "ES256" - ]; - tune = [ - { - listing_visibility = "unauth"; - default_lease_ttl = "2h"; - max_lease_ttl = "2h"; - token_type = "default-service"; - passthrough_request_headers = [ ]; - allowed_response_headers = [ ]; - audit_non_hmac_request_keys = [ ]; - audit_non_hmac_response_keys = [ ]; - } - ]; - }; - - resource.vault_jwt_auth_backend_role.kanidm_cli_writer = { - backend = "\${ resource.vault_jwt_auth_backend.kanidm_cli.path }"; - role_name = "kanidm_cli_writer"; - bound_audiences = [ "openbao-cli" ]; - allowed_redirect_uris = [ "http://localhost:8250/oidc/callback" ]; - user_claim = "sub"; - token_policies = [ "writer" ]; - groups_claim = "groups"; - oidc_scopes = [ - "openid" - "profile" - "email" - ]; - }; - - resource.vault_jwt_auth_backend_role.kanidm_writer = { - backend = "\${ resource.vault_jwt_auth_backend.kanidm.path }"; - role_name = "kanidm_writer"; - bound_audiences = [ "openbao" ]; - allowed_redirect_uris = openbaoAllowedRedirectUrls; - user_claim = "sub"; - token_policies = [ "writer" ]; - groups_claim = "groups"; - oidc_scopes = [ - "openid" - "profile" - "email" - ]; - }; - - resource.vault_identity_group.kanidm_writer = { - name = "kanidm_writer"; - policies = [ "writer" ]; - type = "external"; - }; - - resource.vault_identity_group.kanidm_cli_writer = { - name = "kanidm_cli_writer"; - policies = [ "writer" ]; - type = "external"; - }; - - resource.vault_identity_group_alias.oidc_writer = { - name = "openbao_cli_writer"; - mount_accessor = "\${ vault_jwt_auth_backend.kanidm_cli.accessor }"; - canonical_id = "\${ vault_identity_group.kanidm_cli_writer.id }"; - }; - - resource.vault_identity_group_alias.kanidm_writer = { - name = "openbao_writer"; - mount_accessor = "\${ vault_jwt_auth_backend.kanidm.accessor }"; - canonical_id = "\${ vault_identity_group.kanidm_writer.id }"; - }; - - resource.vault_policy.writer = { - name = "writer"; - - policy = '' - path "*" { - capabilities = ["create", "update", "patch", "read", "delete", "list"] - } - ''; + khscodes.vault = { + enable = true; + mount.kanidm = { + path = "kanidm"; + type = "kv"; + options = { + version = "2"; + }; + description = "Secrets used for kanidm"; + }; }; } - ) - ]; + ) + # Sets up OIDC authentication within OpenBAO. + # OpenBAO queries the openid url for its configuration when adding it, so it is not possible, + # to add this before + ( + if bootstrapping then + { } + else + { + resource.vault_jwt_auth_backend.kanidm_cli = { + description = "Kanidm cli auth backend"; + path = "oidc"; + type = "oidc"; + oidc_discovery_url = "https://${domain}/oauth2/openid/openbao-cli"; + oidc_client_id = "openbao-cli"; + oidc_client_secret = "\${ resource.random_password.openbao_cli_secret.result }"; + default_role = "kanidm_cli_writer"; + jwt_supported_algs = [ + "ES256" + ]; + tune = [ + { + listing_visibility = "hidden"; + default_lease_ttl = "2h"; + max_lease_ttl = "2h"; + token_type = "default-service"; + passthrough_request_headers = [ ]; + allowed_response_headers = [ ]; + audit_non_hmac_request_keys = [ ]; + audit_non_hmac_response_keys = [ ]; + } + ]; + }; + resource.vault_jwt_auth_backend.kanidm = { + description = "Kanidm auth backend"; + path = "kanidm"; + type = "oidc"; + oidc_discovery_url = "https://${domain}/oauth2/openid/openbao"; + oidc_client_id = "openbao"; + oidc_client_secret = "\${ resource.random_password.openbao_secret.result }"; + default_role = "kanidm_writer"; + jwt_supported_algs = [ + "ES256" + ]; + tune = [ + { + listing_visibility = "unauth"; + default_lease_ttl = "2h"; + max_lease_ttl = "2h"; + token_type = "default-service"; + passthrough_request_headers = [ ]; + allowed_response_headers = [ ]; + audit_non_hmac_request_keys = [ ]; + audit_non_hmac_response_keys = [ ]; + } + ]; + }; - khscodes.services.vault-agent.templates = [ - { - contents = '' - {{- with secret "kanidm/data/apps/openbao" -}} - {{ .Data.data.basic_secret }} - {{- end -}} - ''; - destination = openbaoAppBasicSecretFile; - perms = "0600"; - owner = "kanidm"; - group = "kanidm"; - reloadOrRestartUnits = [ "kanidm.service" ]; - } - { - contents = '' - {{- with secret "kanidm/data/apps/openbao_cli" -}} - {{ .Data.data.basic_secret }} - {{- end -}} - ''; - destination = openbaoCliAppBasicSecretFile; - perms = "0600"; - owner = "kanidm"; - group = "kanidm"; - reloadOrRestartUnits = [ "kanidm.service" ]; - } - { - contents = '' - {{- with secret "kanidm/data/apps/monitoring" -}} - {{ .Data.data.basic_secret }} - {{- end -}} - ''; - destination = monitoringAppBasicSecretFile; - perms = "0600"; - owner = "kanidm"; - group = "kanidm"; - reloadOrRestartUnits = [ "kanidm.service" ]; - } - { - contents = '' - {{- with secret "kanidm/data/apps/forgejo" -}} - {{ .Data.data.basic_secret }} - {{- end -}} - ''; - destination = forgejoAppBasicSecretFile; - perms = "0600"; - owner = "kanidm"; - group = "kanidm"; - reloadOrRestartUnits = [ "kanidm.service" ]; - } - ]; + resource.vault_jwt_auth_backend_role.kanidm_cli_writer = { + backend = "\${ resource.vault_jwt_auth_backend.kanidm_cli.path }"; + role_name = "kanidm_cli_writer"; + bound_audiences = [ "openbao-cli" ]; + allowed_redirect_uris = [ "http://localhost:8250/oidc/callback" ]; + user_claim = "sub"; + token_policies = [ "writer" ]; + groups_claim = "groups"; + oidc_scopes = [ + "openid" + "profile" + "email" + ]; + }; - security.acme.certs.${domain}.reloadServices = [ "kanidm.service" ]; - # Allow kanidm to read the certificate file - users.groups.nginx.members = [ "kanidm" ]; + resource.vault_jwt_auth_backend_role.kanidm_writer = { + backend = "\${ resource.vault_jwt_auth_backend.kanidm.path }"; + role_name = "kanidm_writer"; + bound_audiences = [ "openbao" ]; + allowed_redirect_uris = openbaoAllowedRedirectUrls; + user_claim = "sub"; + token_policies = [ "writer" ]; + groups_claim = "groups"; + oidc_scopes = [ + "openid" + "profile" + "email" + ]; + }; - khscodes.services.nginx.virtualHosts.${domain} = { - locations."/" = { - proxyPass = "https://${config.services.kanidm.serverSettings.bindaddress}/"; - recommendedProxySettings = true; + resource.vault_identity_group.kanidm_writer = { + name = "kanidm_writer"; + policies = [ "writer" ]; + type = "external"; + }; + + resource.vault_identity_group.kanidm_cli_writer = { + name = "kanidm_cli_writer"; + policies = [ "writer" ]; + type = "external"; + }; + + resource.vault_identity_group_alias.oidc_writer = { + name = "openbao_cli_writer"; + mount_accessor = "\${ vault_jwt_auth_backend.kanidm_cli.accessor }"; + canonical_id = "\${ vault_identity_group.kanidm_cli_writer.id }"; + }; + + resource.vault_identity_group_alias.kanidm_writer = { + name = "openbao_writer"; + mount_accessor = "\${ vault_jwt_auth_backend.kanidm.accessor }"; + canonical_id = "\${ vault_identity_group.kanidm_writer.id }"; + }; + + resource.vault_policy.writer = { + name = "writer"; + + policy = '' + path "*" { + capabilities = ["create", "update", "patch", "read", "delete", "list"] + } + ''; + }; + } + ) + ]; + security.acme.certs.${domain}.reloadServices = [ "kanidm.service" ]; + # Allow kanidm to read the certificate file + users.groups.nginx.members = [ "kanidm" ]; + + khscodes.services.nginx.virtualHosts.${domain} = { + locations."/" = { + proxyPass = "https://${config.services.kanidm.serverSettings.bindaddress}/"; + recommendedProxySettings = true; + }; }; - }; - environment.systemPackages = [ - kanidm-reset-password - ]; + environment.systemPackages = [ + kanidm-reset-password + ]; + }; } diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_application.nix b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_application.nix new file mode 100644 index 0000000..e6817e7 --- /dev/null +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm_application.nix @@ -0,0 +1,115 @@ +{ + config, + lib, + ... +}: +let + cfg = config.khscodes.security.kanidm; + secretFileForApplication = key: "/run/kanidm/${key}_secret"; + secretFiles = lib.attrsets.mapAttrsToList ( + key: value: secretFileForApplication key + ) cfg.applications; + vaultAgentTemplates = lib.attrsets.mapAttrsToList (key: value: { + contents = '' + {{- with secret "kanidm/data/apps/${key}" -}} + {{ .Data.data.basic_secret }} + {{- end -}} + ''; + destination = secretFileForApplication key; + perms = "0600"; + owner = "kanidm"; + group = "kanidm"; + reloadOrRestartUnits = [ "kanidm.service" ]; + }) cfg.applications; + terranixModules = lib.attrsets.mapAttrsToList ( + key: value: + { config, ... }: + let + sanitizedKey = lib.khscodes.sanitize-terraform-name ( + if value.terranixName == null then key else value.terranixName + ); + in + { + resource.random_password."${sanitizedKey}_secret" = { + length = 48; + numeric = true; + lower = true; + upper = true; + special = false; + }; + resource.vault_kv_secret_v2."${sanitizedKey}_secret" = { + mount = config.khscodes.vault.output.mount.kanidm.path; + name = "apps/${key}"; + data_json = '' + { "basic_secret": "''${ resource.random_password.${sanitizedKey}_secret.result }" } + ''; + }; + } + ) cfg.applications; + systemsOauth2 = lib.attrsets.mapAttrs (key: value: { + inherit (value) scopeMaps claimMaps; + present = true; + public = false; + preferShortUsername = true; + basicSecretFile = lib.mkIf (!bootstrapping) (secretFileForApplication key); + originUrl = value.allowedRedirectUris; + originLanding = value.landingUri; + displayName = value.displayName; + }) cfg.applications; + kanidmApplication = lib.khscodes.mkSubmodule { + description = "Kanidm application"; + options = { + terranixName = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + displayName = lib.mkOption { + type = lib.types.str; + }; + scopeMaps = lib.mkOption { + type = lib.types.attrsOf (lib.types.listOf lib.types.str); + }; + allowedRedirectUris = lib.mkOption { + type = lib.types.listOf lib.types.str; + }; + landingUri = lib.mkOption { + type = lib.types.str; + }; + claimMaps = lib.mkOption { + type = lib.types.anything; + default = { }; + }; + }; + }; + bootstrapping = config.khscodes."security.kaareskovgaard.net".bootstrap.enable; +in +{ + options.khscodes.security.kanidm.applications = lib.mkOption { + type = lib.types.attrsOf kanidmApplication; + }; + + config = { + # Allow the server to read the secrets for its own apps + khscodes.infrastructure.vault-server-approle.policy."kanidm/data/apps/*" = { + capabilities = [ "read" ]; + }; + # Don't add dependencies from bootstrapping when not bootstrapping. + systemd.services.kanidm = lib.mkIf (!bootstrapping) { + unitConfig = { + ConditionPathExists = secretFiles; + }; + }; + khscodes.services.vault-agent.templates = vaultAgentTemplates; + khscodes.infrastructure.provisioning.post.modules = terranixModules ++ [ + { + terraform.required_providers.random = { + source = "hashicorp/random"; + version = "3.7.2"; + }; + provider.random = { }; + } + ]; + # We cannot add oauth2 apps before the secrets for them are generated. + services.kanidm.provision.systems.oauth2 = lib.mkIf (!bootstrapping) systemsOauth2; + }; +} diff --git a/nix/systems/aarch64-linux/security.kaareskovgaard.net/openbao.nix b/nix/systems/aarch64-linux/security.kaareskovgaard.net/openbao.nix index ca2e99d..5a14003 100644 --- a/nix/systems/aarch64-linux/security.kaareskovgaard.net/openbao.nix +++ b/nix/systems/aarch64-linux/security.kaareskovgaard.net/openbao.nix @@ -42,6 +42,9 @@ in }; khscodes.services.nginx.virtualHosts.${domain} = { + rateLimit = { + burst = 100; + }; locations."/" = { proxyPass = "https://${config.services.openbao.settings.listener.tcp.address}/"; recommendedProxySettings = true; diff --git a/nix/systems/x86_64-linux/monitoring.kaareskovgaard.net/default.nix b/nix/systems/x86_64-linux/monitoring.kaareskovgaard.net/default.nix index 91cda8c..6e21b2c 100644 --- a/nix/systems/x86_64-linux/monitoring.kaareskovgaard.net/default.nix +++ b/nix/systems/x86_64-linux/monitoring.kaareskovgaard.net/default.nix @@ -52,7 +52,7 @@ in token_url = "https://login.kaareskovgaard.net/oauth2/token"; api_url = "https://login.kaareskovgaard.net/oauth2/openid/monitoring/userinfo"; client_id = "monitoring"; - client_secret = "$__file{/var/lib/vault-agent/grafana/kanidm_client_secret}"; + client_secret = "$__file{${config.khscodes.infrastructure.kanidm-client-application.secretFile}}"; scopes = "openid profile email"; use_pkce = true; skip_org_role_sync = false; @@ -252,23 +252,12 @@ in perms = "0644"; reloadOrRestartUnits = [ "nginx.service" ]; } - { - contents = '' - {{- with secret "kanidm/data/apps/monitoring" -}} - {{ .Data.data.basic_secret }} - {{- end -}} - ''; - destination = "/var/lib/vault-agent/grafana/kanidm_client_secret"; - owner = "grafana"; - group = "grafana"; - perms = "0600"; - reloadOrRestartUnits = [ "grafana.service" ]; - } ]; - infrastructure.vault-server-approle.policy = { - "kanidm/data/apps/monitoring" = { - capabilities = [ "read" ]; - }; + infrastructure.kanidm-client-application = { + enable = true; + appName = "monitoring"; + secretOwner = "grafana"; + reloadOrRestartUnits = [ "grafana.service" ]; }; }; khscodes.networking.fqdn = "monitoring.kaareskovgaard.net";