vars: implement secret generation
Some checks failed
buildbot/nix-build .#checks.aarch64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-minimal-inventory-machine Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-minimal-inventory-machine Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-app-no-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-ts-api Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli-docs Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-git Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-bash Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-archlinux Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-apk Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-deb Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-e2fsprogs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-installer-rpm Build done.
buildbot/nix-build .#checks.x86_64-linux.check-for-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.renderClanOptions Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-openssh Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.12-qemu" Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sops Build done.
buildbot/nix-build .#checks.x86_64-linux.package-default Build done.
buildbot/nix-build .#checks.x86_64-linux.treefmt Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-age Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-rsync Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-tor Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-nix Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sshpass Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-app-pytest Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.12-mypy" Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-default Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-inventory-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-inventory-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-example-valid Build done.
buildbot/nix-build .#checks.x86_64-linux.container Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-zbar Build done.
buildbot/nix-build .#checks.x86_64-linux.borgbackup Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.matrix-synapse Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-minimal-inventory-machine Build done.
buildbot/nix-build .#checks.x86_64-linux.package-merge-after-ci Build done.
buildbot/nix-build .#checks.x86_64-linux.package-pending-reviews Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotier-members Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.template-minimal Build done.
buildbot/nix-build .#checks.x86_64-linux.zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.wayland-proxy-virtwl Build done.
buildbot/nix-build .#checks.x86_64-linux.package-gui-install-test-ubuntu-22-04 Build done.
buildbot/nix-build .#checks.x86_64-linux.test-installation Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.package-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-editor Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-nix-unit-tests Build done.
buildbot/nix-build .#checks.x86_64-linux.package-moonlight-sunshine-accept Build done.
buildbot/nix-build .#checks.x86_64-linux.module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-deploy-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.deltachat Build done.
buildbot/nix-build .#checks.x86_64-linux.package-tea-create-pr Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotierone Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-app Build done.
buildbot/nix-build .#checks.x86_64-linux.package-impure-checks Build done.
buildbot/nix-build .#checks.x86_64-linux.postgresql Build done.
buildbot/nix-build .#checks.x86_64-linux.package-function-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.secrets Build done.
buildbot/nix-build .#checks.x86_64-linux.package-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.syncthing Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-inventory-eval Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-without-core Build done.
buildbot/nix-build .#checks.x86_64-linux.module-clan-vars-eval Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-app Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-with-core Build done.
buildbot/nix-build .#checks.x86_64-linux.test-backups Build done.
buildbot/nix-build .#checks.x86_64-linux.flash Build done.
buildbot/nix-eval Build done.
checks / checks-impure (pull_request) Failing after 3m59s

This commit is contained in:
DavHau 2024-07-10 17:26:47 +07:00
parent b066c3633a
commit 74a2eafc38
5 changed files with 204 additions and 127 deletions

View File

@ -36,128 +36,138 @@ in
Each generator is expected to produce a set of files under a directory.
'';
default = { };
type = attrsOf (submodule {
imports = [ ./generator.nix ];
options = options {
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 (
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;
type = attrsOf (
submodule (generator: {
imports = [ ./generator.nix ];
options = options {
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 (
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;
};
generatorName = {
type = lib.types.str;
description = ''
name of the generator
'';
readOnly = true;
default = generator.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";
};
};
secret = {
})
);
};
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 (submodule {
options = {
description = {
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
The description of the prompted value
'';
type = str;
readOnly = true;
example = "SSH private key";
};
value = {
type = {
description = ''
The content of the generated value.
Only available if the file is not secret.
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 = str;
default = throw "Cannot access value of secret file";
defaultText = "Throws error because the value of a secret file is not accessible";
type = enum [
"hidden"
"line"
"multiline"
];
default = "line";
};
};
})
);
});
};
runtimeInputs = {
description = ''
A list of packages that the generator script requires.
These packages will be available in the PATH when the script is run.
'';
type = listOf package;
default = [ ];
};
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 = either str path;
};
finalScript = {
description = ''
The final generator script, wrapped, so:
- all required programs are in PATH
- sandbox is set up correctly
'';
type = lib.types.str;
readOnly = true;
internal = true;
visible = false;
};
};
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 (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";
};
};
});
};
runtimeInputs = {
description = ''
A list of packages that the generator script requires.
These packages will be available in the PATH when the script is run.
'';
type = listOf package;
default = [ ];
};
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 = either str path;
};
finalScript = {
description = ''
The final generator script, wrapped, so:
- all required programs are in PATH
- sandbox is set up correctly
'';
type = lib.types.str;
readOnly = true;
internal = true;
visible = false;
};
};
});
})
);
};
};
}

View File

@ -38,7 +38,7 @@ in
fileModule = file: {
path =
lib.mkIf file.secret
config.sops.secrets.${"${config.clan.core.machineName}-${file.config.name}"}.path
config.sops.secrets.${"vars-${config.clan.core.machineName}-${file.config.generatorName}-${file.config.name}"}.path
or "/no-such-path";
};
secretModule = "clan_cli.vars.secret_modules.sops";

View File

@ -14,13 +14,16 @@ class SecretStore(SecretStoreBase):
self.machine = machine
# no need to generate keys if we don't manage secrets
if not hasattr(self.machine, "vars_data") or not self.machine.vars_generators:
if not self.machine.vars_generators:
return
has_secrets = False
for generator in self.machine.vars_generators.values():
if "files" in generator:
for file in generator["files"].values():
if file["secret"]:
return
has_secrets = True
if not has_secrets:
return
if has_machine(self.machine.flake_dir, self.machine.name):
return
@ -38,7 +41,7 @@ class SecretStore(SecretStoreBase):
) -> Path | None:
path = (
sops_secrets_folder(self.machine.flake_dir)
/ f"{self.machine.name}-{generator_name}-{name}"
/ f"vars-{self.machine.name}-{generator_name}-{name}"
)
encrypt_secret(
self.machine.flake_dir,
@ -49,15 +52,15 @@ class SecretStore(SecretStoreBase):
)
return path
def get(self, service: str, name: str) -> bytes:
def get(self, generator_name: str, name: str) -> bytes:
return decrypt_secret(
self.machine.flake_dir, f"{self.machine.name}-{name}"
self.machine.flake_dir, f"vars-{self.machine.name}-{generator_name}-{name}"
).encode("utf-8")
def exists(self, service: str, name: str) -> bool:
def exists(self, generator_name: str, name: str) -> bool:
return has_secret(
self.machine.flake_dir,
f"{self.machine.name}-{name}",
f"vars-{self.machine.name}-{generator_name}-{name}",
)
def upload(self, output_dir: Path) -> None:

View File

@ -7,6 +7,11 @@ class KeyPair:
self.privkey = privkey
class SopsSetup:
def __init__(self, keys: list[KeyPair]) -> None:
self.keys = keys
KEYS = [
KeyPair(
"age1dhwqzkah943xzc34tc3dlmfayyevcmdmxzjezdgdy33euxwf59vsp3vk3c",
@ -29,3 +34,14 @@ def age_keys() -> list[KeyPair]:
Root directory of the tests
"""
return KEYS
@pytest.fixture
def sops_setup(
monkeypatch: pytest.MonkeyPatch,
) -> SopsSetup:
"""
Root directory of the tests
"""
monkeypatch.setenv("SOPS_AGE_KEY", KEYS[0].privkey)
return SopsSetup(KEYS)

View File

@ -1,17 +1,15 @@
import os
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from age_keys import SopsSetup
from fixtures_flakes import generate_flake
from helpers import cli
from root import CLAN_CORE
if TYPE_CHECKING:
pass
@pytest.mark.impure
def test_generate_secret(
def test_generate_public_var(
monkeypatch: pytest.MonkeyPatch,
temporary_home: Path,
# age_keys: list["KeyPair"],
@ -41,8 +39,58 @@ def test_generate_secret(
),
)
monkeypatch.chdir(flake.path)
cmd = ["vars", "generate", "--flake", str(flake.path), "my_machine"]
cli.run(cmd)
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
assert (
flake.path / "machines" / "my_machine" / "vars" / "my_generator" / "my_secret"
).is_file()
@pytest.mark.impure
def test_generate_secret_var(
monkeypatch: pytest.MonkeyPatch,
temporary_home: Path,
sops_setup: SopsSetup,
) -> None:
flake = generate_flake(
temporary_home,
flake_template=CLAN_CORE / "templates" / "minimal",
machine_configs=dict(
my_machine=dict(
clan=dict(
core=dict(
vars=dict(
generators=dict(
my_generator=dict(
files=dict(
my_secret=dict(
secret=True,
)
),
script="echo hello > $out/my_secret",
)
)
)
)
)
)
),
)
monkeypatch.chdir(flake.path)
cli.run(
[
"secrets",
"users",
"add",
"--flake",
str(flake.path),
os.environ["USER"],
sops_setup.keys[0].pubkey,
]
)
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
assert not (
flake.path / "machines" / "my_machine" / "vars" / "my_generator" / "my_secret"
).is_file()
assert (
flake.path / "sops" / "secrets" / "vars-my_machine-my_generator-my_secret"
).is_dir()