diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 4f87af4..c222f25 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -29,3 +29,12 @@ jobs: nix build --no-link '.#packages.x86_64-linux.terraform-provider-hcloud' nix build --no-link '.#packages.x86_64-linux.terraform-provider-openstack' nix build --no-link '.#packages.x86_64-linux.terraform-provider-unifi' + nix build --no-link '.#packages.x86_64-linux.terraform-provider-vault' + systems: + runs-on: cache.kaareskovgaard.net + steps: + - uses: actions/checkout@v4 + - run: | + nix build --no-link '.#nixosConfigurations."desktop.kaareskovgaard.net".config.system.build.toplevel' + nix build --no-link '.#nixosConfigurations."desktop.kaareskovgaard.net".config.system.build.vm' + nix build --no-link '.#nixosConfigurations."test.kaareskovgaard.net".config.system.build.toplevel' diff --git a/README.md b/README.md index bb691af..54f72ca 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ To delete the resources again run: nix run '.#destroy-instance' -- ``` +NOTE: It is normal for the secret id associated with vault/openbao roles to not be deletable. Simply run the destroy-instance command a 2nd time and everything should work just fine. + ## Secrets To transfer the secrets needed for OpenTofu from Bitwarden to OpenBAO run: diff --git a/desktop.qcow2.REMOVED.git-id b/desktop.qcow2.REMOVED.git-id index 5910d02..98782aa 100644 --- a/desktop.qcow2.REMOVED.git-id +++ b/desktop.qcow2.REMOVED.git-id @@ -1 +1 @@ -fdf6ba679a51aab2a95570b227e011fdfdf436ce \ No newline at end of file +c232e59d1bbe8b0774b7714233fbff517d1790ed \ No newline at end of file diff --git a/flake.lock b/flake.lock index b278631..0ae0cda 100644 --- a/flake.lock +++ b/flake.lock @@ -546,11 +546,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751741127, - "narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=", + "lastModified": 1751943650, + "narHash": "sha256-7orTnNqkGGru8Je6Un6mq1T8YVVU/O5kyW4+f9C1mZQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "29e290002bfff26af1db6f64d070698019460302", + "rev": "88983d4b665fb491861005137ce2b11a9f89f203", "type": "github" }, "original": { @@ -624,11 +624,11 @@ ] }, "locked": { - "lastModified": 1751942411, - "narHash": "sha256-01uMHCt2U9tP4f24DGch145tT8YQppLY5TC9mWK7O0A=", + "lastModified": 1752028888, + "narHash": "sha256-LRj3/PUpII6taWOrX1w/OeI6f1ncND02PP/kEHvPCqU=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "c587235f892930a61c9e415f0d9792a1b27a41a2", + "rev": "a0f1c656e053463b47639234b151a05e4441bb19", "type": "github" }, "original": { @@ -681,11 +681,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1752009340, - "narHash": "sha256-6IKc+fdgJ+mWW8pBOVS5MYvttHBhvWSbff/31pG3SAY=", + "lastModified": 1752084754, + "narHash": "sha256-JorlZGCWxlYV01lXmUuDeKOZoLPdoN3fAKJv8YIuavs=", "owner": "nix-community", "repo": "stylix", - "rev": "c647aaa1dead3752fb49f226a4f67ae1030d7747", + "rev": "2df042576646d012d15637f43d6075995e785ce3", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 72b1f8c..06b5426 100644 --- a/flake.nix +++ b/flake.nix @@ -97,7 +97,7 @@ inherit inputs; khscodesLib = inputs.self.lib; }; - terranixModules.openbao = import ./nix/modules/terranix/openbao { + terranixModules.vault = import ./nix/modules/terranix/vault { inherit inputs; khscodesLib = inputs.self.lib; }; diff --git a/nix/lib/disko-root-bios/default.nix b/nix/lib/disko-root-bios/default.nix new file mode 100644 index 0000000..a8423bb --- /dev/null +++ b/nix/lib/disko-root-bios/default.nix @@ -0,0 +1,35 @@ +{ ... }: +{ + disko-root-bios = + { + diskName, + device, + bootPartName ? "boot", + rootPartName ? "root", + }: + { + devices.disk = { + "${diskName}" = { + inherit device; + type = "disk"; + content = { + type = "gpt"; + partitions = { + ${bootPartName} = { + size = "1M"; + type = "EF02"; # for grub MBR + }; + ${rootPartName} = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nix/modules/nixos/infrastructure/hetzner-instance/default.nix b/nix/modules/nixos/infrastructure/hetzner-instance/default.nix index d65e044..e42af94 100644 --- a/nix/modules/nixos/infrastructure/hetzner-instance/default.nix +++ b/nix/modules/nixos/infrastructure/hetzner-instance/default.nix @@ -7,6 +7,7 @@ let cfg = config.khscodes.infrastructure.hetzner-instance; fqdn = config.khscodes.networking.fqdn; + provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData; firewallTcpRules = lib.lists.map (p: { direction = "in"; protocol = "tcp"; @@ -160,6 +161,7 @@ in initial_image = "debian-12"; rdns = fqdn; ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ]; + user_data = provisioningUserData; }; khscodes.cloudflare = { enable = true; diff --git a/nix/modules/nixos/infrastructure/khs-openstack-instance/default.nix b/nix/modules/nixos/infrastructure/khs-openstack-instance/default.nix index 4478c81..c4f283d 100644 --- a/nix/modules/nixos/infrastructure/khs-openstack-instance/default.nix +++ b/nix/modules/nixos/infrastructure/khs-openstack-instance/default.nix @@ -7,6 +7,7 @@ let cfg = config.khscodes.infrastructure.khs-openstack-instance; fqdn = config.khscodes.networking.fqdn; + provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData; firewallTcpRules = lib.lists.flatten ( lib.lists.map (p: [ { @@ -83,14 +84,6 @@ in default = "${fqdn}.tfstate"; }; }; - secretsSource = lib.mkOption { - type = lib.types.enum [ - "bitwarden" - "vault" - ]; - description = "Whether to load opentofu secrets from Bitwarden or Vault"; - default = "vault"; - }; flavor = lib.mkOption { type = lib.types.nullOr lib.types.str; description = "The server type to create"; @@ -188,6 +181,7 @@ in flavor = cfg.flavor; ssh_public_key = cfg.ssh_key; firewall_rules = firewallRules; + user_data = provisioningUserData; }; khscodes.unifi.enable = true; khscodes.unifi.static_route.compute = { @@ -236,11 +230,15 @@ in message = "Must set config.khscodes.networking.fqdn when using opentofu"; } ]; - + khscodes.services.openssh = { + enable = true; + hostCertificate = { + enable = true; + }; + }; khscodes.infrastructure.provisioning = { pre = { modules = modules; - secretsSource = cfg.secretsSource; endpoints = [ "aws" "cloudflare" diff --git a/nix/modules/nixos/infrastructure/provisioning/default.nix b/nix/modules/nixos/infrastructure/provisioning/default.nix index afb8c28..e62826a 100644 --- a/nix/modules/nixos/infrastructure/provisioning/default.nix +++ b/nix/modules/nixos/infrastructure/provisioning/default.nix @@ -29,6 +29,8 @@ let "unifi" "hcloud" "cloudflare" + "vault" + "authentik" ] ); description = "Needed endpoints to be used during provisioning"; @@ -40,6 +42,11 @@ in options.khscodes.infrastructure.provisioning = { pre = provisioning; post = provisioning; + instanceUserData = lib.mkOption { + type = lib.types.str; + description = "User data that should be added to the instance during provisioning"; + default = ""; + }; preConfig = lib.mkOption { type = lib.types.nullOr lib.types.path; description = "The generated config for the pre provisioning, if any was specified"; diff --git a/nix/modules/nixos/infrastructure/vault-server-approle/default.nix b/nix/modules/nixos/infrastructure/vault-server-approle/default.nix new file mode 100644 index 0000000..05fdb99 --- /dev/null +++ b/nix/modules/nixos/infrastructure/vault-server-approle/default.nix @@ -0,0 +1,120 @@ +{ + config, + lib, + inputs, + ... +}: +let + cfg = config.khscodes.infrastructure.vault-server-approle; +in +{ + options.khscodes.infrastructure.vault-server-approle = { + enable = lib.mkEnableOption "Enables creating an OpenBAO role for the server"; + stage = lib.mkOption { + type = lib.types.enum [ + "pre" + "post" + ]; + description = "The provisioning stage that should include the provisioning. This should be pre for every server except the OpenBAO server itself"; + default = "pre"; + }; + role_name = lib.mkOption { + type = lib.types.str; + description = "Name of the role being created"; + default = config.networking.fqdnOrHostName; + }; + policy = lib.mkOption { + type = lib.types.attrsOf ( + lib.khscodes.mkSubmodule { + options = { + capabilities = lib.mkOption { + type = lib.types.listOf ( + lib.types.enum [ + "create" + "update" + "patch" + "read" + "delete" + "list" + ] + ); + }; + }; + description = "Vault role policy"; + } + ); + }; + stageModules = lib.mkOption { + type = lib.types.listOf lib.types.anything; + description = "Extra modules to add to the configured stage"; + default = [ ]; + }; + }; + + config = lib.mkIf cfg.enable { + khscodes.infrastructure.provisioning.${cfg.stage} = { + endpoints = [ "vault" ]; + modules = [ + ( + { config, ... }: + { + imports = [ inputs.self.terranixModules.vault ]; + khscodes.vault = { + enable = true; + approle_auth_backend_role.${cfg.role_name} = { + backend = "approle"; + role_name = cfg.role_name; + # I keep the secret ids alive for quite long, as I have no way of + # automatically bootstrapping a new secret id. + secret_id_ttl = 5 * 60 * 60; + secret_id_num_uses = 5 * 60; + token_ttl = 20 * 60; + token_max_ttl = 30 * 60; + token_policies = [ cfg.role_name ]; + }; + approle_auth_backend_role_secret_id.${cfg.role_name} = { + backend = "approle"; + # Not hardcoding the role name here, as reading it like this will create a dependency + # on the role being created first, which is needed. + role_name = config.khscodes.vault.output.approle_auth_backend_role.${cfg.role_name}.role_name; + # Should only be 5-10 mins once done testing + wrapping_ttl = 5 * 60 * 60; + + # All of this simply tries to ensure that I never recreate this secret id + # even if the original wrapped secret id is expired (which I expect it to be). + with_wrapped_accessor = true; + lifecycle = { + ignore_changes = [ + "num_uses" + "ttl" + ]; + }; + }; + policy.${cfg.role_name} = { + name = cfg.role_name; + policy = lib.strings.concatStringsSep "\n\n" ( + lib.lists.map ( + { name, value }: + '' + path "${name}" { + capabilities = ${builtins.toJSON value.capabilities} + } + '' + ) (lib.attrsToList cfg.policy) + ); + }; + }; + } + ) + ] ++ cfg.stageModules; + }; + # I can only provide the user data if the stage is pre (along with the instance creation) + # Also I should probably find a way of injecting this in a nicer way than this mess. + khscodes.infrastructure.provisioning.instanceUserData = lib.mkIf (cfg.stage == "pre") '' + { + "VAULT_ROLE_ID": "''${ vault_approle_auth_backend_role.${lib.khscodes.sanitize-terraform-name cfg.role_name}.role_id }", + "VAULT_SECRET_ID_WRAPPED": "''${ vault_approle_auth_backend_role_secret_id.${lib.khscodes.sanitize-terraform-name cfg.role_name}.wrapping_token }" + } + ''; + }; +} diff --git a/nix/modules/nixos/networking/fqdn/default.nix b/nix/modules/nixos/networking/fqdn/default.nix index 0689292..d419e36 100644 --- a/nix/modules/nixos/networking/fqdn/default.nix +++ b/nix/modules/nixos/networking/fqdn/default.nix @@ -21,6 +21,7 @@ in { networking.hostName = lib.mkForce hostname; networking.domain = lib.mkForce domain; + networking.fqdn = cfg; # Add the name of the server to the ssh host certificate domains, but let other configs enable getting the host certificates. khscodes.services.openssh.hostCertificate.hostNames = [ cfg ]; boot.kernel.sysctl = { diff --git a/nix/modules/nixos/openstack/default.nix b/nix/modules/nixos/openstack/default.nix index d372a03..20f6c61 100644 --- a/nix/modules/nixos/openstack/default.nix +++ b/nix/modules/nixos/openstack/default.nix @@ -16,12 +16,14 @@ in }; }; config = lib.mkIf cfg.enable { - disko = lib.khscodes.disko-root-lvm-bios { - device = "/dev/sda"; - diskName = cfg.diskName; - }; + disko = lib.mkDefault ( + lib.khscodes.disko-root-bios { + device = "/dev/sda"; + diskName = cfg.diskName; + } + ); boot.loader.grub.efiSupport = false; boot.loader.timeout = 1; - khscodes.virtualisation.qemu-guest.enable = true; + # khscodes.virtualisation.qemu-guest.enable = true; }; } diff --git a/nix/modules/nixos/services/openssh/default.nix b/nix/modules/nixos/services/openssh/default.nix index 56d8620..fbbbbd1 100644 --- a/nix/modules/nixos/services/openssh/default.nix +++ b/nix/modules/nixos/services/openssh/default.nix @@ -7,11 +7,6 @@ in enable = lib.mkEnableOption "Enables openssh service for the instance"; hostCertificate = { enable = lib.mkEnableOption "Enables getting host certificates from OpenBAO"; - secretName = lib.mkOption { - type = lib.types.str; - description = "Secret where the certificate is stored"; - example = "ssh-host/sign/ca-kaareskovgaard.net"; - }; hostNames = lib.mkOption { type = lib.types.listOf lib.types.str; description = "The list of host names to get certificates for"; @@ -24,6 +19,9 @@ in let certificateNames = lib.lists.unique cfg.hostCertificate.hostNames; hostCertificatEnable = cfg.hostCertificate.enable && cfg.hostCertificate.hostNames != [ ]; + vaultRoleName = config.khscodes.infrastructure.vault-server-approle.role_name; + fqdn = config.networking.fqdnOrHostName; + sshHostBackend = "ssh-host"; in { services.openssh = { @@ -37,6 +35,34 @@ in HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub ''; }; + khscodes.infrastructure.vault-server-approle = { + enable = true; + policy."${sshHostBackend}/sign/${vaultRoleName}" = { + capabilities = [ + "read" + "update" + "create" + ]; + }; + stageModules = [ + { + khscodes.vault.ssh_secret_backend_role.${vaultRoleName} = { + name = fqdn; + backend = sshHostBackend; + key_type = "ca"; + allow_host_certificates = true; + allow_bare_domains = true; + allowed_domains = certificateNames; + allowed_user_key_config = [ + { + type = "ed25519"; + lengths = [ 0 ]; + } + ]; + }; + } + ]; + }; khscodes.services.vault-agent = lib.mkIf hostCertificatEnable { enable = true; templates = [ @@ -44,7 +70,7 @@ in contents = '' {{- $public_key := file "/etc/ssh/ssh_host_ed25519_key.pub" -}} {{- $public_key = printf "public_key=%s" $public_key -}} - {{- with secret "${cfg.hostCertificate.secretName}" "cert_type=host" $public_key "valid_principals=${lib.strings.concatStringsSep "," certificateNames}" -}} + {{- with secret "ssh-host/sign/${fqdn}" "cert_type=host" $public_key "valid_principals=${lib.strings.concatStringsSep "," certificateNames}" -}} {{ .Data.signed_key }} {{- end -}} ''; diff --git a/nix/modules/nixos/services/openstack-read-vault-auth-from-userdata/default.nix b/nix/modules/nixos/services/openstack-read-vault-auth-from-userdata/default.nix new file mode 100644 index 0000000..3cb6e35 --- /dev/null +++ b/nix/modules/nixos/services/openstack-read-vault-auth-from-userdata/default.nix @@ -0,0 +1,63 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.khscodes.services.openstack-read-vault-auth-from-userdata; +in +{ + options.khscodes.services.openstack-read-vault-auth-from-userdata = { + enable = lib.mkEnableOption "Enables reading vault auth information from instance userdata"; + }; + + config = lib.mkIf (cfg.enable && config.khscodes.services.vault-agent.enable) ( + let + vault_addr = config.khscodes.services.vault-agent.vault.address; + secretIdFilePath = config.khscodes.services.vault-agent.secretIdFilePath; + roleIdFilePath = config.khscodes.services.vault-agent.roleIdFilePath; + in + { + systemd.services."openstack-read-vault-auth-from-userdata" = { + enable = true; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = lib.getExe ( + pkgs.writeShellApplication { + name = "openstack-read-vault-auth-from-userdata"; + runtimeInputs = [ + pkgs.curl + pkgs.jq + pkgs.openbao + ]; + text = '' + if [[ -f "${lib.escapeShellArg secretIdFilePath}" ]]; then + echo "Secret id already found, not copying new id" + exit 0 + fi + userdata="$(curl http://169.254.169.254/openstack/2012-08-10/user_data)" + role_id="$(echo "$userdata" | jq --raw '.VAULT_ROLE_ID')" + secret_id_wrapped="$(echo "$userdata" | jq --raw '.VAULT_SECRET_ID_WRAPPED')" + secret_id="$(BAO_ADDR=${lib.escapeShellArg vault_addr} bao unwrap -field=secret_id "$secret_id_wrapped")" + mkdir -p "$(dirname ${lib.escapeShellArg secretIdFilePath})" + mkdir -p "$(dirname ${lib.escapeShellArg roleIdFilePath})" + echo -n "$role_id" > ${lib.escapeShellArg roleIdFilePath} + echo -n "$secret_id" > ${lib.escapeShellArg secretIdFilePath} + chown root:root "$${lib.escapeShellArg secretIdFilePath}" + chmod 0600 "$${lib.escapeShellArg secretIdFilePath}" + chown root:root "$${lib.escapeShellArg roleIdFilePath}" + chmod 0600 "$${lib.escapeShellArg roleIdFilePath}" + echo "Role id and secret id copied, restart vault-agent" + systemctl restart vault-agent-openbao.service + ''; + } + ); + }; + }; + } + ); +} diff --git a/nix/modules/nixos/services/vault-agent/default.nix b/nix/modules/nixos/services/vault-agent/default.nix index bab6eb8..2b88c6a 100644 --- a/nix/modules/nixos/services/vault-agent/default.nix +++ b/nix/modules/nixos/services/vault-agent/default.nix @@ -5,9 +5,7 @@ ... }: let - cfg = config.input-output.openbao.agent; - secretIdFilePath = "/var/lib/vault-agent/secret-id"; - roleIdFilePath = "/var/lib/vault-agent/role-id"; + cfg = config.khscodes.services.vault-agent; mkSubmodule = { options, @@ -62,8 +60,8 @@ let type = "approle"; config = { mount_path = "auth/approle"; - role_id_file_path = roleIdFilePath; - secret_id_file_path = secretIdFilePath; + role_id_file_path = cfg.vault.roleIdFilePath; + secret_id_file_path = cfg.vault.secretIdFilePath; remove_secret_id_file_after_reading = false; }; } @@ -90,10 +88,22 @@ in default = pkgs.openbao; defaultText = "pkgs.openbao"; }; - vault.address = lib.mkOption { - type = lib.types.str; - description = "Address of the Vault/OpenBAO service"; - default = "https://vault.kaareskovgaard.net"; + vault = { + address = lib.mkOption { + type = lib.types.str; + description = "Address of the Vault/OpenBAO service"; + default = "https://vault.kaareskovgaard.net"; + }; + roleIdFilePath = lib.mkOption { + type = lib.types.str; + description = "Location of the role id"; + default = "/var/lib/vault-agent/role-id"; + }; + secretIdFilePath = lib.mkOption { + type = lib.types.str; + description = "Location of the secret id"; + default = "/var/lib/vault-agent/secret-id"; + }; }; templates = lib.mkOption { default = [ ]; @@ -146,42 +156,10 @@ in wantedBy = unitsDependsOnAgent; unitConfig = { ConditionPathExists = [ - secretIdFilePath - roleIdFilePath + cfg.vault.secretIdFilePath + cfg.vault.roleIdFilePath ]; }; }; - environment.systemPackages = [ - (pkgs.writeShellApplication { - name = "vault-agent-load-credentials"; - meta = { - mainProgram = "vault-agent-load-credentials"; - }; - runtimeInputs = [ - pkgs.systemd - pkgs.openbao - ]; - text = '' - if [[ -z "''${1:-}" || -z "''${2:-}" ]]; then - >&2 echo "Usage: vault-agent-load-credentials " - exit 1 - fi - role_id="$1" - secret_id_wrapped="$2" - secret_id="$(BAO_ADDR=${lib.escapeShellArg cfg.vault.address} bao unwrap -field=secret_id "$secret_id_wrapped")" - mkdir -p "$(dirname ${lib.escapeShellArg secretIdFilePath})" - mkdir -p "$(dirname ${lib.escapeShellArg roleIdFilePath})" - echo -n "$role_id" > ${lib.escapeShellArg roleIdFilePath} - echo -n "$secret_id" > ${lib.escapeShellArg secretIdFilePath} - chown root:root "$${lib.escapeShellArg secretIdFilePath}" - chmod 0600 "$${lib.escapeShellArg secretIdFilePath}" - chown root:root "$${lib.escapeShellArg roleIdFilePath}" - chmod 0600 "$${lib.escapeShellArg roleIdFilePath}" - systemctl restart vault-agent-openbao.service - ${restartUnits unitsDependsOnAgent} - ${reloadOrRestartUnits unitsDependsOnAgent} - ''; - }) - ]; }; } diff --git a/nix/modules/nixos/virtualisation/qemu-guest/default.nix b/nix/modules/nixos/virtualisation/qemu-guest/default.nix index 4ab6ae4..5eec748 100644 --- a/nix/modules/nixos/virtualisation/qemu-guest/default.nix +++ b/nix/modules/nixos/virtualisation/qemu-guest/default.nix @@ -1,7 +1,6 @@ { config, lib, - modulesPath, ... }: let @@ -24,22 +23,21 @@ in enableWhenVmTarget = lib.mkEnableOption "Enables some enhancement settings when building as a vm"; }; - imports = [ "${modulesPath}/virtualisation/qemu-vm.nix" ]; - config = lib.mkIf cfg.enableWhenVmTarget { virtualisation = { vmVariant = { - services.qemuGuest.enable = true; - services.spice-vdagentd.enable = true; khscodes.virtualisation.qemu-guest.enable = true; - }; - memorySize = 1024 * 8; - qemu = { - options = [ - "-smp 8" - "-vga none -device virtio-gpu-gl,hostmem=2G,blob=true,venus=true" - rng - ] ++ spice; + services.spice-vdagentd.enable = true; + virtualisation = { + memorySize = 1024 * 8; + qemu = { + options = [ + "-smp 8" + "-vga none -device virtio-gpu-gl,hostmem=2G,blob=true,venus=true" + rng + ] ++ spice; + }; + }; }; }; }; diff --git a/nix/modules/nixos/virtualisation/qemu-guest/profile.nix b/nix/modules/nixos/virtualisation/qemu-guest/profile.nix index 77560ee..b7c329b 100644 --- a/nix/modules/nixos/virtualisation/qemu-guest/profile.nix +++ b/nix/modules/nixos/virtualisation/qemu-guest/profile.nix @@ -8,5 +8,10 @@ let cfg = config.khscodes.virtualisation.qemu-guest; in { - config = lib.mkIf cfg.enable (import "${modulesPath}/profiles/qemu-guest.nix" { }); + config = lib.mkIf cfg.enable ( + (import "${modulesPath}/profiles/qemu-guest.nix" { }) + // { + services.qemuGuest.enable = true; + } + ); } diff --git a/nix/modules/terranix/hcloud/default.nix b/nix/modules/terranix/hcloud/default.nix index 039c649..e5d22c3 100644 --- a/nix/modules/terranix/hcloud/default.nix +++ b/nix/modules/terranix/hcloud/default.nix @@ -51,6 +51,11 @@ let default = null; description = "FQDN to map rDNS to"; }; + user_data = lib.mkOption { + type = lib.types.str; + default = ""; + description = "User data for the instance"; + }; }; }; hcloudDataSshKeys = khscodesLib.mkSubmodule { @@ -106,6 +111,7 @@ in ipv6_enabled = true; ipv6 = "\${ hcloud_primary_ip.${name}_ipv6.id }"; }; + user_data = builtins.toJSON value.user_data; lifecycle = { ignore_changes = [ "ssh_keys" diff --git a/nix/modules/terranix/openbao/default.nix b/nix/modules/terranix/openbao/default.nix deleted file mode 100644 index adfe0a6..0000000 --- a/nix/modules/terranix/openbao/default.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ khscodesLib, inputs }: -{ lib, config, ... }: -let - cfg = config.khscodes.openbao; - modules = [ - ./output.nix - ./vault_mount.nix - ]; -in -{ - options.khscodes.openbao = { - enable = lib.mkEnableOption "Enables the openbao provider"; - }; - - imports = lib.lists.map (m: import m { inherit khscodesLib inputs; }) modules; - - config = lib.mkIf cfg.enable { - provider.vault = { - address = "https://auth.kaareskovgaard.net"; - }; - terraform.required_providers.vault = { - source = "hashicorp/vault"; - version = "5.0.0"; - }; - resource.vault_mount = lib.mapAttrs' ( - name: value: { - name = khscodesLib.sanitize-terraform-name name; - value = value; - } - ); - }; -} diff --git a/nix/modules/terranix/openbao/output.nix b/nix/modules/terranix/openbao/output.nix deleted file mode 100644 index 9e92755..0000000 --- a/nix/modules/terranix/openbao/output.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ khscodesLib, ... }: -{ config, lib, ... }: -let - cfg = config.khscodes.openbao; -in -{ - options.khscodes.openbao = { }; - config = { - }; -} diff --git a/nix/modules/terranix/openbao/ssh_secret_backend_ca.nix b/nix/modules/terranix/openbao/ssh_secret_backend_ca.nix deleted file mode 100644 index 4521947..0000000 --- a/nix/modules/terranix/openbao/ssh_secret_backend_ca.nix +++ /dev/null @@ -1,45 +0,0 @@ -{ khscodesLib, ... }: -{ lib, config, ... }: -let - cfg = config.khscodes.openbao; -in -{ - options.khscodes.openbao = { - vault_ssh_secret_backend_ca = lib.mkOption { - type = lib.types.attrsOf ( - khscodesLib.mkSubmodule { - options = { - backend = lib.mkOption { - type = lib.types.str; - description = "Path of the backend mount"; - }; - generate_signing_key = lib.mkOption { - type = lib.types.bool; - description = "Generate a signing key on the server"; - }; - key_type = lib.mkOption { - type = lib.types.str; - description = "The type of the signing key to use/generate"; - }; - }; - description = "vault_ssh_secret_backend_ca"; - } - ); - }; - }; - config = lib.mkIf cfg.enable { - provider.vault = { - address = "https://auth.kaareskovgaard.net"; - }; - terraform.required_providers.vault = { - source = "hashicorp/vault"; - version = "5.0.0"; - }; - resource.vault_ssh_secret_backend_ca = lib.mapAttrs' ( - name: value: { - name = khscodesLib.sanitize-terraform-name name; - value = value; - } - ); - }; -} diff --git a/nix/modules/terranix/openstack/default.nix b/nix/modules/terranix/openstack/default.nix index ba13dba..c22c743 100644 --- a/nix/modules/terranix/openstack/default.nix +++ b/nix/modules/terranix/openstack/default.nix @@ -85,6 +85,11 @@ let "1.0.0.1" ]; }; + user_data = lib.mkOption { + type = lib.types.str; + default = ""; + description = "User data for the instance"; + }; volume_size = lib.mkOption { type = lib.types.int; description = "Size of the root volume, in gigabytes"; @@ -432,6 +437,7 @@ in uuid = "\${ openstack_networking_network_v2.${sanitizedName}.id }"; } ]; + user_data = value.user_data; }; } ) cfg.compute_instance; diff --git a/nix/modules/terranix/vault/approle_auth_backend.nix b/nix/modules/terranix/vault/approle_auth_backend.nix new file mode 100644 index 0000000..06ecb66 --- /dev/null +++ b/nix/modules/terranix/vault/approle_auth_backend.nix @@ -0,0 +1,120 @@ +{ khscodesLib, ... }: +{ lib, config, ... }: +let + cfg = config.khscodes.vault; +in +{ + options.khscodes.vault = { + approle_auth_backend_role = lib.mkOption { + type = lib.types.attrsOf ( + khscodesLib.mkSubmodule { + options = { + backend = lib.mkOption { + type = lib.types.str; + description = "Path of the backend"; + default = "approle"; + }; + role_name = lib.mkOption { + type = lib.types.str; + description = "Name of the role"; + }; + secret_id_ttl = lib.mkOption { + type = lib.types.int; + description = "TTL for the secret id, in seconds"; + }; + secret_id_num_uses = lib.mkOption { + type = lib.types.int; + description = "Maximum number of uses per secret id"; + }; + token_ttl = lib.mkOption { + type = lib.types.int; + description = "TTL for the tokens issued, in seconds"; + }; + token_max_ttl = lib.mkOption { + type = lib.types.int; + description = "Max TTL for the tokens issued, in seconds"; + }; + token_policies = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Policies attached to the backend role"; + }; + }; + description = "vault_approle_auth_backend_role"; + } + ); + description = "Defines an app backend role"; + default = { }; + }; + approle_auth_backend_role_secret_id = lib.mkOption { + type = lib.types.attrsOf ( + khscodesLib.mkSubmodule { + options = { + backend = lib.mkOption { + type = lib.types.str; + description = "Path of the backend"; + default = "approle"; + }; + role_name = lib.mkOption { + type = lib.types.str; + description = "NThe name of the role to create the SecretID for"; + }; + cidr_list = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "If set, specifies blocks of IP addresses which can perform the login operation using this SecretID"; + default = [ ]; + }; + metadata = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + description = "Metadata associated with tokens issued by this secret"; + default = { }; + }; + num_uses = lib.mkOption { + type = lib.types.int; + description = "Number of uses for the secret id"; + default = 300; + }; + wrapping_ttl = lib.mkOption { + type = lib.types.nullOr lib.types.int; + description = "If set, the SecretID response will be response-wrapped and available for the duration specified. Only a single unwrapping of the token is allowed."; + default = null; + }; + with_wrapped_accessor = lib.mkOption { + type = lib.types.bool; + description = "Set to `true` to use the wrapped secret-id accessor as the resource ID. If `false` (default value), a fresh secret ID will be regenerated whenever the wrapping token is expired or invalidated through unwrapping."; + default = false; + }; + lifecycle.ignore_changes = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Ignores changes to the following properties when rerunning the terraform script"; + default = [ ]; + }; + }; + description = "vault_approle_auth_backend_role_secret_id"; + } + ); + description = "Defines an app backend role secret id"; + default = { }; + }; + }; + config = lib.mkIf cfg.enable { + resource.vault_approle_auth_backend_role = lib.mapAttrs' (name: value: { + name = khscodesLib.sanitize-terraform-name name; + value = value; + }) cfg.approle_auth_backend_role; + resource.vault_approle_auth_backend_role_secret_id = lib.mapAttrs' (name: value: { + name = khscodesLib.sanitize-terraform-name name; + value = { + inherit (value) + backend + role_name + cidr_list + wrapping_ttl + num_uses + with_wrapped_accessor + lifecycle + ; + metadata = if value.metadata != null then builtins.toJSON value.metadata else null; + }; + }) cfg.approle_auth_backend_role_secret_id; + }; +} diff --git a/nix/modules/terranix/vault/default.nix b/nix/modules/terranix/vault/default.nix new file mode 100644 index 0000000..11dfa26 --- /dev/null +++ b/nix/modules/terranix/vault/default.nix @@ -0,0 +1,49 @@ +{ khscodesLib, inputs }: +{ lib, config, ... }: +let + cfg = config.khscodes.vault; + modules = [ + ./approle_auth_backend.nix + ./output.nix + ./mount.nix + ./ssh_secret_backend.nix + ]; +in +{ + options.khscodes.vault = { + enable = lib.mkEnableOption "Enables the openbao provider"; + policy = lib.mkOption { + type = lib.types.attrsOf ( + khscodesLib.mkSubmodule { + options = { + name = lib.mkOption { + type = lib.types.str; + description = "Name of the policy"; + }; + policy = lib.mkOption { + type = lib.types.lines; + description = "The policy"; + }; + }; + description = "vault_policy"; + } + ); + }; + }; + + imports = lib.lists.map (m: import m { inherit khscodesLib inputs; }) modules; + + config = lib.mkIf cfg.enable { + provider.vault = { + address = "https://vault.kaareskovgaard.net"; + }; + terraform.required_providers.vault = { + source = "hashicorp/vault"; + version = "5.0.0"; + }; + resource.vault_policy = lib.mapAttrs' (name: value: { + name = khscodesLib.sanitize-terraform-name name; + value = value; + }) cfg.policy; + }; +} diff --git a/nix/modules/terranix/openbao/vault_mount.nix b/nix/modules/terranix/vault/mount.nix similarity index 67% rename from nix/modules/terranix/openbao/vault_mount.nix rename to nix/modules/terranix/vault/mount.nix index 4f4be60..7618940 100644 --- a/nix/modules/terranix/openbao/vault_mount.nix +++ b/nix/modules/terranix/vault/mount.nix @@ -1,11 +1,11 @@ { khscodesLib, ... }: { lib, config, ... }: let - cfg = config.khscodes.openbao; + cfg = config.khscodes.vault; in { - options.khscodes.openbao = { - vault_mount = lib.mkOption { + options.khscodes.vault = { + mount = lib.mkOption { type = lib.types.attrsOf ( khscodesLib.mkSubmodule { options = { @@ -32,21 +32,14 @@ in description = "vault_mount"; } ); + description = "Defines a vault mount"; + default = { }; }; }; config = lib.mkIf cfg.enable { - provider.vault = { - address = "https://auth.kaareskovgaard.net"; - }; - terraform.required_providers.vault = { - source = "hashicorp/vault"; - version = "5.0.0"; - }; - resource.vault_mount = lib.mapAttrs' ( - name: value: { - name = khscodesLib.sanitize-terraform-name name; - value = value; - } - ); + resource.vault_mount = lib.mapAttrs' (name: value: { + name = khscodesLib.sanitize-terraform-name name; + value = value; + }) cfg.mount; }; } diff --git a/nix/modules/terranix/vault/output.nix b/nix/modules/terranix/vault/output.nix new file mode 100644 index 0000000..da0b2b0 --- /dev/null +++ b/nix/modules/terranix/vault/output.nix @@ -0,0 +1,35 @@ +{ khscodesLib, ... }: +{ config, lib, ... }: +let + cfg = config.khscodes.vault; +in +{ + options.khscodes.vault = { + output = { + approle_auth_backend_role = lib.mkOption { + type = lib.types.attrsOf ( + khscodesLib.mkSubmodule { + options = { + role_name = lib.mkOption { + type = lib.types.str; + description = "The name of the role. Can be used instead of hardcoding the role, to create a dependency in OpenTofu"; + }; + }; + description = "vault_approle_auth_backend_role output"; + } + ); + }; + }; + }; + config = { + khscodes.vault.output.approle_auth_backend_role = lib.mapAttrs ( + name: value: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + role_name = "\${ vault_approle_auth_backend_role.${sanitizedName}.role_name }"; + } + ) cfg.approle_auth_backend_role; + }; +} diff --git a/nix/modules/terranix/vault/ssh_secret_backend.nix b/nix/modules/terranix/vault/ssh_secret_backend.nix new file mode 100644 index 0000000..4543404 --- /dev/null +++ b/nix/modules/terranix/vault/ssh_secret_backend.nix @@ -0,0 +1,129 @@ +{ khscodesLib, ... }: +{ lib, config, ... }: +let + cfg = config.khscodes.vault; +in +{ + options.khscodes.vault = { + ssh_secret_backend_role = lib.mkOption { + type = lib.types.attrsOf ( + khscodesLib.mkSubmodule { + options = { + name = lib.mkOption { + type = lib.types.str; + description = "Specifies the name of the role to create."; + }; + backend = lib.mkOption { + type = lib.types.str; + description = "The path where the SSH secret backend is mounted."; + }; + key_type = lib.mkOption { + type = lib.types.enum [ + "otp" + "dynamic" + "ca" + ]; + description = "Specifies the type of credentials generated by this role."; + }; + allow_bare_domains = lib.mkOption { + type = lib.types.nullOr (lib.types.bool); + description = "Specifies if host certificates that are requested are allowed to use the base domains listed in allowed_domains."; + default = false; + }; + allow_host_certificates = lib.mkOption { + type = lib.types.nullOr (lib.types.bool); + description = "Specifies if certificates are allowed to be signed for use as a 'host'."; + default = false; + }; + allow_subdomains = lib.mkOption { + type = lib.types.nullOr (lib.types.bool); + description = "Specifies if host certificates that are requested are allowed to be subdomains of those listed in allowed_domains."; + default = false; + }; + allow_user_certificates = lib.mkOption { + type = lib.types.nullOr (lib.types.bool); + description = "Specifies if certificates are allowed to be signed for use as a 'user'."; + default = false; + }; + allowed_critical_options = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Specifies a list of critical options that certificates can have when signed."; + default = [ ]; + }; + allowed_users = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Specifies a list of usernames that are to be allowed, only if certain usernames are to be allowed."; + default = [ ]; + }; + default_user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "Specifies the default username for which a credential will be generated."; + default = null; + }; + allowed_domains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "The list of domains for which a client can request a host certificate"; + default = [ ]; + }; + allowed_user_key_config = lib.mkOption { + type = lib.types.listOf ( + khscodesLib.mkSubmodule { + options = { + type = lib.mkOption { + type = lib.types.enum [ + "rsa" + "ecdsa" + "ec" + "dsa" + "ed25519" + "ssh-rsa" + "ssh-dss" + "ssh-ed25519" + "ecdsa-sha2-nistp256" + "ecdsa-sha2-nistp384" + "ecdsa-sha2-nistp521" + ]; + description = "The SSH public key type."; + }; + lengths = lib.mkOption { + type = lib.types.listOf lib.types.int; + description = "A list of allowed key lengths as integers. For key types that do not support setting the length a value of [0] should be used."; + }; + }; + description = "allowed_user_key_config"; + } + ); + description = "Set of configuration blocks to define allowed user key configuration, like key type and their lengths."; + }; + }; + description = "vault_ssh_secret_backend_role"; + } + ); + description = "Defines an ssh secret backend"; + default = { }; + }; + }; + config = lib.mkIf cfg.enable { + resource.vault_ssh_secret_backend_role = lib.mapAttrs' (name: value: { + name = khscodesLib.sanitize-terraform-name name; + value = { + inherit (value) + name + backend + key_type + allow_bare_domains + allow_host_certificates + allow_subdomains + allow_user_certificates + default_user + allowed_user_key_config + ; + allowed_critical_options = lib.strings.concatStringsSep "," ( + lib.lists.unique value.allowed_critical_options + ); + allowed_domains = lib.strings.concatStringsSep "," (lib.lists.unique value.allowed_domains); + allowed_users = lib.strings.concatStringsSep "," (lib.lists.unique value.allowed_users); + }; + }) cfg.ssh_secret_backend_role; + }; +} diff --git a/nix/packages/opentofu/default.nix b/nix/packages/opentofu/default.nix index 2d18324..4212c13 100644 --- a/nix/packages/opentofu/default.nix +++ b/nix/packages/opentofu/default.nix @@ -4,4 +4,5 @@ pkgs.opentofu.withPlugins (p: [ pkgs.khscodes.terraform-provider-cloudflare pkgs.khscodes.terraform-provider-hcloud pkgs.khscodes.terraform-provider-openstack + pkgs.khscodes.terraform-provider-vault ]) diff --git a/nix/packages/terraform-provider-vault/default.nix b/nix/packages/terraform-provider-vault/default.nix new file mode 100644 index 0000000..580112e --- /dev/null +++ b/nix/packages/terraform-provider-vault/default.nix @@ -0,0 +1,10 @@ +{ pkgs }: +pkgs.terraform-providers.mkProvider { + hash = "sha256-Vqnmw69fktBQhSkj/W0legJ4sHOQP9Moqqi6Z5qYFy4="; + homepage = "https://registry.terraform.io/providers/hashicorp/vault"; + owner = "hashicorp"; + repo = "terraform-provider-vault"; + rev = "v5.0.0"; + spdx = "MPL-2.0"; + vendorHash = "sha256-6gWw4ypQZWPX7VC9cZxHiU/KhTYEdMTZ276B9neGAiI="; +} diff --git a/nix/systems/aarch64-linux/khs.codes/default.nix b/nix/systems/aarch64-linux/khs.codes/default.nix index aed4c34..6e096ac 100644 --- a/nix/systems/aarch64-linux/khs.codes/default.nix +++ b/nix/systems/aarch64-linux/khs.codes/default.nix @@ -8,7 +8,6 @@ enable = true; mapRdns = true; server_type = "cax11"; - secretsSource = "bitwarden"; }; khscodes.networking.fqdn = "khs.codes"; system.stateVersion = "25.05"; diff --git a/nix/systems/x86_64-linux/desktop.kaareskovgaard.net/default.nix b/nix/systems/x86_64-linux/desktop.kaareskovgaard.net/default.nix index 5a5ac3b..5e6b310 100644 --- a/nix/systems/x86_64-linux/desktop.kaareskovgaard.net/default.nix +++ b/nix/systems/x86_64-linux/desktop.kaareskovgaard.net/default.nix @@ -1,9 +1,14 @@ { inputs, + lib, ... }: { imports = [ "${inputs.self}/nix/profiles/nixos/khs-desktop.nix" ]; khscodes.networking.fqdn = "desktop.kaareskovgaard.net"; + disko = lib.khscodes.disko-root-bios { + device = "/dev/sda"; + diskName = "nixos"; + }; system.stateVersion = "25.05"; } diff --git a/nix/systems/x86_64-linux/test.kaareskovgaard.net/default.nix b/nix/systems/x86_64-linux/test.kaareskovgaard.net/default.nix index c6f0b6a..8aa3a36 100644 --- a/nix/systems/x86_64-linux/test.kaareskovgaard.net/default.nix +++ b/nix/systems/x86_64-linux/test.kaareskovgaard.net/default.nix @@ -7,11 +7,10 @@ khscodes.infrastructure.khs-openstack-instance = { enable = true; flavor = "m.medium"; - secretsSource = "vault"; }; snowfallorg.users.khs.admin = true; users.users.khs = { - initialPassword = "changeMe"; + initialPassword = "changeme"; openssh.authorizedKeys.keys = [ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqY0FHnWFKfLG2yfgr4qka5sR9CK+EMAhzlHUkaQyWHTKD+G0/vC/fNPyL1VV3Dxc/ajxGuPzVE+mBMoyxazL3EtuCDOVvHJ5CR+MUSEckg/DDwcGHqy6rC8BvVVpTAVL04ByQdwFnpE1qNSBaQLkxaFVdtriGKkgMkc7+UNeYX/bv7yn+APqfP1a3xr6wdkSSdO8x4N2jsSygOIMx10hLyCV4Ueu7Kp8Ww4rGY8j5o7lKJhbgfItBfSOuQHdppHVF/GKYRhdnK6Y2fZVYbhq4KipUtclbZ6O/VYd8/sOO98+LMm7cOX+K35PQjUpYgcoNy5+Sw3CNS/NHn4JvOtTaUEYP7fK6c9LhMULOO3T7Cm6TMdiFjUKHkyG+s2Mu/LXJJoilw571zwuh6chkeitW8+Ht7k0aPV96kNEvTdoXwLhBifVEaChlAsLAzSUjUq+YYCiXVk0VIXCZQWKj8LoVNTmaqDksWwbcT64fw/FpVC0N18WHbKcFUEIW/O4spJMa30CQwf9FeqpoWoaF1oRClCSDPvX0AauCu0JcmRinz1/JmlXljnXWbSfm20/V+WyvktlI0wTD0cdpNuSasT9vS77YfJ8nutcWWZKSkCj4R4uHeCNpDTX5YXzapy7FxpM9ANCXLIvoGX7Yafba2Po+er7SSsUIY1AsnBBr8ZoDVw==" ];