Merge pull request 'password-store owner & group support' (#2418) from lassulus/clan-core:pass-owner into main

This commit is contained in:
clan-bot
2024-11-16 09:38:18 +00:00
4 changed files with 143 additions and 35 deletions

View File

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

View File

@@ -1,12 +1,94 @@
{ config, lib, ... }:
{
config.clan.core.vars.settings =
lib.mkIf (config.clan.core.vars.settings.secretStore == "password-store")
{
fileModule = file: {
path = lib.mkIf file.config.secret "${config.clan.core.vars.settings.secretUploadDirectory}/vars/${file.config.generatorName}/${file.config.name}";
config,
options,
lib,
pkgs,
...
}:
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";
secretModule = "clan_cli.vars.secret_modules.password_store";
};
system.activationScripts.setupSecrets =
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
shared: bool
deployed: bool
owner: str
group: str
@property
def value(self) -> bytes:
@@ -184,6 +186,8 @@ class StoreBase(ABC):
secret=file["secret"],
shared=generator["share"],
deployed=file["deploy"],
owner=file.get("owner", "root"),
group=file.get("group", "root"),
)
)
return all_vars

View File

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