diff --git a/.credentials/admin-environment.age b/.credentials/admin-environment.age new file mode 100644 index 0000000..bcd8f7e Binary files /dev/null and b/.credentials/admin-environment.age differ diff --git a/.credentials/secrets.nix b/.credentials/secrets.nix new file mode 100644 index 0000000..c899d92 --- /dev/null +++ b/.credentials/secrets.nix @@ -0,0 +1,6 @@ +let + keys = import ../keys; +in +{ + "admin-environment.age".publicKeys = keys.rootKeys; +} diff --git a/.gitignore b/.gitignore index d50eeb8..d85129f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,9 @@ result-* *.qcow2 .gcroots .pre-commit-config.yaml + +# Ignore Terraform configuration file +config.tf.json + +# Ignore Terraform stuff +.terraform diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..2e96052 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,38 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/numtide/secret" { + version = "1.2.1" + constraints = "~> 1.2.1" + hashes = [ + "h1:t2z3CjxVsXjKb3g59WGkLtvDIR4NzLU7UFEcyAgF2C0=", + "zh:17cbc7f3b90ee2b3ae5adfc3bd9cb70166a5ffbd8e642e64afa7cb0e32a34bae", + "zh:5d66ce2aea25fc3c12cec6fc569b8ff314df6d773b9c3449983a4e9cde8347c7", + "zh:67d02e96bf0d07f2fcf16ce9427a7a26f53e695676405d0c2b815808f950411d", + "zh:77c3c05681ce199e6b0e2e5a2dfe418f61ae8863d527e7a7d47a9699d912683b", + "zh:7f37e633b4f94ba9f347cfe68d44f80fe066188feb954b13ee0f621caae4121d", + "zh:ea16bbe494c6ddd0af7bbea9554474c387517db4e7f0d15513bb29ff893871bc", + ] +} + +provider "registry.opentofu.org/raitobezarius/garage" { + version = "1.0.3" + constraints = "~> 1.0.3" + hashes = [ + "h1:QKbZcU7u9OG1t/h4S3+pXS3sOUfVMmfLTiYh5L5j1rE=", + "zh:04f220a2baf4bd1bae07888a1c311cacd6076c209de83adbe573525fc50f2ea4", + "zh:078938d5fa07e024d779c664823427af28935bbeb77e0ff940bac3e7bc41f1e8", + "zh:2dd58a2d82094a1b07ff1b6de57e4a0d96e1f20abecd4f70a6469079b46b76d9", + "zh:325da7a74b1c84f934b38134d7c419253292aeed6f6836a2fb37f42d13a8ff67", + "zh:3ca9230ef87e70691b24fd83d40bb5b6a08f0b91ab26cbb2e692f92155b6d179", + "zh:45ef683a18a5053c93c691d08f3903fd4918467dfa056b1c274207de8a6aeb74", + "zh:4c9ee6c34b07c209c5daf1e9ff182f828667e54a90a683bc11cdcea86e4f8ef7", + "zh:5f0bb6524b2fffa606e0e3585af93dfc31b611c7abf55e4371ae5fc36e85972c", + "zh:7a3495dc211164c7d4042769c20d7111c767d0fd5908742e0766281c70d7d184", + "zh:7ce79867cdd4b1f7028da811cd5cb271a46820c79c0328a1221dd3bb6215c631", + "zh:93278861ee6bcb64e23bd1268f79b02035fba4fca0a98607a98f46abf8dfdf83", + "zh:937e681beea8b0dd899557f2a194c8128bd8810417ff04954bc9958ff826e980", + "zh:cae6e1598dd32f23f3900c41e50a6ece7d9456dbd033d855bb238ac21539d67b", + "zh:f6f7556ba7d5578604290170a709e00140be6d7f8a510a20bce49a9a23d75e5f", + ] +} diff --git a/default.nix b/default.nix index 414feb8..cb1ec95 100644 --- a/default.nix +++ b/default.nix @@ -67,9 +67,18 @@ let commitizen.enable = true; }; }; + + terranixConfig = import "${sources.terranix}/core" { + inherit pkgs; + strip_nulls = true; + terranix_config.imports = [ ./terranix ]; + }; + terranixConfigFile = (pkgs.formats.json { }).generate "config.tf.json" terranixConfig.config; in { + inherit terranixConfigFile terranixConfig; + nodes = builtins.mapAttrs ( host: { site, ... }: "${host}.${site}.infra.dgnum.eu" ) (import ./meta/nodes.nix); @@ -83,11 +92,36 @@ in name = "dgnum-infra"; packages = [ + (pkgs.writeShellScriptBin "tf" '' + set -eo pipefail + ln -snf ${terranixConfigFile} config.tf.json + exec ${pkgs.lib.getExe pkgs.opentofu} "$@" + '') + (pkgs.writeShellScriptBin "decryptAndSourceEnvironment" '' + set -eo pipefail + + # TODO: don't hardcode me. + SECRET_FILE=".credentials/admin-environment.age" + IDENTITIES=() + for identity in [ "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_rsa" ]; do + test -r "$identity" || continue + IDENTITIES+=(-i) + IDENTITIES+=("$identity") + done + + test "''${#IDENTITIES[@]}" -eq 0 && echo "[agenix-shell] WARNING: no readable identities found!" + + test -f "$SECRET_FILE" || echo "[agenix-shell] WARNING: encrypted environment file $SECRET_FILE not found!" + export eval $(${pkgs.lib.getExe pkgs.rage} --decrypt "''${IDENTITIES[@]}" -o - $SECRET_FILE) + + echo "[agenix-shell] Repository-wide secrets loaded in the environment." + '') (pkgs.nixos-generators.overrideAttrs (_: { version = "1.8.0-unstable"; src = builtins.storePath sources.nixos-generators; })) pkgs.npins + pkgs.rage (pkgs.callPackage ./lib/colmena { inherit (nix-pkgs) colmena; }) (pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { }) @@ -97,6 +131,8 @@ in shellHook = '' ${git-checks.shellHook} + # If we want to export these environments, we need to source it, not call it. + source $(which decryptAndSourceEnvironment) ''; preferLocalBuild = true; diff --git a/npins/sources.json b/npins/sources.json index fa089ef..ee03e5b 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -300,6 +300,21 @@ "url": null, "hash": "11vvfxw2sznc155x0xlgl00g6n9sr90xa0b1hr14vchg7gkz46r5" }, + "terranix": { + "type": "GitRelease", + "repository": { + "type": "GitHub", + "owner": "terranix", + "repo": "terranix" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "version": "2.7.0", + "revision": "00710f39f38a0a654a2c4fd96cbb988b4f4cedfa", + "url": "https://api.github.com/repos/terranix/terranix/tarball/2.7.0", + "hash": "1wsyhsdsjw6xlhpkhaqvia3x0na3nx2vamcb2rbcbdmb7ra1y9f6" + }, "wp4nix": { "type": "Git", "repository": { diff --git a/terranix/common.nix b/terranix/common.nix new file mode 100644 index 0000000..0e8b0be --- /dev/null +++ b/terranix/common.nix @@ -0,0 +1,7 @@ +{ + # Until we get some kind of KMS operational, store secrets in the state file. + terraform.required_providers.secret = { + version = "~> 1.2.1"; + source = "numtide/secret"; + }; +} diff --git a/terranix/default.nix b/terranix/default.nix new file mode 100644 index 0000000..b6ff81e --- /dev/null +++ b/terranix/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./common.nix + ./state.nix + ]; +} diff --git a/terranix/state.nix b/terranix/state.nix new file mode 100644 index 0000000..ef2f7d5 --- /dev/null +++ b/terranix/state.nix @@ -0,0 +1,21 @@ +{ + # We use terraform.backend.s3 directly instead of the type-checked Terranix + # backend.s3 options. The latter does not support setting arbitrary s3 + # endpoints. + # + # Note: currently requires the user to provide AWS_ACCESS_KEY_ID as well as + # AWS_SECRET_ACCESS_KEY in their environment variables. + + terraform.backend.s3 = { + endpoints.s3 = "s3.dgnum.eu"; + region = "garage"; + bucket = "monorepo-terraform-state"; + key = "state"; + + # It's just a dumb Garage server, don't try to be smart. + skip_credentials_validation = true; + skip_region_validation = true; + skip_requesting_account_id = true; + skip_metadata_api_check = true; + }; +}