Compare commits

...

2 commits

Author SHA1 Message Date
459b45ccc5
Get openstack working again
Some checks failed
/ systems (push) Successful in 8m26s
/ dev-shell (push) Successful in 2m4s
/ rust-packages (push) Successful in 5m2s
/ terraform-providers (push) Successful in 10m59s
/ check (push) Failing after 6m4s
Also first instance of getting server up with working certificate
right away, through cloud user data.
2025-07-10 00:51:28 +02:00
608d758f30
Begin testing bootstrapping of vault authentication
However, the nixos-install script fails on khs openstack
as the system won't boot up after installation due it
being unable to locate the root disk. I am not sure what disk
it ends up finding.
2025-07-09 23:53:42 +02:00
32 changed files with 707 additions and 194 deletions

View file

@ -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-hcloud'
nix build --no-link '.#packages.x86_64-linux.terraform-provider-openstack' 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-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'

View file

@ -40,6 +40,8 @@ To delete the resources again run:
nix run '.#destroy-instance' -- <hostname> nix run '.#destroy-instance' -- <hostname>
``` ```
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 ## Secrets
To transfer the secrets needed for OpenTofu from Bitwarden to OpenBAO run: To transfer the secrets needed for OpenTofu from Bitwarden to OpenBAO run:

Binary file not shown.

18
flake.lock generated
View file

@ -546,11 +546,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1751741127, "lastModified": 1751943650,
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=", "narHash": "sha256-7orTnNqkGGru8Je6Un6mq1T8YVVU/O5kyW4+f9C1mZQ=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "29e290002bfff26af1db6f64d070698019460302", "rev": "88983d4b665fb491861005137ce2b11a9f89f203",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -624,11 +624,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1751942411, "lastModified": 1752028888,
"narHash": "sha256-01uMHCt2U9tP4f24DGch145tT8YQppLY5TC9mWK7O0A=", "narHash": "sha256-LRj3/PUpII6taWOrX1w/OeI6f1ncND02PP/kEHvPCqU=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "c587235f892930a61c9e415f0d9792a1b27a41a2", "rev": "a0f1c656e053463b47639234b151a05e4441bb19",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -681,11 +681,11 @@
"tinted-zed": "tinted-zed" "tinted-zed": "tinted-zed"
}, },
"locked": { "locked": {
"lastModified": 1752009340, "lastModified": 1752084754,
"narHash": "sha256-6IKc+fdgJ+mWW8pBOVS5MYvttHBhvWSbff/31pG3SAY=", "narHash": "sha256-JorlZGCWxlYV01lXmUuDeKOZoLPdoN3fAKJv8YIuavs=",
"owner": "nix-community", "owner": "nix-community",
"repo": "stylix", "repo": "stylix",
"rev": "c647aaa1dead3752fb49f226a4f67ae1030d7747", "rev": "2df042576646d012d15637f43d6075995e785ce3",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -97,7 +97,7 @@
inherit inputs; inherit inputs;
khscodesLib = inputs.self.lib; khscodesLib = inputs.self.lib;
}; };
terranixModules.openbao = import ./nix/modules/terranix/openbao { terranixModules.vault = import ./nix/modules/terranix/vault {
inherit inputs; inherit inputs;
khscodesLib = inputs.self.lib; khscodesLib = inputs.self.lib;
}; };

View file

@ -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 = "/";
};
};
};
};
};
};
};
}

View file

@ -7,6 +7,7 @@
let let
cfg = config.khscodes.infrastructure.hetzner-instance; cfg = config.khscodes.infrastructure.hetzner-instance;
fqdn = config.khscodes.networking.fqdn; fqdn = config.khscodes.networking.fqdn;
provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData;
firewallTcpRules = lib.lists.map (p: { firewallTcpRules = lib.lists.map (p: {
direction = "in"; direction = "in";
protocol = "tcp"; protocol = "tcp";
@ -160,6 +161,7 @@ in
initial_image = "debian-12"; initial_image = "debian-12";
rdns = fqdn; rdns = fqdn;
ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ]; ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ];
user_data = provisioningUserData;
}; };
khscodes.cloudflare = { khscodes.cloudflare = {
enable = true; enable = true;

View file

@ -7,6 +7,7 @@
let let
cfg = config.khscodes.infrastructure.khs-openstack-instance; cfg = config.khscodes.infrastructure.khs-openstack-instance;
fqdn = config.khscodes.networking.fqdn; fqdn = config.khscodes.networking.fqdn;
provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData;
firewallTcpRules = lib.lists.flatten ( firewallTcpRules = lib.lists.flatten (
lib.lists.map (p: [ lib.lists.map (p: [
{ {
@ -83,14 +84,6 @@ in
default = "${fqdn}.tfstate"; 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 { flavor = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
description = "The server type to create"; description = "The server type to create";
@ -188,6 +181,7 @@ in
flavor = cfg.flavor; flavor = cfg.flavor;
ssh_public_key = cfg.ssh_key; ssh_public_key = cfg.ssh_key;
firewall_rules = firewallRules; firewall_rules = firewallRules;
user_data = provisioningUserData;
}; };
khscodes.unifi.enable = true; khscodes.unifi.enable = true;
khscodes.unifi.static_route.compute = { khscodes.unifi.static_route.compute = {
@ -236,11 +230,15 @@ in
message = "Must set config.khscodes.networking.fqdn when using opentofu"; message = "Must set config.khscodes.networking.fqdn when using opentofu";
} }
]; ];
khscodes.services.openssh = {
enable = true;
hostCertificate = {
enable = true;
};
};
khscodes.infrastructure.provisioning = { khscodes.infrastructure.provisioning = {
pre = { pre = {
modules = modules; modules = modules;
secretsSource = cfg.secretsSource;
endpoints = [ endpoints = [
"aws" "aws"
"cloudflare" "cloudflare"

View file

@ -29,6 +29,8 @@ let
"unifi" "unifi"
"hcloud" "hcloud"
"cloudflare" "cloudflare"
"vault"
"authentik"
] ]
); );
description = "Needed endpoints to be used during provisioning"; description = "Needed endpoints to be used during provisioning";
@ -40,6 +42,11 @@ in
options.khscodes.infrastructure.provisioning = { options.khscodes.infrastructure.provisioning = {
pre = provisioning; pre = provisioning;
post = 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 { preConfig = lib.mkOption {
type = lib.types.nullOr lib.types.path; type = lib.types.nullOr lib.types.path;
description = "The generated config for the pre provisioning, if any was specified"; description = "The generated config for the pre provisioning, if any was specified";

View file

@ -0,0 +1,121 @@
{
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.services.openstack-read-vault-auth-from-userdata.enable = true;
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 }"
}
'';
};
}

View file

@ -21,6 +21,7 @@ in
{ {
networking.hostName = lib.mkForce hostname; networking.hostName = lib.mkForce hostname;
networking.domain = lib.mkForce domain; 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. # 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 ]; khscodes.services.openssh.hostCertificate.hostNames = [ cfg ];
boot.kernel.sysctl = { boot.kernel.sysctl = {

View file

@ -16,10 +16,12 @@ in
}; };
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
disko = lib.khscodes.disko-root-lvm-bios { disko = lib.mkDefault (
device = "/dev/sda"; lib.khscodes.disko-root-lvm-bios {
diskName = cfg.diskName; device = "/dev/sda";
}; diskName = cfg.diskName;
}
);
boot.loader.grub.efiSupport = false; boot.loader.grub.efiSupport = false;
boot.loader.timeout = 1; boot.loader.timeout = 1;
khscodes.virtualisation.qemu-guest.enable = true; khscodes.virtualisation.qemu-guest.enable = true;

View file

@ -7,11 +7,6 @@ in
enable = lib.mkEnableOption "Enables openssh service for the instance"; enable = lib.mkEnableOption "Enables openssh service for the instance";
hostCertificate = { hostCertificate = {
enable = lib.mkEnableOption "Enables getting host certificates from OpenBAO"; 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 { hostNames = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
description = "The list of host names to get certificates for"; description = "The list of host names to get certificates for";
@ -24,6 +19,9 @@ in
let let
certificateNames = lib.lists.unique cfg.hostCertificate.hostNames; certificateNames = lib.lists.unique cfg.hostCertificate.hostNames;
hostCertificatEnable = cfg.hostCertificate.enable && 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 in
{ {
services.openssh = { services.openssh = {
@ -37,6 +35,34 @@ in
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub 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 { khscodes.services.vault-agent = lib.mkIf hostCertificatEnable {
enable = true; enable = true;
templates = [ templates = [
@ -44,7 +70,7 @@ in
contents = '' contents = ''
{{- $public_key := file "/etc/ssh/ssh_host_ed25519_key.pub" -}} {{- $public_key := file "/etc/ssh/ssh_host_ed25519_key.pub" -}}
{{- $public_key = printf "public_key=%s" $public_key -}} {{- $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 }} {{ .Data.signed_key }}
{{- end -}} {{- end -}}
''; '';

View file

@ -0,0 +1,66 @@
{
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.vault.secretIdFilePath;
roleIdFilePath = config.khscodes.services.vault-agent.vault.roleIdFilePath;
in
{
systemd.services."openstack-read-vault-auth-from-userdata" = {
enable = true;
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.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
pkgs.getent
pkgs.systemd
];
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-output '.VAULT_ROLE_ID')"
secret_id_wrapped="$(echo "$userdata" | jq --raw-output '.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
'';
}
);
};
};
}
);
}

View file

@ -5,9 +5,7 @@
... ...
}: }:
let let
cfg = config.input-output.openbao.agent; cfg = config.khscodes.services.vault-agent;
secretIdFilePath = "/var/lib/vault-agent/secret-id";
roleIdFilePath = "/var/lib/vault-agent/role-id";
mkSubmodule = mkSubmodule =
{ {
options, options,
@ -62,8 +60,8 @@ let
type = "approle"; type = "approle";
config = { config = {
mount_path = "auth/approle"; mount_path = "auth/approle";
role_id_file_path = roleIdFilePath; role_id_file_path = cfg.vault.roleIdFilePath;
secret_id_file_path = secretIdFilePath; secret_id_file_path = cfg.vault.secretIdFilePath;
remove_secret_id_file_after_reading = false; remove_secret_id_file_after_reading = false;
}; };
} }
@ -90,10 +88,22 @@ in
default = pkgs.openbao; default = pkgs.openbao;
defaultText = "pkgs.openbao"; defaultText = "pkgs.openbao";
}; };
vault.address = lib.mkOption { vault = {
type = lib.types.str; address = lib.mkOption {
description = "Address of the Vault/OpenBAO service"; type = lib.types.str;
default = "https://vault.kaareskovgaard.net"; 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 { templates = lib.mkOption {
default = [ ]; default = [ ];
@ -146,42 +156,10 @@ in
wantedBy = unitsDependsOnAgent; wantedBy = unitsDependsOnAgent;
unitConfig = { unitConfig = {
ConditionPathExists = [ ConditionPathExists = [
secretIdFilePath cfg.vault.secretIdFilePath
roleIdFilePath 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 <role-id> <secret-id-wrapped>"
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}
'';
})
];
}; };
} }

View file

@ -1,7 +1,6 @@
{ {
config, config,
lib, lib,
modulesPath,
... ...
}: }:
let let
@ -24,22 +23,24 @@ in
enableWhenVmTarget = lib.mkEnableOption "Enables some enhancement settings when building as a vm"; enableWhenVmTarget = lib.mkEnableOption "Enables some enhancement settings when building as a vm";
}; };
imports = [ "${modulesPath}/virtualisation/qemu-vm.nix" ]; imports = [ ./profile.nix ];
config = lib.mkIf cfg.enableWhenVmTarget { config = lib.mkIf cfg.enable {
virtualisation = { services.qemuGuest.enable = true;
virtualisation = lib.mkIf cfg.enableWhenVmTarget {
vmVariant = { vmVariant = {
services.qemuGuest.enable = true;
services.spice-vdagentd.enable = true;
khscodes.virtualisation.qemu-guest.enable = true; khscodes.virtualisation.qemu-guest.enable = true;
}; services.spice-vdagentd.enable = true;
memorySize = 1024 * 8; virtualisation = {
qemu = { memorySize = 1024 * 8;
options = [ qemu = {
"-smp 8" options = [
"-vga none -device virtio-gpu-gl,hostmem=2G,blob=true,venus=true" "-smp 8"
rng "-vga none -device virtio-gpu-gl,hostmem=2G,blob=true,venus=true"
] ++ spice; rng
] ++ spice;
};
};
}; };
}; };
}; };

View file

@ -51,6 +51,11 @@ let
default = null; default = null;
description = "FQDN to map rDNS to"; description = "FQDN to map rDNS to";
}; };
user_data = lib.mkOption {
type = lib.types.str;
default = "";
description = "User data for the instance";
};
}; };
}; };
hcloudDataSshKeys = khscodesLib.mkSubmodule { hcloudDataSshKeys = khscodesLib.mkSubmodule {
@ -106,6 +111,7 @@ in
ipv6_enabled = true; ipv6_enabled = true;
ipv6 = "\${ hcloud_primary_ip.${name}_ipv6.id }"; ipv6 = "\${ hcloud_primary_ip.${name}_ipv6.id }";
}; };
user_data = builtins.toJSON value.user_data;
lifecycle = { lifecycle = {
ignore_changes = [ ignore_changes = [
"ssh_keys" "ssh_keys"

View file

@ -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;
}
);
};
}

View file

@ -1,10 +0,0 @@
{ khscodesLib, ... }:
{ config, lib, ... }:
let
cfg = config.khscodes.openbao;
in
{
options.khscodes.openbao = { };
config = {
};
}

View file

@ -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;
}
);
};
}

View file

@ -85,6 +85,11 @@ let
"1.0.0.1" "1.0.0.1"
]; ];
}; };
user_data = lib.mkOption {
type = lib.types.str;
default = "";
description = "User data for the instance";
};
volume_size = lib.mkOption { volume_size = lib.mkOption {
type = lib.types.int; type = lib.types.int;
description = "Size of the root volume, in gigabytes"; description = "Size of the root volume, in gigabytes";
@ -432,6 +437,7 @@ in
uuid = "\${ openstack_networking_network_v2.${sanitizedName}.id }"; uuid = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
} }
]; ];
user_data = value.user_data;
}; };
} }
) cfg.compute_instance; ) cfg.compute_instance;

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -1,11 +1,11 @@
{ khscodesLib, ... }: { khscodesLib, ... }:
{ lib, config, ... }: { lib, config, ... }:
let let
cfg = config.khscodes.openbao; cfg = config.khscodes.vault;
in in
{ {
options.khscodes.openbao = { options.khscodes.vault = {
vault_mount = lib.mkOption { mount = lib.mkOption {
type = lib.types.attrsOf ( type = lib.types.attrsOf (
khscodesLib.mkSubmodule { khscodesLib.mkSubmodule {
options = { options = {
@ -32,21 +32,14 @@ in
description = "vault_mount"; description = "vault_mount";
} }
); );
description = "Defines a vault mount";
default = { };
}; };
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
provider.vault = { resource.vault_mount = lib.mapAttrs' (name: value: {
address = "https://auth.kaareskovgaard.net"; name = khscodesLib.sanitize-terraform-name name;
}; value = value;
terraform.required_providers.vault = { }) cfg.mount;
source = "hashicorp/vault";
version = "5.0.0";
};
resource.vault_mount = lib.mapAttrs' (
name: value: {
name = khscodesLib.sanitize-terraform-name name;
value = value;
}
);
}; };
} }

View file

@ -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;
};
}

View file

@ -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;
};
}

View file

@ -4,4 +4,5 @@ pkgs.opentofu.withPlugins (p: [
pkgs.khscodes.terraform-provider-cloudflare pkgs.khscodes.terraform-provider-cloudflare
pkgs.khscodes.terraform-provider-hcloud pkgs.khscodes.terraform-provider-hcloud
pkgs.khscodes.terraform-provider-openstack pkgs.khscodes.terraform-provider-openstack
pkgs.khscodes.terraform-provider-vault
]) ])

View file

@ -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=";
}

View file

@ -5,6 +5,6 @@ pkgs.writeShellApplication {
text = '' text = ''
instance="''${1:-}" instance="''${1:-}"
connect_host="''${2:-$1}" connect_host="''${2:-$1}"
nixos-rebuild switch --flake "${inputs.self}#$instance" --target-host "$connect_host" --build-host "localhost" nixos-rebuild switch --flake "${inputs.self}#$instance" --target-host "$connect_host" --build-host "$connect_host" --use-remote-sudo
''; '';
} }

View file

@ -8,7 +8,6 @@
enable = true; enable = true;
mapRdns = true; mapRdns = true;
server_type = "cax11"; server_type = "cax11";
secretsSource = "bitwarden";
}; };
khscodes.networking.fqdn = "khs.codes"; khscodes.networking.fqdn = "khs.codes";
system.stateVersion = "25.05"; system.stateVersion = "25.05";

View file

@ -1,9 +1,14 @@
{ {
inputs, inputs,
lib,
... ...
}: }:
{ {
imports = [ "${inputs.self}/nix/profiles/nixos/khs-desktop.nix" ]; imports = [ "${inputs.self}/nix/profiles/nixos/khs-desktop.nix" ];
khscodes.networking.fqdn = "desktop.kaareskovgaard.net"; khscodes.networking.fqdn = "desktop.kaareskovgaard.net";
disko = lib.khscodes.disko-root-bios {
device = "/dev/sda";
diskName = "nixos";
};
system.stateVersion = "25.05"; system.stateVersion = "25.05";
} }

View file

@ -7,11 +7,10 @@
khscodes.infrastructure.khs-openstack-instance = { khscodes.infrastructure.khs-openstack-instance = {
enable = true; enable = true;
flavor = "m.medium"; flavor = "m.medium";
secretsSource = "vault";
}; };
snowfallorg.users.khs.admin = true; snowfallorg.users.khs.admin = true;
users.users.khs = { users.users.khs = {
initialPassword = "changeMe"; initialPassword = "changeme";
openssh.authorizedKeys.keys = [ 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==" "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=="
]; ];