Merge branch 'main' into DavHau-dave
This commit is contained in:
@@ -1,5 +1,26 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib.types) submoduleWith;
|
||||
submodule =
|
||||
module:
|
||||
submoduleWith {
|
||||
specialArgs.pkgs = pkgs;
|
||||
modules = [ module ];
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./public/in_repo.nix
|
||||
# ./public/vm.nix
|
||||
# ./secret/password-store.nix
|
||||
./secret/sops.nix
|
||||
# ./secret/vm.nix
|
||||
];
|
||||
options.clan.core.vars = lib.mkOption {
|
||||
visible = false;
|
||||
description = ''
|
||||
@@ -11,6 +32,20 @@
|
||||
- generate secrets like private keys automatically when they are needed
|
||||
- output multiple values like private and public keys simultaneously
|
||||
'';
|
||||
type = lib.types.submoduleWith { modules = [ ./interface.nix ]; };
|
||||
type = submodule { imports = [ ./interface.nix ]; };
|
||||
};
|
||||
|
||||
config.system.clan.deployment.data = {
|
||||
vars = {
|
||||
generators = lib.flip lib.mapAttrs config.clan.core.vars.generators (
|
||||
_name: generator: {
|
||||
inherit (generator) finalScript;
|
||||
files = lib.flip lib.mapAttrs generator.files (_name: file: { inherit (file) secret; });
|
||||
}
|
||||
);
|
||||
inherit (config.clan.core.vars.settings) secretUploadDirectory secretModule publicModule;
|
||||
};
|
||||
inherit (config.clan.networking) targetHost buildHost;
|
||||
inherit (config.clan.deployment) requireExplicitUpdate;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,21 +54,6 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# 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 = config.generators ? my-generator;
|
||||
expected = true;
|
||||
};
|
||||
|
||||
# script can be text
|
||||
test_script_text =
|
||||
let
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkOption;
|
||||
inherit (lib.types)
|
||||
anything
|
||||
attrsOf
|
||||
bool
|
||||
either
|
||||
@@ -14,30 +18,27 @@ let
|
||||
submoduleWith
|
||||
;
|
||||
# the original types.submodule has strange behavior
|
||||
submodule = module: submoduleWith { modules = [ module ]; };
|
||||
submodule =
|
||||
module:
|
||||
submoduleWith {
|
||||
specialArgs.pkgs = pkgs;
|
||||
modules = [ module ];
|
||||
};
|
||||
options = lib.mapAttrs (_: mkOption);
|
||||
subOptions = opts: submodule { options = options opts; };
|
||||
in
|
||||
{
|
||||
options = options {
|
||||
settings = {
|
||||
options = {
|
||||
settings = import ./settings-opts.nix { inherit lib; };
|
||||
generators = lib.mkOption {
|
||||
description = ''
|
||||
Settings for the generated variables.
|
||||
A set of generators that can be used to generate files.
|
||||
Generators are scripts that produce files based on the values of other generators and user input.
|
||||
Each generator is expected to produce a set of files under a directory.
|
||||
'';
|
||||
type = submodule {
|
||||
freeformType = anything;
|
||||
imports = [ ./settings.nix ];
|
||||
};
|
||||
};
|
||||
generators = {
|
||||
default = {
|
||||
imports = [
|
||||
# implementation of the generator
|
||||
./generator.nix
|
||||
];
|
||||
};
|
||||
type = submodule {
|
||||
freeformType = attrsOf (subOptions {
|
||||
default = { };
|
||||
type = attrsOf (submodule {
|
||||
imports = [ ./generator.nix ];
|
||||
options = options {
|
||||
dependencies = {
|
||||
description = ''
|
||||
A list of other generators that this generator depends on.
|
||||
@@ -52,32 +53,45 @@ in
|
||||
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";
|
||||
};
|
||||
});
|
||||
type = attrsOf (
|
||||
submodule (file: {
|
||||
imports = [ config.settings.fileModule ];
|
||||
options = options {
|
||||
name = {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
name of the public fact
|
||||
'';
|
||||
readOnly = true;
|
||||
default = file.config._module.args.name;
|
||||
};
|
||||
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 = ''
|
||||
@@ -85,28 +99,30 @@ in
|
||||
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";
|
||||
type = attrsOf (submodule {
|
||||
options = {
|
||||
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";
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -140,8 +156,8 @@ in
|
||||
internal = true;
|
||||
visible = false;
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
12
nixosModules/clanCore/vars/public/in_repo.nix
Normal file
12
nixosModules/clanCore/vars/public/in_repo.nix
Normal file
@@ -0,0 +1,12 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
config.clan.core.vars.settings =
|
||||
lib.mkIf (config.clan.core.vars.settings.publicStore == "in_repo")
|
||||
{
|
||||
publicModule = "clan_cli.vars.public_modules.in_repo";
|
||||
fileModule = file: {
|
||||
path =
|
||||
config.clan.core.clanDir + "/machines/${config.clan.core.machineName}/vars/${file.config.name}";
|
||||
};
|
||||
};
|
||||
}
|
||||
61
nixosModules/clanCore/vars/secret/sops.nix
Normal file
61
nixosModules/clanCore/vars/secret/sops.nix
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
secretsDir = config.clan.core.clanDir + "/sops/secrets";
|
||||
groupsDir = config.clan.core.clanDir + "/sops/groups";
|
||||
|
||||
# My symlink is in the nixos module detected as a directory also it works in the repl. Is this because of pure evaluation?
|
||||
containsSymlink =
|
||||
path:
|
||||
builtins.pathExists path
|
||||
&& (builtins.readFileType path == "directory" || builtins.readFileType path == "symlink");
|
||||
|
||||
containsMachine =
|
||||
parent: name: type:
|
||||
type == "directory" && containsSymlink "${parent}/${name}/machines/${config.clan.core.machineName}";
|
||||
|
||||
containsMachineOrGroups =
|
||||
name: type:
|
||||
(containsMachine secretsDir name type)
|
||||
|| lib.any (
|
||||
group: type == "directory" && containsSymlink "${secretsDir}/${name}/groups/${group}"
|
||||
) groups;
|
||||
|
||||
filterDir =
|
||||
filter: dir:
|
||||
lib.optionalAttrs (builtins.pathExists dir) (lib.filterAttrs filter (builtins.readDir dir));
|
||||
|
||||
groups = builtins.attrNames (filterDir (containsMachine groupsDir) groupsDir);
|
||||
secrets = filterDir containsMachineOrGroups secretsDir;
|
||||
in
|
||||
{
|
||||
config.clan.core.vars.settings = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") {
|
||||
# Before we generate a secret we cannot know the path yet, so we need to set it to an empty string
|
||||
fileModule = file: {
|
||||
path =
|
||||
lib.mkIf file.secret
|
||||
config.sops.secrets.${"${config.clan.core.machineName}-${file.config.name}"}.path
|
||||
or "/no-such-path";
|
||||
};
|
||||
secretModule = "clan_cli.vars.secret_modules.sops";
|
||||
secretUploadDirectory = lib.mkDefault "/var/lib/sops-nix";
|
||||
};
|
||||
|
||||
config.sops = lib.mkIf (config.clan.core.vars.settings.secretStore == "sops") {
|
||||
secrets = builtins.mapAttrs (name: _: {
|
||||
sopsFile = config.clan.core.clanDir + "/sops/secrets/${name}/secret";
|
||||
format = "binary";
|
||||
}) secrets;
|
||||
# To get proper error messages about missing secrets we need a dummy secret file that is always present
|
||||
defaultSopsFile = lib.mkIf config.sops.validateSopsFiles (
|
||||
lib.mkDefault (builtins.toString (pkgs.writeText "dummy.yaml" ""))
|
||||
);
|
||||
age.keyFile = lib.mkIf (builtins.pathExists (
|
||||
config.clan.core.clanDir + "/sops/secrets/${config.clan.core.machineName}-age.key/secret"
|
||||
)) (lib.mkDefault "/var/lib/sops-nix/key.txt");
|
||||
};
|
||||
}
|
||||
71
nixosModules/clanCore/vars/settings-opts.nix
Normal file
71
nixosModules/clanCore/vars/settings-opts.nix
Normal file
@@ -0,0 +1,71 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
fileModule = lib.mkOption {
|
||||
type = lib.types.deferredModule;
|
||||
internal = true;
|
||||
description = ''
|
||||
A module to be imported in every vars.files.<name> submodule.
|
||||
Used by backends to define the `path` attribute.
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
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;
|
||||
description = ''
|
||||
The directory where public facts are stored.
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{ 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;
|
||||
description = ''
|
||||
The directory where public facts are stored.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user