Begin adding support for using opentofu through openbao secrets
This commit is contained in:
parent
8e31f30762
commit
e61b3b06f3
12 changed files with 551 additions and 39 deletions
|
@ -229,12 +229,10 @@ in
|
||||||
khscodes.provisioning.pre = {
|
khscodes.provisioning.pre = {
|
||||||
modules = modules;
|
modules = modules;
|
||||||
secretsSource = cfg.secretsSource;
|
secretsSource = cfg.secretsSource;
|
||||||
variablesNeeded = [
|
endspoints = [
|
||||||
"TF_VAR_cloudflare_token"
|
"aws"
|
||||||
"TF_VAR_cloudflare_email"
|
"cloudflare"
|
||||||
"AWS_ACCESS_KEY_ID"
|
"hcloud"
|
||||||
"AWS_SECRET_ACCESS_KEY"
|
|
||||||
"TF_VAR_hcloud_api_token"
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,17 @@ let
|
||||||
description = "Where to get the secrets for the provisioning from";
|
description = "Where to get the secrets for the provisioning from";
|
||||||
default = "vault";
|
default = "vault";
|
||||||
};
|
};
|
||||||
variablesNeeded = lib.mkOption {
|
endspoints = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf (
|
||||||
description = "Needed environment variables for the provisioning";
|
lib.types.enum [
|
||||||
|
"openstack"
|
||||||
|
"aws"
|
||||||
|
"unifi"
|
||||||
|
"hcloud"
|
||||||
|
"cloudflare"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
description = "Needed endpoints to be used during provisioning";
|
||||||
default = [ ];
|
default = [ ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
19
nix/packages/bitwarden-to-vault/default.nix
Normal file
19
nix/packages/bitwarden-to-vault/default.nix
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{ pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
script = pkgs.writeShellApplication {
|
||||||
|
name = "bitwarden-to-vault-wrapped";
|
||||||
|
meta = {
|
||||||
|
mainProgram = "bitwarden-to-vault-wrapped";
|
||||||
|
};
|
||||||
|
runtimeInputs = [ pkgs.khscodes.openbao-helper ];
|
||||||
|
text = ''
|
||||||
|
openbao-helper transfer
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.khscodes.mkBwEnv {
|
||||||
|
inherit pkgs;
|
||||||
|
name = "bitwarden-to-vault";
|
||||||
|
items = import ../bw-opentofu/secrets-map.nix;
|
||||||
|
exe = lib.getExe script;
|
||||||
|
}
|
|
@ -4,25 +4,7 @@ let
|
||||||
# TODO: We should figure out a way of passing the secrets map at runtime instead of build time.
|
# TODO: We should figure out a way of passing the secrets map at runtime instead of build time.
|
||||||
# for now this map just needs to include every secret we could need, which also makes the reading of secrets take way longer than
|
# for now this map just needs to include every secret we could need, which also makes the reading of secrets take way longer than
|
||||||
# needed.
|
# needed.
|
||||||
secrets = {
|
secrets = import ./secrets-map.nix;
|
||||||
"KHS Openstack" = {
|
|
||||||
TF_VAR_openstack_username = "login.username";
|
|
||||||
TF_VAR_openstack_password = "login.password";
|
|
||||||
TF_VAR_openstack_tenant_name = "Project Name";
|
|
||||||
TF_VAR_openstack_auth_url = "Auth URL";
|
|
||||||
TF_VAR_openstack_endpoint_type = "Interface";
|
|
||||||
TF_VAR_openstack_region = "Region Name";
|
|
||||||
};
|
|
||||||
"Cloudflare" = {
|
|
||||||
TF_VAR_cloudflare_token = "DNS API Token";
|
|
||||||
TF_VAR_cloudflare_email = "login.username";
|
|
||||||
AWS_ACCESS_KEY_ID = "BW Terraform access key id";
|
|
||||||
AWS_SECRET_ACCESS_KEY = "BW Terraform secret access key";
|
|
||||||
};
|
|
||||||
"Hetzner Cloud" = {
|
|
||||||
TF_VAR_hcloud_api_token = "Terraform API Token";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
wrappedScript = pkgs.writeShellApplication {
|
wrappedScript = pkgs.writeShellApplication {
|
||||||
name = "bw-opentofu-wrapped";
|
name = "bw-opentofu-wrapped";
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
|
|
24
nix/packages/bw-opentofu/secrets-map.nix
Normal file
24
nix/packages/bw-opentofu/secrets-map.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"KHS Openstack" = {
|
||||||
|
TF_VAR_openstack_username = "login.username";
|
||||||
|
TF_VAR_openstack_password = "login.password";
|
||||||
|
TF_VAR_openstack_tenant_name = "Project Name";
|
||||||
|
TF_VAR_openstack_auth_url = "Auth URL";
|
||||||
|
TF_VAR_openstack_endpoint_type = "Interface";
|
||||||
|
TF_VAR_openstack_region = "Region Name";
|
||||||
|
};
|
||||||
|
"Cloudflare" = {
|
||||||
|
TF_VAR_cloudflare_token = "DNS API Token";
|
||||||
|
TF_VAR_cloudflare_email = "login.username";
|
||||||
|
AWS_ACCESS_KEY_ID = "BW Terraform access key id";
|
||||||
|
AWS_SECRET_ACCESS_KEY = "BW Terraform secret access key";
|
||||||
|
};
|
||||||
|
"Hetzner Cloud" = {
|
||||||
|
TF_VAR_hcloud_api_token = "Terraform API Token";
|
||||||
|
};
|
||||||
|
"Ubiquiti" = {
|
||||||
|
TF_VAR_unifi_username = "Terraform username";
|
||||||
|
TF_VAR_unifi_password = "Terraform password";
|
||||||
|
TF_VAR_unifi_url = "Terraform URL";
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,4 +3,11 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
inputs,
|
||||||
}:
|
}:
|
||||||
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage "hetzner-static-ip"
|
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage {
|
||||||
|
crateName = "hetzner-static-ip";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.curl
|
||||||
|
pkgs.uutils-coreutils-noprefix
|
||||||
|
pkgs.iproute2
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
19
nix/packages/openbao-helper/default.nix
Normal file
19
nix/packages/openbao-helper/default.nix
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
}:
|
||||||
|
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage {
|
||||||
|
crateName = "openbao-helper";
|
||||||
|
# Not replacing path for openbao helper as it will execve other processes.
|
||||||
|
# Ideally I would like to not touch this process' path at all, perhaps by
|
||||||
|
# placing a file along with the compiled binary listing where the programs are located
|
||||||
|
# such that no tampering of the ENV can take place. But doing it this way at least
|
||||||
|
# it will just suffix the paths.
|
||||||
|
replacePath = false;
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.curl
|
||||||
|
pkgs.uutils-coreutils-noprefix
|
||||||
|
pkgs.openbao
|
||||||
|
];
|
||||||
|
}
|
49
rust/Cargo.lock
generated
49
rust/Cargo.lock
generated
|
@ -73,6 +73,24 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.40"
|
version = "4.5.40"
|
||||||
|
@ -244,6 +262,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.174"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libyml"
|
name = "libyml"
|
||||||
version = "0.0.5"
|
version = "0.0.5"
|
||||||
|
@ -266,12 +290,37 @@ version = "2.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.30.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openbao-helper"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"common",
|
||||||
|
"hakari",
|
||||||
|
"log",
|
||||||
|
"nix",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
|
|
@ -21,6 +21,7 @@ clap = { version = "4.5.39", default-features = false, features = [
|
||||||
"derive",
|
"derive",
|
||||||
] }
|
] }
|
||||||
log = { version = "0.4.27", default-features = false, features = ["std"] }
|
log = { version = "0.4.27", default-features = false, features = ["std"] }
|
||||||
|
nix = { version = "0.30.1", default-features = false, features = ["process"] }
|
||||||
serde = { version = "1.0.219", default-features = false, features = [
|
serde = { version = "1.0.219", default-features = false, features = [
|
||||||
"derive",
|
"derive",
|
||||||
"std",
|
"std",
|
||||||
|
|
|
@ -33,7 +33,11 @@ let
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
buildRustPackage =
|
buildRustPackage =
|
||||||
crateName:
|
{
|
||||||
|
crateName,
|
||||||
|
runtimeInputs,
|
||||||
|
replacePath ? false,
|
||||||
|
}:
|
||||||
craneLib.buildPackage (
|
craneLib.buildPackage (
|
||||||
individualCrateArgs
|
individualCrateArgs
|
||||||
// {
|
// {
|
||||||
|
@ -41,14 +45,14 @@ in
|
||||||
cargoExtraArgs = "-p ${crateName}";
|
cargoExtraArgs = "-p ${crateName}";
|
||||||
src = fileSetForCrate crateName;
|
src = fileSetForCrate crateName;
|
||||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
postFixup = ''
|
postFixup =
|
||||||
wrapProgram $out/bin/${crateName} --set PATH "${
|
if replacePath then
|
||||||
lib.makeBinPath [
|
''
|
||||||
pkgs.curl
|
wrapProgram $out/bin/${crateName} --set PATH "${lib.makeBinPath runtimeInputs}"
|
||||||
pkgs.uutils-coreutils-noprefix
|
''
|
||||||
pkgs.iproute2
|
else
|
||||||
]
|
''
|
||||||
}"
|
wrapProgram $out/bin/${crateName} --suffix PATH : "${lib.makeBinPath runtimeInputs}"
|
||||||
'';
|
'';
|
||||||
meta = {
|
meta = {
|
||||||
mainProgram = crateName;
|
mainProgram = crateName;
|
||||||
|
|
14
rust/program/openbao-helper/Cargo.toml
Normal file
14
rust/program/openbao-helper/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "openbao-helper"
|
||||||
|
edition = "2024"
|
||||||
|
version = "1.0.0"
|
||||||
|
metadata.crane.name = "openbao-helper"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
clap = { workspace = true }
|
||||||
|
common = { path = "../../lib/common" }
|
||||||
|
log = { workspace = true }
|
||||||
|
nix = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
hakari = { version = "0.1", path = "../../lib/hakari" }
|
387
rust/program/openbao-helper/src/main.rs
Normal file
387
rust/program/openbao-helper/src/main.rs
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
use std::{collections::BTreeSet, ffi::CString};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
common::entrypoint(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// Transfers secrets from the current environment into their respective secrets in openbao
|
||||||
|
Transfer,
|
||||||
|
/// Wraps the program by reading the specified data from bao and setting respective environment variables
|
||||||
|
WrapProgram(WrapProgram),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, clap::Args)]
|
||||||
|
pub struct WrapProgram {
|
||||||
|
/// The endpoints to fetch,
|
||||||
|
#[arg(short = 'e', long = "endpoint", number_of_values = 1)]
|
||||||
|
pub endpoint: Vec<Endpoint>,
|
||||||
|
/// Command to wrap
|
||||||
|
#[arg(allow_hyphen_values = true, last = true)]
|
||||||
|
pub cmd: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
||||||
|
pub enum Endpoint {
|
||||||
|
#[value(name = "openstack")]
|
||||||
|
Openstack,
|
||||||
|
#[value(name = "cloudflare")]
|
||||||
|
Cloudflare,
|
||||||
|
#[value(name = "aws")]
|
||||||
|
Aws,
|
||||||
|
#[value(name = "hcloud")]
|
||||||
|
Hcloud,
|
||||||
|
#[value(name = "unifi")]
|
||||||
|
Unifi,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint {
|
||||||
|
pub fn try_into_env_data(self) -> anyhow::Result<Vec<(&'static str, String)>> {
|
||||||
|
match self {
|
||||||
|
Self::Openstack => {
|
||||||
|
let data = OpenstackData::read_from_bao()?;
|
||||||
|
Ok(data.into_env_data())
|
||||||
|
}
|
||||||
|
Self::Aws => {
|
||||||
|
let data = AwsData::read_from_bao()?;
|
||||||
|
Ok(data.into_env_data())
|
||||||
|
}
|
||||||
|
Self::Hcloud => {
|
||||||
|
let data = HcloudData::read_from_bao()?;
|
||||||
|
Ok(data.into_env_data())
|
||||||
|
}
|
||||||
|
Self::Cloudflare => {
|
||||||
|
let data = CloudflareData::read_from_bao()?;
|
||||||
|
Ok(data.into_env_data())
|
||||||
|
}
|
||||||
|
Self::Unifi => {
|
||||||
|
let data = UnifiData::read_from_bao()?;
|
||||||
|
Ok(data.into_env_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn program() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
match args.command {
|
||||||
|
Commands::Transfer => transfer(),
|
||||||
|
Commands::WrapProgram(w) => wrap_program(w),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct OpenBaoKvEntry<T> {
|
||||||
|
data: OpenBaoKvEntryData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct OpenBaoKvEntryData<T> {
|
||||||
|
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<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> {
|
||||||
|
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 = <Vec<(&'static str, String)> 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<Self> {
|
||||||
|
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<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("TF_VAR_unifi_username")?;
|
||||||
|
let password = common::env::read_env("TF_VAR_unifi_password")?;
|
||||||
|
let url = common::env::read_env("TF_VAR_unifi_url")?;
|
||||||
|
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![
|
||||||
|
("TF_VAR_unifi_username", self.username),
|
||||||
|
("TF_VAR_unifi_password", self.password),
|
||||||
|
("TF_VAR_unifi_url", 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<()> {
|
||||||
|
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()?;
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_kv_data(
|
||||||
|
key: &str,
|
||||||
|
data: impl IntoIterator<Item = (&'static str, String)>,
|
||||||
|
) -> 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(format!("{key}={value}"));
|
||||||
|
}
|
||||||
|
cmd.try_spawn_to_string()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_program(wrap_program: WrapProgram) -> anyhow::Result<()> {
|
||||||
|
let (args, env) = {
|
||||||
|
let WrapProgram { cmd, endpoint } = wrap_program;
|
||||||
|
if endpoint.is_empty() {
|
||||||
|
return Err(anyhow::format_err!("Must specify at least one endpoint"));
|
||||||
|
}
|
||||||
|
if cmd.is_empty() {
|
||||||
|
return Err(anyhow::format_err!("No command to execute was specified"));
|
||||||
|
}
|
||||||
|
let unique: BTreeSet<_> = BTreeSet::from_iter(endpoint);
|
||||||
|
let mut env = Vec::<CString>::new();
|
||||||
|
for (key, value) in std::env::vars() {
|
||||||
|
env.push(
|
||||||
|
CString::new(format!("{key}={value}"))
|
||||||
|
.with_context(|| format!("Environment variable {key} contained a null byte"))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for env_set in unique {
|
||||||
|
for (key, value) in env_set.try_into_env_data()? {
|
||||||
|
env.push(CString::new(format!("{key}={value}")).with_context(|| {
|
||||||
|
format!("Environment variable {key} contained a null byte")
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut args = Vec::new();
|
||||||
|
for arg in cmd {
|
||||||
|
let arg = CString::new(arg)
|
||||||
|
.context("Argument to program to wrap cannot contain null bytes")?;
|
||||||
|
args.push(arg);
|
||||||
|
}
|
||||||
|
(args, env)
|
||||||
|
};
|
||||||
|
nix::unistd::execvpe(&args[0], args.as_slice(), env.as_slice())?;
|
||||||
|
// This will never get executed
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue