From f7d4bef46c8107679a5b696cf53e3547cd398773 Mon Sep 17 00:00:00 2001 From: Kaare Hoff Skovgaard Date: Wed, 9 Jul 2025 15:12:11 +0200 Subject: [PATCH] Make some more changes to machine setup Work being done as an attempt to be able to create a small monitoring server --- .forgejo/workflows/push.yaml | 3 + flake.nix | 7 - .../home/khs/shell/nushell/default.nix | 1 - nix/modules/nixos/infrastructure/default.nix | 1 - nix/modules/nixos/networking/default.nix | 1 - nix/modules/nixos/networking/fqdn/default.nix | 2 + .../nixos/opentofu-openbao/default.nix | 1 - nix/modules/nixos/security/default.nix | 1 - nix/modules/nixos/services/default.nix | 8 - .../nixos/services/openssh/default.nix | 57 ++- .../nixos/services/vault-agent/default.nix | 187 ++++++++++ nix/modules/nixos/users/default.nix | 1 - nix/modules/nixos/virtualisation/default.nix | 1 - nix/packages/bw-opentofu/secrets-map.nix | 7 + nix/packages/pre-provisioning/default.nix | 2 +- rust/program/openbao-helper/src/enventry.rs | 108 ++++++ rust/program/openbao-helper/src/main.rs | 350 +++++------------- 17 files changed, 449 insertions(+), 289 deletions(-) delete mode 100644 nix/modules/nixos/infrastructure/default.nix delete mode 100644 nix/modules/nixos/networking/default.nix delete mode 100644 nix/modules/nixos/opentofu-openbao/default.nix delete mode 100644 nix/modules/nixos/security/default.nix delete mode 100644 nix/modules/nixos/services/default.nix create mode 100644 nix/modules/nixos/services/vault-agent/default.nix delete mode 100644 nix/modules/nixos/users/default.nix delete mode 100644 nix/modules/nixos/virtualisation/default.nix create mode 100644 rust/program/openbao-helper/src/enventry.rs diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index a2656dc..4f87af4 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -25,4 +25,7 @@ jobs: steps: - uses: actions/checkout@v4 - run: | + nix build --no-link '.#packages.x86_64-linux.terraform-provider-cloudflare' + 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' diff --git a/flake.nix b/flake.nix index 83f07df..72b1f8c 100644 --- a/flake.nix +++ b/flake.nix @@ -58,13 +58,6 @@ outputs = inputs@{ self, ... }: let - dirsInPath = - path: - let - files = builtins.readDir path; - dirs = inputs.nixpkgs.lib.filterAttrs (name: kind: kind == "directory") files; - in - builtins.attrNames dirs; inputNixosModules = [ inputs.disko.nixosModules.disko inputs.stylix.nixosModules.stylix diff --git a/nix/modules/home/khs/shell/nushell/default.nix b/nix/modules/home/khs/shell/nushell/default.nix index 8069729..e846379 100644 --- a/nix/modules/home/khs/shell/nushell/default.nix +++ b/nix/modules/home/khs/shell/nushell/default.nix @@ -1,7 +1,6 @@ { lib, config, - pkgs, ... }: let diff --git a/nix/modules/nixos/infrastructure/default.nix b/nix/modules/nixos/infrastructure/default.nix deleted file mode 100644 index c915eb0..0000000 --- a/nix/modules/nixos/infrastructure/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ ... }: { } diff --git a/nix/modules/nixos/networking/default.nix b/nix/modules/nixos/networking/default.nix deleted file mode 100644 index c915eb0..0000000 --- a/nix/modules/nixos/networking/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ ... }: { } diff --git a/nix/modules/nixos/networking/fqdn/default.nix b/nix/modules/nixos/networking/fqdn/default.nix index bc185cc..0689292 100644 --- a/nix/modules/nixos/networking/fqdn/default.nix +++ b/nix/modules/nixos/networking/fqdn/default.nix @@ -21,6 +21,8 @@ in { networking.hostName = lib.mkForce hostname; networking.domain = lib.mkForce domain; + # 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 = { "kernel.hostname" = cfg; }; diff --git a/nix/modules/nixos/opentofu-openbao/default.nix b/nix/modules/nixos/opentofu-openbao/default.nix deleted file mode 100644 index 32e84c8..0000000 --- a/nix/modules/nixos/opentofu-openbao/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ pkgs, ... }: { } diff --git a/nix/modules/nixos/security/default.nix b/nix/modules/nixos/security/default.nix deleted file mode 100644 index c915eb0..0000000 --- a/nix/modules/nixos/security/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ ... }: { } diff --git a/nix/modules/nixos/services/default.nix b/nix/modules/nixos/services/default.nix deleted file mode 100644 index 0e9f32f..0000000 --- a/nix/modules/nixos/services/default.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ ... }: -{ } -# let -# modules = lib.khscodes.dirsInPath ./.; -# in -# { -# imports = lib.lists.map (d: import d args) modules; -# } diff --git a/nix/modules/nixos/services/openssh/default.nix b/nix/modules/nixos/services/openssh/default.nix index a5090ba..56d8620 100644 --- a/nix/modules/nixos/services/openssh/default.nix +++ b/nix/modules/nixos/services/openssh/default.nix @@ -5,16 +5,55 @@ in { options.khscodes.services.openssh = { enable = lib.mkEnableOption "Enables openssh service for the instance"; - }; - - config = lib.mkIf cfg.enable { - services.openssh = { - enable = true; - settings = { - PasswordAuthentication = false; - PermitRootLogin = "no"; - KbdInteractiveAuthentication = false; + 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"; + default = [ ]; }; }; }; + + config = lib.mkIf cfg.enable ( + let + certificateNames = lib.lists.unique cfg.hostCertificate.hostNames; + hostCertificatEnable = cfg.hostCertificate.enable && cfg.hostCertificate.hostNames != [ ]; + in + { + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + KbdInteractiveAuthentication = false; + }; + extraConfig = lib.mkIf hostCertificatEnable '' + HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub + ''; + }; + khscodes.services.vault-agent = lib.mkIf hostCertificatEnable { + enable = true; + templates = [ + { + 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}" -}} + {{ .Data.signed_key }} + {{- end -}} + ''; + destination = "/etc/ssh/ssh_host_ed25519_key-cert.pub"; + perms = "0644"; + restartUnits = [ "sshd.service" ]; + } + ]; + }; + } + ); } diff --git a/nix/modules/nixos/services/vault-agent/default.nix b/nix/modules/nixos/services/vault-agent/default.nix new file mode 100644 index 0000000..bab6eb8 --- /dev/null +++ b/nix/modules/nixos/services/vault-agent/default.nix @@ -0,0 +1,187 @@ +{ + lib, + config, + pkgs, + ... +}: +let + cfg = config.input-output.openbao.agent; + secretIdFilePath = "/var/lib/vault-agent/secret-id"; + roleIdFilePath = "/var/lib/vault-agent/role-id"; + mkSubmodule = + { + options, + description, + }: + lib.types.submoduleWith { + description = description; + shorthandOnlyDefinesConfig = true; + modules = lib.toList { inherit options; }; + }; + restartUnits = + svcs: + lib.strings.concatStringsSep "\n" ( + lib.lists.map (svc: "systemctl restart ${lib.escapeShellArg svc}") svcs + ); + reloadOrRestartUnits = + svcs: + lib.strings.concatStringsSep "\n" ( + lib.lists.map (svc: "systemctl reload-or-restart ${lib.escapeShellArg svc}") svcs + ); + mapTemplate = + template: + let + command = lib.getExe ( + pkgs.writeShellApplication { + name = "restart-command"; + runtimeInputs = [ pkgs.systemd ]; + text = '' + ${restartUnits template.restartUnits} + ${reloadOrRestartUnits template.reloadOrRestartUnits} + ${template.exec} + ''; + meta = { + mainProgram = "restart-command"; + }; + } + ); + in + { + inherit (template) destination perms contents; + exec = { + command = command; + }; + }; + settings = { + vault = { + address = cfg.vault.address; + }; + auto_auth = { + method = [ + { + type = "approle"; + config = { + mount_path = "auth/approle"; + role_id_file_path = roleIdFilePath; + secret_id_file_path = secretIdFilePath; + remove_secret_id_file_after_reading = false; + }; + } + ]; + }; + + template_config = { + exit_on_retry_failure = true; + static_secret_render_interval = "60m"; + max_connections_per_host = 10; + leases_renewal_threshold = 0.5; + }; + template = lib.mkIf (cfg.templates != [ ]) (lib.lists.map mapTemplate cfg.templates); + }; + unitsDependsOnAgent = lib.lists.unique ( + lib.lists.flatten (lib.lists.map (t: t.restartUnits ++ t.reloadOrRestartUnits) cfg.templates) + ); +in +{ + options.khscodes.services.vault-agent = { + enable = lib.mkEnableOption "Enables the OpenBAO agent"; + package = lib.mkOption { + type = lib.types.package; + 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"; + }; + templates = lib.mkOption { + default = [ ]; + type = lib.types.listOf (mkSubmodule { + description = "List of templates to render"; + options = { + contents = lib.mkOption { + type = lib.types.str; + description = "Contents of the template (.ctmpl)"; + }; + destination = lib.mkOption { + type = lib.types.str; + description = "Destination file for the template"; + }; + restartUnits = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "List of systemd units to restart when template changes"; + default = [ ]; + }; + reloadOrRestartUnits = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "List of systemd units to reload-or-restart when template changes"; + default = [ ]; + }; + perms = lib.mkOption { + type = lib.types.str; + description = "Permissions of the generated file, by default will only be readable by root"; + default = "0600"; + }; + exec = lib.mkOption { + type = lib.types.lines; + default = ''''; + description = "Command to execute when template renders new data"; + }; + }; + }); + }; + }; + + config = lib.mkIf cfg.enable { + services.vault-agent.instances.openbao = { + inherit settings; + enable = true; + package = cfg.package; + user = "root"; + group = "root"; + }; + systemd.services."vault-agent-openbao" = { + before = unitsDependsOnAgent; + wantedBy = unitsDependsOnAgent; + unitConfig = { + ConditionPathExists = [ + secretIdFilePath + 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/users/default.nix b/nix/modules/nixos/users/default.nix deleted file mode 100644 index c915eb0..0000000 --- a/nix/modules/nixos/users/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ ... }: { } diff --git a/nix/modules/nixos/virtualisation/default.nix b/nix/modules/nixos/virtualisation/default.nix deleted file mode 100644 index c915eb0..0000000 --- a/nix/modules/nixos/virtualisation/default.nix +++ /dev/null @@ -1 +0,0 @@ -{ ... }: { } diff --git a/nix/packages/bw-opentofu/secrets-map.nix b/nix/packages/bw-opentofu/secrets-map.nix index 05d18df..8109b39 100644 --- a/nix/packages/bw-opentofu/secrets-map.nix +++ b/nix/packages/bw-opentofu/secrets-map.nix @@ -21,4 +21,11 @@ UNIFI_PASSWORD = "Terraform password"; UNIFI_API = "Terraform URL"; }; + "auth.kaareskovgaard.net" = { + "AUTHENTIK_TOKEN" = "Admin API Token"; + "TF_VAR_authentik_username" = "login.username"; + }; + "vault.kaareskovgaard.net" = { + "VAULT_TOKEN" = "Initial root token"; + }; } diff --git a/nix/packages/pre-provisioning/default.nix b/nix/packages/pre-provisioning/default.nix index 04232a1..1281025 100644 --- a/nix/packages/pre-provisioning/default.nix +++ b/nix/packages/pre-provisioning/default.nix @@ -15,7 +15,7 @@ pkgs.writeShellApplication { text = '' hostname="$1" cmd="''${2:-apply}" - baseAttr='${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.infrastructue.provisioning' + baseAttr='${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.infrastructure.provisioning' config="$(nix build --no-link --print-out-paths "''${baseAttr}.preConfig")" secretsSource="$(nix eval --raw "''${baseAttr}.pre.secretsSource")" endpoints="$(nix eval --json "''${baseAttr}.pre.endpoints")" diff --git a/rust/program/openbao-helper/src/enventry.rs b/rust/program/openbao-helper/src/enventry.rs new file mode 100644 index 0000000..0a38665 --- /dev/null +++ b/rust/program/openbao-helper/src/enventry.rs @@ -0,0 +1,108 @@ +use std::{collections::BTreeMap, marker::PhantomData, vec::IntoIter}; + +use serde::Deserialize; + +pub trait EnvEntryConfig { + const SECRETS: &'static [&'static str]; + const BAO_KEY: &'static str; +} + +pub struct EnvEntry(Vec<(&'static str, String)>, PhantomData); + +impl EnvEntry { + pub fn try_new_from_env() -> anyhow::Result { + let mut result = Vec::with_capacity(T::SECRETS.len()); + for key in T::SECRETS { + let value = common::env::read_env(key)?; + result.push((*key, value)); + } + Ok(Self(result, PhantomData)) + } + + fn new_from_values(values: Vec<(&'static str, String)>) -> Self { + Self(values, PhantomData) + } + + pub fn read_from_bao() -> anyhow::Result { + read_bao_data() + } +} + +impl From> for Vec<(&'static str, String)> { + fn from(value: EnvEntry) -> Self { + value.0 + } +} + +impl IntoIterator for EnvEntry { + type Item = (&'static str, String); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'de, T: EnvEntryConfig> serde::Deserialize<'de> for EnvEntry { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(EnvEntryVisitor(PhantomData)) + } +} +struct EnvEntryVisitor(PhantomData); + +impl<'de, T: EnvEntryConfig> serde::de::Visitor<'de> for EnvEntryVisitor { + type Value = EnvEntry; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_fmt(format_args!( + "a map with unique keys {} with string values", + T::SECRETS.join(", "), + )) + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut values = BTreeMap::<&'static str, String>::new(); + while let Some((key, value)) = map.next_entry::<&'de str, String>()? { + let mapped_key = T::SECRETS.iter().find(|n| **n == key).copied(); + let Some(key) = mapped_key else { + return Err(serde::de::Error::unknown_field(key, T::SECRETS)); + }; + if values.contains_key(key) { + return Err(serde::de::Error::duplicate_field(key)); + } + values.insert(key, value); + } + for key in T::SECRETS { + if !values.contains_key(key) { + return Err(serde::de::Error::missing_field(key)); + } + } + let values = values.into_iter().collect(); + let entry = EnvEntry::::new_from_values(values); + Ok(entry) + } +} + +#[derive(Debug, Deserialize)] +struct OpenBaoKvEntry { + data: OpenBaoKvEntryData, +} + +#[derive(Debug, Deserialize)] +struct OpenBaoKvEntryData { + data: T, +} + +fn read_bao_data() -> anyhow::Result> { + let mut cmd = common::proc::Command::new("bao"); + cmd.args(["kv", "get", "-format=json", "-mount=opentofu", T::BAO_KEY]); + let result: OpenBaoKvEntry> = cmd.try_spawn_to_json()?; + Ok(result.data.data) +} diff --git a/rust/program/openbao-helper/src/main.rs b/rust/program/openbao-helper/src/main.rs index 9f32de2..8b6ddb0 100644 --- a/rust/program/openbao-helper/src/main.rs +++ b/rust/program/openbao-helper/src/main.rs @@ -2,7 +2,10 @@ use std::{collections::BTreeSet, ffi::CString}; use anyhow::Context as _; use clap::{Parser, Subcommand}; -use serde::Deserialize; + +mod enventry; + +use enventry::*; fn main() { common::entrypoint(program); @@ -45,6 +48,10 @@ pub enum Endpoint { Hcloud, #[value(name = "unifi")] Unifi, + #[value(name = "vault")] + Vault, + #[value(name = "authentik")] + Authentik, } impl Endpoint { @@ -52,23 +59,31 @@ impl Endpoint { match self { Self::Openstack => { let data = OpenstackData::read_from_bao()?; - Ok(data.into_env_data()) + Ok(data.into()) } Self::Aws => { let data = AwsData::read_from_bao()?; - Ok(data.into_env_data()) + Ok(data.into()) } Self::Hcloud => { let data = HcloudData::read_from_bao()?; - Ok(data.into_env_data()) + Ok(data.into()) } Self::Cloudflare => { let data = CloudflareData::read_from_bao()?; - Ok(data.into_env_data()) + Ok(data.into()) } Self::Unifi => { let data = UnifiData::read_from_bao()?; - Ok(data.into_env_data()) + Ok(data.into()) + } + Self::Authentik => { + let data = AuthentikData::read_from_bao()?; + Ok(data.into()) + } + Self::Vault => { + let data = VaultData::read_from_bao()?; + Ok(data.into()) } } } @@ -82,267 +97,88 @@ fn program() -> anyhow::Result<()> { } } -#[derive(Debug, Deserialize)] -struct OpenBaoKvEntry { - data: OpenBaoKvEntryData, +macro_rules! entry_definition { + ($config_id:ident, $id: ident, $bao_key: expr, $secrets: expr) => { + struct $config_id; + + impl EnvEntryConfig for $config_id { + const SECRETS: &'static [&'static str] = $secrets; + const BAO_KEY: &'static str = $bao_key; + } + + type $id = EnvEntry<$config_id>; + }; } -#[derive(Debug, Deserialize)] -struct OpenBaoKvEntryData { - data: T, -} - -#[derive(Debug, Deserialize)] -struct OpenstackData { - username: String, - password: String, - tenant_name: String, - auth_url: String, - endpoint_type: String, - region: String, -} - -fn read_bao_data Deserialize<'de>>(key: &str) -> anyhow::Result { - let mut cmd = common::proc::Command::new("bao"); - cmd.args(["kv", "get", "-format=json", "-mount=opentofu", key]); - let result: OpenBaoKvEntry = cmd.try_spawn_to_json()?; - Ok(result.data.data) -} - -impl OpenstackData { - pub fn read_from_env() -> anyhow::Result { - let username = common::env::read_env("TF_VAR_openstack_username")?; - let password = common::env::read_env("TF_VAR_openstack_password")?; - let tenant_name = common::env::read_env("TF_VAR_openstack_tenant_name")?; - let auth_url = common::env::read_env("TF_VAR_openstack_auth_url")?; - let endpoint_type = common::env::read_env("TF_VAR_openstack_endpoint_type")?; - let region = common::env::read_env("TF_VAR_openstack_region")?; - Ok(Self { - username, - password, - tenant_name, - auth_url, - endpoint_type, - region, - }) - } - - pub fn read_from_bao() -> anyhow::Result { - let data = read_bao_data("openstack")?; - Ok(data) - } - - pub fn into_env_data(self) -> Vec<(&'static str, String)> { - vec![ - ("TF_VAR_openstack_username", self.username), - ("TF_VAR_openstack_password", self.password), - ("TF_VAR_openstack_tenant_name", self.tenant_name), - ("TF_VAR_openstack_auth_url", self.auth_url), - ("TF_VAR_openstack_endpoint_type", self.endpoint_type), - ("TF_VAR_openstack_region", self.region), - ] - } -} - -impl IntoIterator for OpenstackData { - type Item = (&'static str, String); - - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![ - ("username", self.username), - ("password", self.password), - ("tenant_name", self.tenant_name), - ("auth_url", self.auth_url), - ("endpoint_type", self.endpoint_type), - ("region", self.region), - ] - .into_iter() - } -} - -#[derive(Debug, Deserialize)] -struct CloudflareData { - token: String, - email: String, -} - -impl CloudflareData { - pub fn read_from_env() -> anyhow::Result { - let token = common::env::read_env("TF_VAR_cloudflare_token")?; - let email = common::env::read_env("TF_VAR_cloudflare_email")?; - Ok(Self { token, email }) - } - - pub fn read_from_bao() -> anyhow::Result { - let data = read_bao_data("cloudflare")?; - Ok(data) - } - - pub fn into_env_data(self) -> Vec<(&'static str, String)> { - vec![ - ("TF_VAR_cloudflare_token", self.token), - ("TF_VAR_cloudflare_email", self.email), - ] - } -} - -impl IntoIterator for CloudflareData { - type Item = (&'static str, String); - - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![("token", self.token), ("email", self.email)].into_iter() - } -} - -#[derive(Debug, Deserialize)] -struct AwsData { - key_id: String, - secret_access_key: String, -} - -impl AwsData { - pub fn read_from_env() -> anyhow::Result { - let key_id = common::env::read_env("AWS_ACCESS_KEY_ID")?; - let secret_access_key = common::env::read_env("AWS_SECRET_ACCESS_KEY")?; - Ok(Self { - key_id, - secret_access_key, - }) - } - - pub fn read_from_bao() -> anyhow::Result { - let data = read_bao_data("aws")?; - Ok(data) - } - - pub fn into_env_data(self) -> Vec<(&'static str, String)> { - vec![ - ("AWS_ACCESS_KEY_ID", self.key_id), - ("AWS_SECRET_ACCESS_KEY", self.secret_access_key), - ] - } -} - -impl IntoIterator for AwsData { - type Item = (&'static str, String); - - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![ - ("key_id", self.key_id), - ("secret_access_key", self.secret_access_key), - ] - .into_iter() - } -} - -#[derive(Debug, Deserialize)] -struct HcloudData { - api_token: String, -} - -impl HcloudData { - pub fn read_from_env() -> anyhow::Result { - let api_token = common::env::read_env("TF_VAR_hcloud_api_token")?; - Ok(Self { api_token }) - } - - pub fn read_from_bao() -> anyhow::Result { - let data = read_bao_data("hcloud")?; - Ok(data) - } - - pub fn into_env_data(self) -> Vec<(&'static str, String)> { - vec![("TF_VAR_hcloud_api_token", self.api_token)] - } -} - -impl IntoIterator for HcloudData { - type Item = (&'static str, String); - - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![("api_token", self.api_token)].into_iter() - } -} - -#[derive(Debug, Deserialize)] -struct UnifiData { - username: String, - password: String, - url: String, -} - -impl UnifiData { - pub fn read_from_env() -> anyhow::Result { - let username = common::env::read_env("UNIFI_USERNAME")?; - let password = common::env::read_env("UNIFI_PASSWORD")?; - let url = common::env::read_env("UNIFI_API")?; - Ok(Self { - username, - password, - url, - }) - } - - pub fn read_from_bao() -> anyhow::Result { - let data = read_bao_data("unifi")?; - Ok(data) - } - - pub fn into_env_data(self) -> Vec<(&'static str, String)> { - vec![ - ("UNIFI_USERNAME", self.username), - ("UNIFI_PASSWORD", self.password), - ("UNIFI_API", self.url), - ] - } -} - -impl IntoIterator for UnifiData { - type Item = (&'static str, String); - - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - vec![ - ("username", self.username), - ("password", self.password), - ("url", self.url), - ] - .into_iter() - } -} +entry_definition!( + OpenstackDataConfig, + OpenstackData, + "openstack", + &[ + "TF_VAR_openstack_username", + "TF_VAR_openstack_password", + "TF_VAR_openstack_tenant_name", + "TF_VAR_openstack_auth_url", + "TF_VAR_openstack_endpoint_type", + "TF_VAR_openstack_region" + ] +); +entry_definition!( + CloudflareDataConfig, + CloudflareData, + "cloudflare", + &["TF_VAR_cloudflare_token", "TF_VAR_cloudflare_email"] +); +entry_definition!( + AwsDataConfig, + AwsData, + "aws", + &["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] +); +entry_definition!( + HcloudDataConfig, + HcloudData, + "hcloud", + &["TF_VAR_hcloud_api_token"] +); +entry_definition!( + UnifiDataConfig, + UnifiData, + "unifi", + &["UNIFI_USERNAME", "UNIFI_PASSWORD", "UNIFI_API"] +); +entry_definition!(VaultDataConfig, VaultData, "vault", &["VAULT_TOKEN"]); +entry_definition!( + AuthentikDataConfig, + AuthentikData, + "authentik", + &["AUTHENTIK_TOKEN", "TF_VAR_authentik_username"] +); fn transfer() -> anyhow::Result<()> { - let openstack = OpenstackData::read_from_env()?; - let cloudflare = CloudflareData::read_from_env()?; - let aws = AwsData::read_from_env()?; - let hcloud = HcloudData::read_from_env()?; - let unifi = UnifiData::read_from_env()?; + let openstack = OpenstackData::try_new_from_env()?; + let cloudflare = CloudflareData::try_new_from_env()?; + let aws = AwsData::try_new_from_env()?; + let hcloud = HcloudData::try_new_from_env()?; + let unifi = UnifiData::try_new_from_env()?; + let authentik = AuthentikData::try_new_from_env()?; + let vault = VaultData::try_new_from_env()?; - write_kv_data("openstack", openstack)?; - write_kv_data("cloudflare", cloudflare)?; - write_kv_data("aws", aws)?; - write_kv_data("hcloud", hcloud)?; - write_kv_data("unifi", unifi)?; + write_kv_data(openstack)?; + write_kv_data(cloudflare)?; + write_kv_data(aws)?; + write_kv_data(hcloud)?; + write_kv_data(unifi)?; + write_kv_data(authentik)?; + write_kv_data(vault)?; Ok(()) } -fn write_kv_data( - key: &str, - data: impl IntoIterator, -) -> anyhow::Result<()> { +fn write_kv_data(entry: EnvEntry) -> anyhow::Result<()> { let mut cmd = common::proc::Command::new("bao"); cmd.args(["kv", "put", "-mount=opentofu"]); - cmd.arg(key); - for (key, value) in data { + cmd.arg(T::BAO_KEY); + for (key, value) in entry { cmd.arg(format!("{key}={value}")); } cmd.try_spawn_to_string()?;