Merge pull request 'install: upload vars needed for activation for installation' (#2643) from Enzime/clan-core:push-yvpxptntlmuy into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/2643
This commit is contained in:
Mic92
2024-12-22 05:53:26 +00:00
6 changed files with 26 additions and 12 deletions

View File

@@ -5,7 +5,6 @@
... ...
}: }:
let let
inherit (lib) mkOption;
inherit (builtins) inherit (builtins)
hashString hashString
toJSON toJSON
@@ -198,9 +197,11 @@ in
}; };
neededFor = lib.mkOption { neededFor = lib.mkOption {
description = '' description = ''
Enabling this option causes the secret to be decrypted/installed before users and groups are created. This option determines when the secret will be decrypted and deployed to the target machine.
This can be used to retrieve user's passwords.
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root. By setting this to `activation`, the secret will be deployed prior to running `nixos-rebuild` or `nixos-install`.
By setting this to `user`, the secret will be deployed prior to users and groups are created, allowing
users' passwords to be managed by vars. The secret will be stored in `/run/secrets-for-users` and `owner` and `group` must be `root`.
''; '';
type = lib.types.enum [ type = lib.types.enum [
"activation" "activation"

View File

@@ -1,5 +1,4 @@
import argparse import argparse
import importlib
import logging import logging
import os import os
import sys import sys
@@ -44,9 +43,7 @@ def install_machine(opts: InstallOptions) -> None:
machine = opts.machine machine = opts.machine
machine.override_target_host = opts.target_host machine.override_target_host = opts.target_host
secret_facts_module = importlib.import_module(machine.secret_facts_module)
machine.info(f"installing {machine.name}") machine.info(f"installing {machine.name}")
secret_facts_store = secret_facts_module.SecretStore(machine=machine)
h = machine.target_host h = machine.target_host
target_host = f"{h.user or 'root'}@{h.host}" target_host = f"{h.user or 'root'}@{h.host}"
@@ -63,7 +60,8 @@ def install_machine(opts: InstallOptions) -> None:
upload_dir_ = upload_dir_[1:] upload_dir_ = upload_dir_[1:]
upload_dir = tmpdir / upload_dir_ upload_dir = tmpdir / upload_dir_
upload_dir.mkdir(parents=True) upload_dir.mkdir(parents=True)
secret_facts_store.upload(upload_dir) machine.secret_facts_store.upload(upload_dir)
machine.secret_vars_store.populate_dir(upload_dir)
if opts.password: if opts.password:
os.environ["SSHPASS"] = opts.password os.environ["SSHPASS"] = opts.password

View File

@@ -152,3 +152,7 @@ class StoreBase(ABC):
if target_hash is None and stored_hash is None: if target_hash is None and stored_hash is None:
return True return True
return stored_hash == target_hash return stored_hash == target_hash
@abstractmethod
def populate_dir(self, output_dir: Path) -> None:
pass

View File

@@ -49,3 +49,7 @@ class FactStore(StoreBase):
def exists(self, generator: Generator, name: str) -> bool: def exists(self, generator: Generator, name: str) -> bool:
return (self.directory(generator, name) / "value").exists() return (self.directory(generator, name) / "value").exists()
def populate_dir(self, output_dir: Path) -> None:
msg = "populate_dir is not implemented for public vars stores"
raise NotImplementedError(msg)

View File

@@ -47,3 +47,7 @@ class FactStore(StoreBase):
return fact_path.read_bytes() return fact_path.read_bytes()
msg = f"Fact {name} for service {generator.name} not found" msg = f"Fact {name} for service {generator.name} not found"
raise ClanError(msg) raise ClanError(msg)
def populate_dir(self, output_dir: Path) -> None:
msg = "populate_dir is not implemented for public vars stores"
raise NotImplementedError(msg)

View File

@@ -172,17 +172,20 @@ class SecretStore(StoreBase):
self.machine.flake_dir, self.machine.flake_dir,
sops_secrets_folder(self.machine.flake_dir) / key_name, sops_secrets_folder(self.machine.flake_dir) / key_name,
) )
(output_dir / "key.txt").touch(mode=0o600)
(output_dir / "key.txt").write_text(key) (output_dir / "key.txt").write_text(key)
for generator in self.machine.vars_generators: for generator in self.machine.vars_generators:
for file in generator.files: for file in generator.files:
if file.needed_for == "activation": if file.needed_for == "activation":
(output_dir / generator.name / file.name).parent.mkdir( target_path = output_dir / generator.name / file.name
target_path.parent.mkdir(
parents=True, parents=True,
exist_ok=True, exist_ok=True,
) )
(output_dir / generator.name / file.name).write_bytes( # chmod after in case it doesn't have u+w
self.get(generator, file.name) target_path.touch(mode=0o600)
) target_path.write_bytes(self.get(generator, file.name))
target_path.chmod(file.mode)
def upload(self) -> None: def upload(self) -> None:
with TemporaryDirectory(prefix="sops-upload-") as tempdir: with TemporaryDirectory(prefix="sops-upload-") as tempdir: