summaryrefslogtreecommitdiff
path: root/secrets
diff options
context:
space:
mode:
Diffstat (limited to 'secrets')
-rw-r--r--secrets/agenix-configuration.nix25
-rw-r--r--secrets/eval-agenix.nix13
-rw-r--r--secrets/module.nix210
-rw-r--r--secrets/secrets.nix41
-rw-r--r--secrets/toSecrets.nix35
5 files changed, 251 insertions, 73 deletions
diff --git a/secrets/agenix-configuration.nix b/secrets/agenix-configuration.nix
new file mode 100644
index 0000000..d093d4e
--- /dev/null
+++ b/secrets/agenix-configuration.nix
@@ -0,0 +1,25 @@
+{ config, lib, ... }:
+
+{
+ rootDirectory = ./.;
+
+ recipients = {
+ # Catch-all
+ default = [ config.recipients.getchoo ];
+
+ # Users
+ getchoo = "age1zyqu6zkvl0rmlejhm5auzmtflfy4pa0fzwm0nzy737fqrymr7crsqrvnhs";
+
+ # Machines
+ atlas = "age18eu3ya4ucd2yzdrpkpg7wrymrxewt8j3zj2p2rqgcjeruacp0dgqryp39z";
+ glados = "age1n7tyxx63wpgnmwkzn7dmkm62jxel840rk3ye3vsultrszsfrwuzsawdzhq";
+ glados-wsl = "age1ffqfq3azqfwxwtxnfuzzs0y566a7ydgxce4sqxjqzw8yexc2v4yqfr55vr";
+ };
+
+ secrets = lib.mapAttrsToList (hostname: pubkey: {
+ regex = "^${hostname}\/.*\.age$";
+ recipients = {
+ ${hostname} = pubkey;
+ };
+ }) { inherit (config.recipients) atlas glados glados-wsl; };
+}
diff --git a/secrets/eval-agenix.nix b/secrets/eval-agenix.nix
new file mode 100644
index 0000000..f02577f
--- /dev/null
+++ b/secrets/eval-agenix.nix
@@ -0,0 +1,13 @@
+let
+ lib = import <nixpkgs/lib>;
+in
+
+args:
+
+lib.evalModules (
+ {
+ modules = args.modules ++ [ ./module.nix ];
+ class = "agenix";
+ }
+ // lib.removeAttrs args [ "modules" ]
+)
diff --git a/secrets/module.nix b/secrets/module.nix
new file mode 100644
index 0000000..2244d15
--- /dev/null
+++ b/secrets/module.nix
@@ -0,0 +1,210 @@
+{ config, lib, ... }:
+
+let
+ inherit (lib)
+ attrValues
+ filter
+ flip
+ match
+ mkOption
+ recursiveUpdate
+ removeAttrs
+ removePrefix
+ types
+ ;
+
+ inherit (lib.filesystem) listFilesRecursive;
+
+ cfg = config;
+
+ toRelativePath = filePath: removePrefix (toString cfg.rootDirectory + "/") (toString filePath);
+
+ handleSecretRegex =
+ secret:
+
+ let
+ secretRegexMatches = str: match secret.regex str != null;
+ matched = filter (filePath: secretRegexMatches (toRelativePath filePath)) secretFiles;
+ in
+
+ map (
+ path:
+ recursiveUpdate secret {
+ path = toRelativePath path;
+ }
+ ) matched;
+
+ secretFiles = listFilesRecursive cfg.rootDirectory;
+
+ failedAssertions = map (x: x.message) (filter (x: !x.assertion) cfg.assertions);
+ assertionsMessage = "\nFailed assertions:\n${lib.concatLines (map (x: "- " + x) failedAssertions)}";
+
+ agenixSecretSubmodule = {
+ freeformType = lib.types.attrsOf types.anything;
+
+ options = {
+ publicKeys = mkOption {
+ type = types.listOf types.str;
+ defaultText = "config.recipients.default";
+ description = "List of public keys a given secret is encrypted for.";
+ };
+ };
+ };
+
+ recipientsSubmodule = {
+ freeformType = types.attrsOf types.str;
+
+ options = {
+ default = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "Recipetents added to secrets by default.";
+ };
+ };
+ };
+
+ recipientsOptionSubmodule = {
+ options = {
+ recipients = mkOption {
+ type = types.submodule recipientsSubmodule;
+ default = { };
+ description = "Recipetents that files will be encrypted for.";
+ };
+ };
+ };
+
+ secretSettingsSubmodule =
+ { config, ... }:
+
+ {
+ imports = [ recipientsOptionSubmodule ];
+
+ options = {
+ recipients = mkOption {
+ # We only use this in the toplevel `config.recipients`
+ apply = flip removeAttrs [ "default" ];
+ };
+
+ useDefault = lib.mkEnableOption "the default recipients" // {
+ default = true;
+ };
+
+ settings = mkOption {
+ type = types.submodule agenixSecretSubmodule;
+ default = { };
+ description = ''
+ Settings for a given secret.
+
+ Loosely documented in the agenix [tutorial](https://github.com/ryantm/agenix#tutorial).
+ '';
+ };
+ };
+
+ # Dogfood `settings` to apply global and per-secret recipients
+ # Use `mkForce` to override
+ config = lib.mkMerge [
+ {
+ settings.publicKeys = attrValues config.recipients;
+ }
+
+ (lib.mkIf config.useDefault {
+ settings.publicKeys = cfg.recipients.default;
+ })
+ ];
+ };
+
+ secretPathSubmodule = {
+ imports = [ secretSettingsSubmodule ];
+
+ options = {
+ path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Relative path (to `config.rootDirectory`) of a secret";
+ };
+
+ regex = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "A regex for a given file path relative to `config.rootDirectory`.";
+ };
+ };
+ };
+
+ # TODO: Re-implement when can `types.either (types.submodule ...) (types.submodule ...)` works
+ # It would be good to avoid the `nullOr` above and assertions below
+ /*
+ secretRegexSubmodule = {
+ imports = [ secretSettingsSubmodule ];
+
+ options = {
+ regex = mkOption {
+ type = types.str;
+ description = "A regex for a given file path relative to `config.rootDirectory`.";
+ };
+ };
+ };
+ */
+
+ buildSubmodule = {
+ options = {
+ rules = mkOption {
+ type = types.attrsOf (types.submodule agenixSecretSubmodule);
+ readOnly = true;
+ description = "Final rules passed to the agenix CLi.";
+ };
+ };
+ };
+in
+
+{
+ imports = [
+ recipientsOptionSubmodule
+
+ <nixpkgs/nixos/modules/misc/assertions.nix>
+ ];
+
+ options = {
+ rootDirectory = mkOption {
+ type = types.path;
+ description = "Root directory containing agenix secrets.";
+ };
+
+ secrets = mkOption {
+ # TODO: Use `types.listOf (types.either ...)`
+ type = types.listOf (types.submodule secretPathSubmodule);
+ default = { };
+ description = "Submodule describing agenix secrets.";
+ };
+
+ # Outputs
+ build = mkOption {
+ type = types.submodule buildSubmodule;
+ default = { };
+ apply = build: if failedAssertions != [ ] then throw assertionsMessage else build;
+ internal = true;
+ };
+ };
+
+ config = {
+ assertions = map (secret: {
+ assertion = secret.path != null || secret.regex != null;
+ message = "One of `path` or `regex` must be set";
+ }) cfg.secrets;
+
+ build = {
+ # TODO: Harvest all secrets
+ rules = lib.listToAttrs (
+ lib.concatMap (
+ secret:
+
+ let
+ normalized = if secret.regex != null then handleSecretRegex secret else [ secret ];
+ in
+
+ map (secret': lib.nameValuePair secret'.path secret'.settings) normalized
+ ) cfg.secrets
+ );
+ };
+ };
+}
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index 5c3ae1c..3cd930d 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -1,38 +1,3 @@
-let
- toSecrets = import ./toSecrets.nix;
-
- owners = {
- getchoo = "age1zyqu6zkvl0rmlejhm5auzmtflfy4pa0fzwm0nzy737fqrymr7crsqrvnhs";
- };
-
- hosts = {
- glados = {
- owner = owners.getchoo;
- pubkey = "age1n7tyxx63wpgnmwkzn7dmkm62jxel840rk3ye3vsultrszsfrwuzsawdzhq";
- files = [
- "sethPassword.age"
- "macstadium.age"
- ];
- };
-
- glados-wsl = {
- pubkey = "age1ffqfq3azqfwxwtxnfuzzs0y566a7ydgxce4sqxjqzw8yexc2v4yqfr55vr";
- owner = owners.getchoo;
- inherit (hosts.glados) files;
- };
-
- atlas = {
- pubkey = "age18eu3ya4ucd2yzdrpkpg7wrymrxewt8j3zj2p2rqgcjeruacp0dgqryp39z";
- owner = owners.getchoo;
- files = [
- "userPassword.age"
- "miniflux.age"
- "nixpkgs-tracker-bot.age"
- "tailscaleAuthKey.age"
- "cloudflaredCreds.age"
- "teawieBot.age"
- ];
- };
- };
-in
-toSecrets hosts
+(import ./eval-agenix.nix {
+ modules = [ ./agenix-configuration.nix ];
+}).config.build.rules
diff --git a/secrets/toSecrets.nix b/secrets/toSecrets.nix
deleted file mode 100644
index 3ae33f1..0000000
--- a/secrets/toSecrets.nix
+++ /dev/null
@@ -1,35 +0,0 @@
-hosts:
-let
- # Find any public keys from a given system's attributes
- findPubkeysIn =
- host:
- builtins.filter (item: item != null) [
- (host.pubkey or null)
- (host.owner or null)
- ];
-
- # Memorize them for later
- publicKeysFor = builtins.mapAttrs (_: findPubkeysIn) hosts;
-
- # Map secret files meant for `hostname` to an attribute set containing
- # their relative path and public keys
- #
- # See https://github.com/ryantm/agenix/blob/de96bd907d5fbc3b14fc33ad37d1b9a3cb15edc6/README.md#tutorial
- # as a reference to what this outputs
- secretsFrom =
- hostname: host:
- builtins.listToAttrs (
- map (file: {
- name = "${hostname}/${file}";
- value = {
- publicKeys = publicKeysFor.${hostname};
- };
-
- }) host.files
- );
-
- # Memorize them all
- secretsFor = builtins.mapAttrs secretsFrom hosts;
-in
-# Now merge them all into one attribute set
-builtins.foldl' (acc: secrets: acc // secrets) { } (builtins.attrValues secretsFor)