password-store owner & group support

This commit is contained in:
lassulus
2024-11-14 23:42:15 +01:00
parent 91efd937bf
commit 8f0c575425
4 changed files with 143 additions and 35 deletions

View File

@@ -68,14 +68,24 @@ in
submodule (file: { submodule (file: {
imports = [ imports = [
config.settings.fileModule config.settings.fileModule
(lib.mkRenamedOptionModule [ "owner" ] [ (lib.mkRenamedOptionModule
"sops" [
"owner" "sops"
]) "owner"
(lib.mkRenamedOptionModule [ "group" ] [ ]
"sops" [
"group" "owner"
]) ]
)
(lib.mkRenamedOptionModule
[
"sops"
"group"
]
[
"group"
]
)
]; ];
options = { options = {
name = lib.mkOption { name = lib.mkOption {
@@ -129,15 +139,13 @@ in
type = str; type = str;
}; };
sops = { owner = lib.mkOption {
owner = lib.mkOption { description = "The user name or id that will own the secret file.";
description = "The user name or id that will own the secret file. This option is currently only implemented for sops"; default = "root";
default = "root"; };
}; group = lib.mkOption {
group = lib.mkOption { description = "The group name or id that will own the secret file.";
description = "The group name or id that will own the secret file. This option is currently only implemented for sops"; default = "root";
default = "root";
};
}; };
value = value =

View File

@@ -1,12 +1,94 @@
{ config, lib, ... }:
{ {
config.clan.core.vars.settings = config,
lib.mkIf (config.clan.core.vars.settings.secretStore == "password-store") options,
{ lib,
fileModule = file: { pkgs,
path = lib.mkIf file.config.secret "${config.clan.core.vars.settings.secretUploadDirectory}/vars/${file.config.generatorName}/${file.config.name}"; ...
}:
let
installSecretTarball = pkgs.writeShellApplication {
name = "install-secret-tarball";
runtimeInputs = [
pkgs.gnutar
pkgs.gzip
pkgs.move-mount-beneath
];
text = ''
set -efu -o pipefail
src=$1
mkdir -p /run/secrets.tmp /run/secrets
if mountpoint -q /run/secrets; then
mount -t tmpfs -o noswap -o private tmpfs /run/secrets.tmp
chmod 511 /run/secrets.tmp
mount --bind --make-private /run/secrets.tmp /run/secrets.tmp
mount --bind --make-private /run/secrets /run/secrets
tar -xf "$src" -C /run/secrets.tmp
move-mount --beneath --move /run/secrets.tmp /run/secrets
umount -R /run/secrets.tmp
rmdir /run/secrets.tmp
umount --lazy /run/secrets
else
mount -t tmpfs -o noswap tmpfs /run/secrets
tar -xf "$src" -C /run/secrets
fi
'';
};
useSystemdActivation =
(options.systemd ? sysusers && config.systemd.sysusers.enable)
|| (options.services ? userborn && config.services.userborn.enable);
in
{
config = {
clan.core.vars.settings =
lib.mkIf (config.clan.core.vars.settings.secretStore == "password-store")
{
fileModule = file: {
path = "/run/secrets/vars/${file.config.generatorName}/${file.config.name}";
};
secretUploadDirectory = lib.mkDefault "/etc/secrets";
secretModule = "clan_cli.vars.secret_modules.password_store";
}; };
secretUploadDirectory = lib.mkDefault "/etc/secrets"; system.activationScripts.setupSecrets =
secretModule = "clan_cli.vars.secret_modules.password_store"; lib.mkIf
}; (
(config.clan.core.vars.settings.secretStore == "password-store")
&& (config.clan.core.vars.generators != { } && !useSystemdActivation)
)
(
lib.stringAfter
[
"specialfs"
"users"
"groups"
]
''
[ -e /run/current-system ] || echo setting up secrets...
${installSecretTarball}/bin/install-secret-tarball ${config.clan.core.vars.settings.secretUploadDirectory}/secrets.tar.gz
''
// lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true;
}
);
systemd.services.sops-install-secrets =
lib.mkIf
(
(config.clan.core.vars.settings.secretStore == "password-store")
&& (config.clan.core.vars.generators != { } && useSystemdActivation)
)
{
wantedBy = [ "sysinit.target" ];
after = [ "systemd-sysusers.service" ];
unitConfig.DefaultDependencies = "no";
serviceConfig = {
Type = "oneshot";
ExecStart = [
"${installSecretTarball}/bin/install-secret-tarball ${config.clan.core.vars.settings.secretUploadDirectory}/secrets.tar.gz"
];
RemainAfterExit = true;
};
};
};
} }

View File

@@ -49,6 +49,8 @@ class Var:
secret: bool secret: bool
shared: bool shared: bool
deployed: bool deployed: bool
owner: str
group: str
@property @property
def value(self) -> bytes: def value(self) -> bytes:
@@ -184,6 +186,8 @@ class StoreBase(ABC):
secret=file["secret"], secret=file["secret"],
shared=generator["share"], shared=generator["share"],
deployed=file["deploy"], deployed=file["deploy"],
owner=file.get("owner", "root"),
group=file.get("group", "root"),
) )
) )
return all_vars return all_vars

View File

@@ -1,4 +1,8 @@
import io
import logging
import os import os
import subprocess
import tarfile
from itertools import chain from itertools import chain
from pathlib import Path from pathlib import Path
from typing import override from typing import override
@@ -9,6 +13,8 @@ from clan_cli.nix import nix_shell
from . import SecretStoreBase from . import SecretStoreBase
log = logging.getLogger(__name__)
class SecretStore(SecretStoreBase): class SecretStore(SecretStoreBase):
def __init__(self, machine: Machine) -> None: def __init__(self, machine: Machine) -> None:
@@ -130,6 +136,7 @@ class SecretStore(SecretStoreBase):
# TODO get the path to the secrets from the machine # TODO get the path to the secrets from the machine
["cat", f"{self.machine.secret_vars_upload_directory}/.pass_info"], ["cat", f"{self.machine.secret_vars_upload_directory}/.pass_info"],
check=False, check=False,
stdout=subprocess.PIPE,
).stdout.strip() ).stdout.strip()
if not remote_hash: if not remote_hash:
@@ -139,13 +146,20 @@ class SecretStore(SecretStoreBase):
return local_hash.decode() != remote_hash return local_hash.decode() != remote_hash
def upload(self, output_dir: Path) -> None: def upload(self, output_dir: Path) -> None:
for secret_var in self.get_all(): with tarfile.open(output_dir / "secrets.tar.gz", "w:gz") as tar:
if not secret_var.deployed: for gen_name, generator in self.machine.vars_generators.items():
continue tar_dir = tarfile.TarInfo(name=gen_name)
output_file = output_dir / "vars" / secret_var.generator / secret_var.name tar_dir.type = tarfile.DIRTYPE
output_file.parent.mkdir(parents=True, exist_ok=True) tar_dir.mode = 0o511
with (output_file).open("wb") as f: tar.addfile(tarinfo=tar_dir)
f.write( for f_name, file in generator["files"].items():
self.get(secret_var.generator, secret_var.name, secret_var.shared) if not file["deploy"]:
) continue
tar_file = tarfile.TarInfo(name=f"{gen_name}/{f_name}")
content = self.get(gen_name, f_name, generator["share"])
tar_file.size = len(content)
tar_file.mode = 0o440
tar_file.uname = file.get("owner", "root")
tar_file.gname = file.get("group", "root")
tar.addfile(tarinfo=tar_file, fileobj=io.BytesIO(content))
(output_dir / ".pass_info").write_bytes(self.generate_hash()) (output_dir / ".pass_info").write_bytes(self.generate_hash())