password-store owner & group support
This commit is contained in:
@@ -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 =
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user