Make some more changes to machine setup
Work being done as an attempt to be able to create a small monitoring server
This commit is contained in:
parent
89d410cb6c
commit
f7d4bef46c
17 changed files with 449 additions and 289 deletions
|
@ -25,4 +25,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- 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'
|
nix build --no-link '.#packages.x86_64-linux.terraform-provider-unifi'
|
||||||
|
|
|
@ -58,13 +58,6 @@
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{ self, ... }:
|
inputs@{ self, ... }:
|
||||||
let
|
let
|
||||||
dirsInPath =
|
|
||||||
path:
|
|
||||||
let
|
|
||||||
files = builtins.readDir path;
|
|
||||||
dirs = inputs.nixpkgs.lib.filterAttrs (name: kind: kind == "directory") files;
|
|
||||||
in
|
|
||||||
builtins.attrNames dirs;
|
|
||||||
inputNixosModules = [
|
inputNixosModules = [
|
||||||
inputs.disko.nixosModules.disko
|
inputs.disko.nixosModules.disko
|
||||||
inputs.stylix.nixosModules.stylix
|
inputs.stylix.nixosModules.stylix
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{ ... }: { }
|
|
|
@ -1 +0,0 @@
|
||||||
{ ... }: { }
|
|
|
@ -21,6 +21,8 @@ in
|
||||||
{
|
{
|
||||||
networking.hostName = lib.mkForce hostname;
|
networking.hostName = lib.mkForce hostname;
|
||||||
networking.domain = lib.mkForce domain;
|
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 = {
|
boot.kernel.sysctl = {
|
||||||
"kernel.hostname" = cfg;
|
"kernel.hostname" = cfg;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{ pkgs, ... }: { }
|
|
|
@ -1 +0,0 @@
|
||||||
{ ... }: { }
|
|
|
@ -1,8 +0,0 @@
|
||||||
{ ... }:
|
|
||||||
{ }
|
|
||||||
# let
|
|
||||||
# modules = lib.khscodes.dirsInPath ./.;
|
|
||||||
# in
|
|
||||||
# {
|
|
||||||
# imports = lib.lists.map (d: import d args) modules;
|
|
||||||
# }
|
|
|
@ -5,9 +5,27 @@ in
|
||||||
{
|
{
|
||||||
options.khscodes.services.openssh = {
|
options.khscodes.services.openssh = {
|
||||||
enable = lib.mkEnableOption "Enables openssh service for the instance";
|
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";
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable (
|
||||||
|
let
|
||||||
|
certificateNames = lib.lists.unique cfg.hostCertificate.hostNames;
|
||||||
|
hostCertificatEnable = cfg.hostCertificate.enable && cfg.hostCertificate.hostNames != [ ];
|
||||||
|
in
|
||||||
|
{
|
||||||
services.openssh = {
|
services.openssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
|
@ -15,6 +33,27 @@ in
|
||||||
PermitRootLogin = "no";
|
PermitRootLogin = "no";
|
||||||
KbdInteractiveAuthentication = false;
|
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" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
187
nix/modules/nixos/services/vault-agent/default.nix
Normal file
187
nix/modules/nixos/services/vault-agent/default.nix
Normal file
|
@ -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 <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}
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
{ ... }: { }
|
|
|
@ -1 +0,0 @@
|
||||||
{ ... }: { }
|
|
|
@ -21,4 +21,11 @@
|
||||||
UNIFI_PASSWORD = "Terraform password";
|
UNIFI_PASSWORD = "Terraform password";
|
||||||
UNIFI_API = "Terraform URL";
|
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";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ pkgs.writeShellApplication {
|
||||||
text = ''
|
text = ''
|
||||||
hostname="$1"
|
hostname="$1"
|
||||||
cmd="''${2:-apply}"
|
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")"
|
config="$(nix build --no-link --print-out-paths "''${baseAttr}.preConfig")"
|
||||||
secretsSource="$(nix eval --raw "''${baseAttr}.pre.secretsSource")"
|
secretsSource="$(nix eval --raw "''${baseAttr}.pre.secretsSource")"
|
||||||
endpoints="$(nix eval --json "''${baseAttr}.pre.endpoints")"
|
endpoints="$(nix eval --json "''${baseAttr}.pre.endpoints")"
|
||||||
|
|
108
rust/program/openbao-helper/src/enventry.rs
Normal file
108
rust/program/openbao-helper/src/enventry.rs
Normal file
|
@ -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<T>(Vec<(&'static str, String)>, PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: EnvEntryConfig> EnvEntry<T> {
|
||||||
|
pub fn try_new_from_env() -> anyhow::Result<Self> {
|
||||||
|
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<Self> {
|
||||||
|
read_bao_data()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<EnvEntry<T>> for Vec<(&'static str, String)> {
|
||||||
|
fn from(value: EnvEntry<T>) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for EnvEntry<T> {
|
||||||
|
type Item = (&'static str, String);
|
||||||
|
|
||||||
|
type IntoIter = IntoIter<Self::Item>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T: EnvEntryConfig> serde::Deserialize<'de> for EnvEntry<T> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_map(EnvEntryVisitor(PhantomData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct EnvEntryVisitor<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<'de, T: EnvEntryConfig> serde::de::Visitor<'de> for EnvEntryVisitor<T> {
|
||||||
|
type Value = EnvEntry<T>;
|
||||||
|
|
||||||
|
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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||||
|
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::<T>::new_from_values(values);
|
||||||
|
Ok(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct OpenBaoKvEntry<T> {
|
||||||
|
data: OpenBaoKvEntryData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct OpenBaoKvEntryData<T> {
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bao_data<T: EnvEntryConfig>() -> anyhow::Result<EnvEntry<T>> {
|
||||||
|
let mut cmd = common::proc::Command::new("bao");
|
||||||
|
cmd.args(["kv", "get", "-format=json", "-mount=opentofu", T::BAO_KEY]);
|
||||||
|
let result: OpenBaoKvEntry<EnvEntry<T>> = cmd.try_spawn_to_json()?;
|
||||||
|
Ok(result.data.data)
|
||||||
|
}
|
|
@ -2,7 +2,10 @@ use std::{collections::BTreeSet, ffi::CString};
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use serde::Deserialize;
|
|
||||||
|
mod enventry;
|
||||||
|
|
||||||
|
use enventry::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
common::entrypoint(program);
|
common::entrypoint(program);
|
||||||
|
@ -45,6 +48,10 @@ pub enum Endpoint {
|
||||||
Hcloud,
|
Hcloud,
|
||||||
#[value(name = "unifi")]
|
#[value(name = "unifi")]
|
||||||
Unifi,
|
Unifi,
|
||||||
|
#[value(name = "vault")]
|
||||||
|
Vault,
|
||||||
|
#[value(name = "authentik")]
|
||||||
|
Authentik,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
|
@ -52,23 +59,31 @@ impl Endpoint {
|
||||||
match self {
|
match self {
|
||||||
Self::Openstack => {
|
Self::Openstack => {
|
||||||
let data = OpenstackData::read_from_bao()?;
|
let data = OpenstackData::read_from_bao()?;
|
||||||
Ok(data.into_env_data())
|
Ok(data.into())
|
||||||
}
|
}
|
||||||
Self::Aws => {
|
Self::Aws => {
|
||||||
let data = AwsData::read_from_bao()?;
|
let data = AwsData::read_from_bao()?;
|
||||||
Ok(data.into_env_data())
|
Ok(data.into())
|
||||||
}
|
}
|
||||||
Self::Hcloud => {
|
Self::Hcloud => {
|
||||||
let data = HcloudData::read_from_bao()?;
|
let data = HcloudData::read_from_bao()?;
|
||||||
Ok(data.into_env_data())
|
Ok(data.into())
|
||||||
}
|
}
|
||||||
Self::Cloudflare => {
|
Self::Cloudflare => {
|
||||||
let data = CloudflareData::read_from_bao()?;
|
let data = CloudflareData::read_from_bao()?;
|
||||||
Ok(data.into_env_data())
|
Ok(data.into())
|
||||||
}
|
}
|
||||||
Self::Unifi => {
|
Self::Unifi => {
|
||||||
let data = UnifiData::read_from_bao()?;
|
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)]
|
macro_rules! entry_definition {
|
||||||
struct OpenBaoKvEntry<T> {
|
($config_id:ident, $id: ident, $bao_key: expr, $secrets: expr) => {
|
||||||
data: OpenBaoKvEntryData<T>,
|
struct $config_id;
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
impl EnvEntryConfig for $config_id {
|
||||||
struct OpenBaoKvEntryData<T> {
|
const SECRETS: &'static [&'static str] = $secrets;
|
||||||
data: T,
|
const BAO_KEY: &'static str = $bao_key;
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct OpenstackData {
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
tenant_name: String,
|
|
||||||
auth_url: String,
|
|
||||||
endpoint_type: String,
|
|
||||||
region: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_bao_data<T: for<'de> Deserialize<'de>>(key: &str) -> anyhow::Result<T> {
|
|
||||||
let mut cmd = common::proc::Command::new("bao");
|
|
||||||
cmd.args(["kv", "get", "-format=json", "-mount=opentofu", key]);
|
|
||||||
let result: OpenBaoKvEntry<T> = cmd.try_spawn_to_json()?;
|
|
||||||
Ok(result.data.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenstackData {
|
|
||||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
|
||||||
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<Self> {
|
type $id = EnvEntry<$config_id>;
|
||||||
let data = read_bao_data("openstack")?;
|
};
|
||||||
Ok(data)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_env_data(self) -> Vec<(&'static str, String)> {
|
entry_definition!(
|
||||||
vec![
|
OpenstackDataConfig,
|
||||||
("TF_VAR_openstack_username", self.username),
|
OpenstackData,
|
||||||
("TF_VAR_openstack_password", self.password),
|
"openstack",
|
||||||
("TF_VAR_openstack_tenant_name", self.tenant_name),
|
&[
|
||||||
("TF_VAR_openstack_auth_url", self.auth_url),
|
"TF_VAR_openstack_username",
|
||||||
("TF_VAR_openstack_endpoint_type", self.endpoint_type),
|
"TF_VAR_openstack_password",
|
||||||
("TF_VAR_openstack_region", self.region),
|
"TF_VAR_openstack_tenant_name",
|
||||||
|
"TF_VAR_openstack_auth_url",
|
||||||
|
"TF_VAR_openstack_endpoint_type",
|
||||||
|
"TF_VAR_openstack_region"
|
||||||
]
|
]
|
||||||
}
|
);
|
||||||
}
|
entry_definition!(
|
||||||
|
CloudflareDataConfig,
|
||||||
impl IntoIterator for OpenstackData {
|
CloudflareData,
|
||||||
type Item = (&'static str, String);
|
"cloudflare",
|
||||||
|
&["TF_VAR_cloudflare_token", "TF_VAR_cloudflare_email"]
|
||||||
type IntoIter = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
);
|
||||||
|
entry_definition!(
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
AwsDataConfig,
|
||||||
vec![
|
AwsData,
|
||||||
("username", self.username),
|
"aws",
|
||||||
("password", self.password),
|
&["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
||||||
("tenant_name", self.tenant_name),
|
);
|
||||||
("auth_url", self.auth_url),
|
entry_definition!(
|
||||||
("endpoint_type", self.endpoint_type),
|
HcloudDataConfig,
|
||||||
("region", self.region),
|
HcloudData,
|
||||||
]
|
"hcloud",
|
||||||
.into_iter()
|
&["TF_VAR_hcloud_api_token"]
|
||||||
}
|
);
|
||||||
}
|
entry_definition!(
|
||||||
|
UnifiDataConfig,
|
||||||
#[derive(Debug, Deserialize)]
|
UnifiData,
|
||||||
struct CloudflareData {
|
"unifi",
|
||||||
token: String,
|
&["UNIFI_USERNAME", "UNIFI_PASSWORD", "UNIFI_API"]
|
||||||
email: String,
|
);
|
||||||
}
|
entry_definition!(VaultDataConfig, VaultData, "vault", &["VAULT_TOKEN"]);
|
||||||
|
entry_definition!(
|
||||||
impl CloudflareData {
|
AuthentikDataConfig,
|
||||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
AuthentikData,
|
||||||
let token = common::env::read_env("TF_VAR_cloudflare_token")?;
|
"authentik",
|
||||||
let email = common::env::read_env("TF_VAR_cloudflare_email")?;
|
&["AUTHENTIK_TOKEN", "TF_VAR_authentik_username"]
|
||||||
Ok(Self { token, email })
|
);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
|
||||||
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 = <Vec<(&'static str, String)> 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<Self> {
|
|
||||||
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<Self> {
|
|
||||||
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 = <Vec<(&'static str, String)> 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<Self> {
|
|
||||||
let api_token = common::env::read_env("TF_VAR_hcloud_api_token")?;
|
|
||||||
Ok(Self { api_token })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
|
||||||
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 = <Vec<(&'static str, String)> 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<Self> {
|
|
||||||
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<Self> {
|
|
||||||
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 = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
vec![
|
|
||||||
("username", self.username),
|
|
||||||
("password", self.password),
|
|
||||||
("url", self.url),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transfer() -> anyhow::Result<()> {
|
fn transfer() -> anyhow::Result<()> {
|
||||||
let openstack = OpenstackData::read_from_env()?;
|
let openstack = OpenstackData::try_new_from_env()?;
|
||||||
let cloudflare = CloudflareData::read_from_env()?;
|
let cloudflare = CloudflareData::try_new_from_env()?;
|
||||||
let aws = AwsData::read_from_env()?;
|
let aws = AwsData::try_new_from_env()?;
|
||||||
let hcloud = HcloudData::read_from_env()?;
|
let hcloud = HcloudData::try_new_from_env()?;
|
||||||
let unifi = UnifiData::read_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(openstack)?;
|
||||||
write_kv_data("cloudflare", cloudflare)?;
|
write_kv_data(cloudflare)?;
|
||||||
write_kv_data("aws", aws)?;
|
write_kv_data(aws)?;
|
||||||
write_kv_data("hcloud", hcloud)?;
|
write_kv_data(hcloud)?;
|
||||||
write_kv_data("unifi", unifi)?;
|
write_kv_data(unifi)?;
|
||||||
|
write_kv_data(authentik)?;
|
||||||
|
write_kv_data(vault)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_kv_data(
|
fn write_kv_data<T: EnvEntryConfig>(entry: EnvEntry<T>) -> anyhow::Result<()> {
|
||||||
key: &str,
|
|
||||||
data: impl IntoIterator<Item = (&'static str, String)>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let mut cmd = common::proc::Command::new("bao");
|
let mut cmd = common::proc::Command::new("bao");
|
||||||
cmd.args(["kv", "put", "-mount=opentofu"]);
|
cmd.args(["kv", "put", "-mount=opentofu"]);
|
||||||
cmd.arg(key);
|
cmd.arg(T::BAO_KEY);
|
||||||
for (key, value) in data {
|
for (key, value) in entry {
|
||||||
cmd.arg(format!("{key}={value}"));
|
cmd.arg(format!("{key}={value}"));
|
||||||
}
|
}
|
||||||
cmd.try_spawn_to_string()?;
|
cmd.try_spawn_to_string()?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue