secrets: add settings, generator submodules, improve tests

This commit is contained in:
DavHau
2024-07-02 15:38:24 +07:00
parent 1172acdc04
commit d3f31acc5c
3 changed files with 198 additions and 96 deletions

View File

@@ -8,31 +8,36 @@ let
module module
]; ];
}).config; }).config;
usage_simple = {
generators.my_secret = {
files.password = { };
files.username.secret = false;
prompts.prompt1 = { };
script = ''
cp $prompts/prompt1 $files/password
'';
};
};
in in
{ {
single_file_single_prompt = single_file_single_prompt =
let let
config = eval { config = eval usage_simple;
generators.my_secret = {
files.password = { };
files.username.secret = false;
prompts.prompt1 = { };
script = ''
cp $prompts/prompt1 $files/password
'';
};
};
in in
{ {
# files are always secret by default
test_file_secret_by_default = { test_file_secret_by_default = {
expr = config.generators.my_secret.files.password.secret; expr = config.generators.my_secret.files.password.secret;
expected = true; expected = true;
}; };
# secret files must not provide a value
test_secret_value_access_raises_error = { test_secret_value_access_raises_error = {
expr = config.generators.my_secret.files.password.value; expr = config.generators.my_secret.files.password.value;
expectedError.type = "ThrownError"; expectedError.type = "ThrownError";
expectedError.msg = "Cannot access value of secret file"; expectedError.msg = "Cannot access value of secret file";
}; };
# public values must provide a value at eval time
test_public_value_access = { test_public_value_access = {
expr = config.generators.my_secret.files.username ? value; expr = config.generators.my_secret.files.username ? value;
expected = true; expected = true;
@@ -47,4 +52,19 @@ in
expected = true; 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;
};
} }

View File

@@ -2,104 +2,117 @@
let let
inherit (lib) mkOption; inherit (lib) mkOption;
inherit (lib.types) inherit (lib.types)
anything
attrsOf attrsOf
bool bool
enum enum
listOf listOf
str str
submodule submoduleWith
; ;
submodule = module: submoduleWith { modules = [ module ]; };
options = lib.mapAttrs (_: mkOption); options = lib.mapAttrs (_: mkOption);
subOptions = opts: submodule { options = options opts; }; subOptions = opts: submodule { options = options opts; };
in in
{ {
options = options { options = options {
settings = {
description = ''
Settings for the generated variables.
'';
type = submodule {
freeFormType = anything;
imports = [ ./settings.nix ];
};
};
generators = { generators = {
type = attrsOf (subOptions { type = submodule {
dependencies = { freeformType = attrsOf (subOptions {
description = '' dependencies = {
A list of other generators that this generator depends on. description = ''
The output values of these generators will be available to the generator script as files. A list of other generators that this generator depends on.
For example, the file 'file1' of a dependency named 'dep1' will be available via $dependencies/dep1/file1. 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 = [ ]; type = listOf str;
}; default = [ ];
files = { };
description = '' files = {
A set of files to generate. description = ''
The generator 'script' is expected to produce exactly these files under $out. A set of files to generate.
''; The generator 'script' is expected to produce exactly these files under $out.
type = attrsOf (subOptions { '';
secret = { type = attrsOf (subOptions {
description = '' secret = {
Whether the file should be treated as a secret. description = ''
''; Whether the file should be treated as a secret.
type = bool; '';
default = true; type = bool;
}; default = true;
path = { };
description = '' path = {
The path to the file containing the content of the generated value. description = ''
This will be set automatically The path to the file containing the content of the generated value.
''; This will be set automatically
type = str; '';
readOnly = true; type = str;
}; readOnly = true;
value = { };
description = '' value = {
The content of the generated value. description = ''
Only available if the file is not secret. The content of the generated value.
''; Only available if the file is not secret.
type = str; '';
default = throw "Cannot access value of secret file"; type = str;
defaultText = "Throws error because the value of a secret file is not accessible"; default = throw "Cannot access value of secret file";
}; defaultText = "Throws error because the value of a secret file is not accessible";
}); };
}; });
prompts = { };
description = '' prompts = {
A set of prompts to ask the user for values. description = ''
Prompts are available to the generator script as files. A set of prompts to ask the user for values.
For example, a prompt named 'prompt1' will be available via $prompts/prompt1 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 = { type = attrsOf (subOptions {
description = '' description = {
The description of the prompted value description = ''
''; The description of the prompted value
type = str; '';
example = "SSH private key"; type = str;
}; example = "SSH private key";
type = { };
description = '' type = {
The input type of the prompt. description = ''
The following types are available: The input type of the prompt.
- hidden: A hidden text (e.g. password) The following types are available:
- line: A single line of text - hidden: A hidden text (e.g. password)
- multiline: A multiline text - line: A single line of text
''; - multiline: A multiline text
type = enum [ '';
"hidden" type = enum [
"line" "hidden"
"multiline" "line"
]; "multiline"
default = "line"; ];
}; default = "line";
}); };
}; });
script = { };
description = '' script = {
The script to run to generate the files. description = ''
The script will be run with the following environment variables: The script to run to generate the files.
- $dependencies: The directory containing the output values of all declared dependencies The script will be run with the following environment variables:
- $out: The output directory to put the generated files - $dependencies: The directory containing the output values of all declared dependencies
- $prompts: The directory containing the prompted values as files - $out: The output directory to put the generated files
The script should produce the files specified in the 'files' attribute under $out. - $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 = str;
}); };
});
};
}; };
}; };
} }

View File

@@ -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;
};
};
}