From d3f31acc5c687eb6b0f8b21abffc1998a15ecc62 Mon Sep 17 00:00:00 2001 From: DavHau Date: Tue, 2 Jul 2024 15:38:24 +0700 Subject: [PATCH] secrets: add settings, generator submodules, improve tests --- .../clanCore/vars/eval-tests/default.nix | 40 +++- nixosModules/clanCore/vars/interface.nix | 185 ++++++++++-------- nixosModules/clanCore/vars/settings.nix | 69 +++++++ 3 files changed, 198 insertions(+), 96 deletions(-) create mode 100644 nixosModules/clanCore/vars/settings.nix diff --git a/nixosModules/clanCore/vars/eval-tests/default.nix b/nixosModules/clanCore/vars/eval-tests/default.nix index ec676488..b4dfd304 100644 --- a/nixosModules/clanCore/vars/eval-tests/default.nix +++ b/nixosModules/clanCore/vars/eval-tests/default.nix @@ -8,31 +8,36 @@ let module ]; }).config; + + usage_simple = { + generators.my_secret = { + files.password = { }; + files.username.secret = false; + prompts.prompt1 = { }; + script = '' + cp $prompts/prompt1 $files/password + ''; + }; + }; in { single_file_single_prompt = let - config = eval { - generators.my_secret = { - files.password = { }; - files.username.secret = false; - prompts.prompt1 = { }; - script = '' - cp $prompts/prompt1 $files/password - ''; - }; - }; + config = eval usage_simple; in { + # files are always secret by default test_file_secret_by_default = { expr = config.generators.my_secret.files.password.secret; expected = true; }; + # secret files must not provide a value test_secret_value_access_raises_error = { expr = config.generators.my_secret.files.password.value; expectedError.type = "ThrownError"; expectedError.msg = "Cannot access value of secret file"; }; + # public values must provide a value at eval time test_public_value_access = { expr = config.generators.my_secret.files.username ? value; expected = true; @@ -47,4 +52,19 @@ in expected = true; }; }; + + # Ensure that generators.imports works + # This allows importing generators from third party projects without providing + # them access to other settings. + test_generator_modules = + let + generator_module = { + my-generator.files.password = { }; + }; + config = eval { generators.imports = [ generator_module ]; }; + in + { + expr = lib.trace (lib.attrNames config.generators) config.generators ? my-generator; + expected = true; + }; } diff --git a/nixosModules/clanCore/vars/interface.nix b/nixosModules/clanCore/vars/interface.nix index 1b51f071..15f1a501 100644 --- a/nixosModules/clanCore/vars/interface.nix +++ b/nixosModules/clanCore/vars/interface.nix @@ -2,104 +2,117 @@ let inherit (lib) mkOption; inherit (lib.types) + anything attrsOf bool enum listOf str - submodule + submoduleWith ; + submodule = module: submoduleWith { modules = [ module ]; }; options = lib.mapAttrs (_: mkOption); subOptions = opts: submodule { options = options opts; }; in { options = options { + settings = { + description = '' + Settings for the generated variables. + ''; + type = submodule { + freeFormType = anything; + imports = [ ./settings.nix ]; + }; + }; generators = { - type = attrsOf (subOptions { - dependencies = { - description = '' - A list of other generators that this generator depends on. - The output values of these generators will be available to the generator script as files. - For example, the file 'file1' of a dependency named 'dep1' will be available via $dependencies/dep1/file1. - ''; - type = listOf str; - default = [ ]; - }; - files = { - description = '' - A set of files to generate. - The generator 'script' is expected to produce exactly these files under $out. - ''; - type = attrsOf (subOptions { - secret = { - description = '' - Whether the file should be treated as a secret. - ''; - type = bool; - default = true; - }; - path = { - description = '' - The path to the file containing the content of the generated value. - This will be set automatically - ''; - type = str; - readOnly = true; - }; - value = { - description = '' - The content of the generated value. - Only available if the file is not secret. - ''; - type = str; - default = throw "Cannot access value of secret file"; - defaultText = "Throws error because the value of a secret file is not accessible"; - }; - }); - }; - prompts = { - description = '' - A set of prompts to ask the user for values. - Prompts are available to the generator script as files. - For example, a prompt named 'prompt1' will be available via $prompts/prompt1 - ''; - type = attrsOf (subOptions { - description = { - description = '' - The description of the prompted value - ''; - type = str; - example = "SSH private key"; - }; - type = { - description = '' - The input type of the prompt. - The following types are available: - - hidden: A hidden text (e.g. password) - - line: A single line of text - - multiline: A multiline text - ''; - type = enum [ - "hidden" - "line" - "multiline" - ]; - default = "line"; - }; - }); - }; - script = { - description = '' - The script to run to generate the files. - The script will be run with the following environment variables: - - $dependencies: The directory containing the output values of all declared dependencies - - $out: The output directory to put the generated files - - $prompts: The directory containing the prompted values as files - The script should produce the files specified in the 'files' attribute under $out. - ''; - type = str; - }; - }); + type = submodule { + freeformType = attrsOf (subOptions { + dependencies = { + description = '' + A list of other generators that this generator depends on. + The output values of these generators will be available to the generator script as files. + For example, the file 'file1' of a dependency named 'dep1' will be available via $dependencies/dep1/file1. + ''; + type = listOf str; + default = [ ]; + }; + files = { + description = '' + A set of files to generate. + The generator 'script' is expected to produce exactly these files under $out. + ''; + type = attrsOf (subOptions { + secret = { + description = '' + Whether the file should be treated as a secret. + ''; + type = bool; + default = true; + }; + path = { + description = '' + The path to the file containing the content of the generated value. + This will be set automatically + ''; + type = str; + readOnly = true; + }; + value = { + description = '' + The content of the generated value. + Only available if the file is not secret. + ''; + type = str; + default = throw "Cannot access value of secret file"; + defaultText = "Throws error because the value of a secret file is not accessible"; + }; + }); + }; + prompts = { + description = '' + A set of prompts to ask the user for values. + Prompts are available to the generator script as files. + For example, a prompt named 'prompt1' will be available via $prompts/prompt1 + ''; + type = attrsOf (subOptions { + description = { + description = '' + The description of the prompted value + ''; + type = str; + example = "SSH private key"; + }; + type = { + description = '' + The input type of the prompt. + The following types are available: + - hidden: A hidden text (e.g. password) + - line: A single line of text + - multiline: A multiline text + ''; + type = enum [ + "hidden" + "line" + "multiline" + ]; + default = "line"; + }; + }); + }; + script = { + description = '' + The script to run to generate the files. + The script will be run with the following environment variables: + - $dependencies: The directory containing the output values of all declared dependencies + - $out: The output directory to put the generated files + - $prompts: The directory containing the prompted values as files + The script should produce the files specified in the 'files' attribute under $out. + ''; + type = str; + }; + }); + }; }; }; } diff --git a/nixosModules/clanCore/vars/settings.nix b/nixosModules/clanCore/vars/settings.nix new file mode 100644 index 00000000..dbab1e92 --- /dev/null +++ b/nixosModules/clanCore/vars/settings.nix @@ -0,0 +1,69 @@ +{ lib, ... }: +{ + options = { + secretStore = lib.mkOption { + type = lib.types.enum [ + "sops" + "password-store" + "vm" + "custom" + ]; + default = "sops"; + description = '' + method to store secret facts + custom can be used to define a custom secret fact store. + ''; + }; + + secretModule = lib.mkOption { + type = lib.types.str; + internal = true; + description = '' + the python import path to the secret module + ''; + }; + + secretUploadDirectory = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + The directory where secrets are uploaded into, This is backend specific. + ''; + }; + + secretPathFunction = lib.mkOption { + type = lib.types.raw; + description = '' + The function to use to generate the path for a secret. + The default function will use the path attribute of the secret. + The function will be called with the secret submodule as an argument. + ''; + }; + + publicStore = lib.mkOption { + type = lib.types.enum [ + "in_repo" + "vm" + "custom" + ]; + default = "in_repo"; + description = '' + method to store public facts. + custom can be used to define a custom public fact store. + ''; + }; + + publicModule = lib.mkOption { + type = lib.types.str; + internal = true; + description = '' + the python import path to the public module + ''; + }; + + publicDirectory = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + }; + }; +}