change secrets management to its own module and update hetzner-vm with it

This commit is contained in:
Chaos 2022-11-15 13:42:28 +00:00
parent c81a933217
commit 0a2aae3a43
No known key found for this signature in database
17 changed files with 371 additions and 198 deletions

View file

@ -1,68 +0,0 @@
{ }: {
mpd_control_password = {
user = "mpd";
group = "mpd";
permissions = "660";
path = "/secrets/mpd_control_password";
};
music_stream_passwd = {
user = "nginx";
group = "nginx";
permissions = "660";
path = "/secrets/music_stream_passwd";
};
chaos_mail_passwd = {
user = "dovecot2";
group = "dovecot2";
permissions = "660";
path = "/secrets/chaos_mail_passwd";
};
gitlab_env = {
user = "gitlab_artifacts_sync";
group = "gitlab_artifacts_sync";
permissions = "660";
path = "/secrets/gitlab_env";
};
restic_password = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/restic_password";
};
restic_env = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/restic_env";
};
wg_privkey = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/wg_privkey";
};
wg_preshared_tablet = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/wg_preshared_tablet";
};
wg_preshared_vault = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/wg_preshared_vault";
};
wg_preshared_storage = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/wg_preshared_storage";
};
wg_preshared_iphone8 = {
user = "root";
group = "root";
permissions = "660";
path = "/secrets/wg_preshared_iphone8";
};
}

View file

@ -1,97 +1,87 @@
{ pkgs, ... }:
let secrets-db = (import ./secrets-db.nix { });
in {
systemd.tmpfiles.rules = [ "d /secrets - root root" ];
environment.systemPackages = [
(pkgs.writeShellScriptBin "init-secrets" ''
set -e -o pipefail
{ pkgs, ... }: {
services.secrets = {
enable = true;
VAULT_ADDR_DEFAULT="https://vault.owo.monster"
[ -n "$VAULT_ADDR" ] && export VAULT_ADDR="$VAULT_ADDR_DEFAULT"
extraPackages = with pkgs;
[
# for music & mail passwd files
apacheHttpd
];
export PATH=$PATH:${pkgs.vault}/bin
export PATH=$PATH:${pkgs.jq}/bin
export PATH=$PATH:${pkgs.apacheHttpd}/bin
kv_get() {
vault kv get -format json $1
}
simple_get() {
kv_get $1 | jq .data.data$2 -r
}
file=${secrets-db.mpd_control_password.path}
echo $file
simple_get "/api-keys/mpd" .password > $file
chown ${secrets-db.mpd_control_password.user}:${secrets-db.mpd_control_password.group} $file
chmod ${secrets-db.mpd_control_password.permissions} $file
file=${secrets-db.music_stream_passwd.path}
echo $file
username=$(simple_get "/api-keys/music-stream" .username)
password=$(simple_get "/api-keys/music-stream" .password)
htpasswd -bc $file "$username" "$password"
chown ${secrets-db.music_stream_passwd.user}:${secrets-db.music_stream_passwd.group} $file
chmod ${secrets-db.music_stream_passwd.permissions} $file
file=${secrets-db.chaos_mail_passwd.path}
echo $file
password=$(simple_get "/passwords/mail" .password)
htpasswd -nbB "" "$password" | cut -d: -f2 > $file
chown ${secrets-db.chaos_mail_passwd.user}:${secrets-db.chaos_mail_passwd.group} $file
chmod ${secrets-db.chaos_mail_passwd.permissions} $file
file=${secrets-db.gitlab_env.path}
echo $file
token=$(simple_get "/api-keys/gitlab/gitlab_pages_serve" .token)
echo "GITLAB_TOKEN=$token" > $file
chown ${secrets-db.gitlab_env.user}:${secrets-db.gitlab_env.group} $file
chmod ${secrets-db.gitlab_env.permissions} $file
file=${secrets-db.restic_password.path}
echo $file
simple_get "/private-public-keys/restic/HetznerVM" .password > $file
chown ${secrets-db.restic_password.user}:${secrets-db.restic_password.group} $file
chmod ${secrets-db.restic_password.permissions} $file
file=${secrets-db.restic_env.path}
echo $file
RESTIC_USERNAME=$(simple_get "/api-keys/storage/restic/HetznerVM" .username)
RESTIC_PASSWORD=$(simple_get "/api-keys/storage/restic/HetznerVM" .password)
echo "RESTIC_REPOSITORY=rest:https://$RESTIC_USERNAME:$RESTIC_PASSWORD@storage-restic.owo.monster/HetznerVM" > $file
chown ${secrets-db.restic_env.user}:${secrets-db.restic_env.group} $file
chmod ${secrets-db.restic_env.permissions} $file
file=${secrets-db.wg_privkey.path}
echo $file
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .private > $file
chown ${secrets-db.wg_privkey.user}:${secrets-db.wg_privkey.group} $file
chmod ${secrets-db.wg_privkey.permissions} $file
file=${secrets-db.wg_preshared_tablet.path}
echo $file
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.tablet > $file
chown ${secrets-db.wg_preshared_tablet.user}:${secrets-db.wg_preshared_tablet.group} $file
chmod ${secrets-db.wg_preshared_tablet.permissions} $file
file=${secrets-db.wg_preshared_vault.path}
echo $file
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.vault > $file
chown ${secrets-db.wg_preshared_vault.user}:${secrets-db.wg_preshared_vault.group} $file
chmod ${secrets-db.wg_preshared_vault.permissions} $file
file=${secrets-db.wg_preshared_storage.path}
echo $file
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.storage > $file
chown ${secrets-db.wg_preshared_storage.user}:${secrets-db.wg_preshared_storage.group} $file
chmod ${secrets-db.wg_preshared_storage.permissions} $file
file=${secrets-db.wg_preshared_iphone8.path}
echo $file
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.iphone8 > $file
chown ${secrets-db.wg_preshared_iphone8.user}:${secrets-db.wg_preshared_iphone8.group} $file
chmod ${secrets-db.wg_preshared_iphone8.permissions} $file
'')
];
secrets = {
mpd_control_password = {
user = "mpd";
group = "mpd";
fetchScript = ''
simple_get "/api-keys/mpd" .password > $secretFile
'';
};
music_stream_passwd = {
user = "nginx";
group = "nginx";
fetchScript = ''
username=$(simple_get "/api-keys/music-stream" .username)
password=$(simple_get "/api-keys/music-stream" .password)
htpasswd -bc $secretFile "$username" "$password" 2>/dev/null
'';
};
chaos_mail_passwd = {
user = "dovecot2";
group = "dovecot2";
fetchScript = ''
password=$(simple_get "/passwords/mail" .password)
htpasswd -nbB "" "$password" 2>/dev/null | cut -d: -f2 > $secretFile
'';
};
gitlab_env = {
user = "gitlab_artifacts_sync";
group = "gitlab_artifacts_sync";
fetchScript = ''
token=$(simple_get "/api-keys/gitlab/gitlab_pages_serve" .token)
echo "GITLAB_TOKEN=$token" > $secretFile
'';
};
restic_password = {
fetchScript = ''
simple_get "/private-public-keys/restic/HetznerVM" .password > $secretFile
'';
};
restic_env = {
fetchScript = ''
RESTIC_USERNAME=$(simple_get "/api-keys/storage/restic/HetznerVM" .username)
RESTIC_PASSWORD=$(simple_get "/api-keys/storage/restic/HetznerVM" .password)
echo "RESTIC_REPOSITORY=rest:https://$RESTIC_USERNAME:$RESTIC_PASSWORD@storage-restic.owo.monster/HetznerVM" > $secretFile
'';
};
wg_privkey = {
fetchScript = ''
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .private > $secretFile
'';
};
wg_preshared_tablet = {
path = "/secrets/wg_preshared_tablet";
fetchScript = ''
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.tablet > $secretFile
'';
};
wg_preshared_vault = {
path = "/secrets/wg_preshared_vault";
fetchScript = ''
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.vault > $secretFile
'';
};
wg_preshared_storage = {
path = "/secrets/wg_preshared_storage";
fetchScript = ''
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.storage > $secretFile
'';
};
wg_preshared_iphone8 = {
path = "/secrets/wg_preshared_iphone8";
fetchScript = ''
simple_get "/private-public-keys/wireguard/chaos-internal/hetzner-vm" .preshared_keys.iphone8 > $secretFile
'';
};
};
};
}

View file

@ -1,9 +1,9 @@
{ ... }:
let secrets-db = (import ../secrets-db.nix { });
{ config, ... }:
let secrets = config.services.secrets.secrets;
in {
services.gitlab_artifacts_sync = {
enable = true;
credentialsEnvironmentFile = "${secrets-db.gitlab_env.path}";
credentialsEnvironmentFile = "${secrets.gitlab_env.path}";
repos = [
{
repoName = "ChaotiCryptidz/VaultUI";

View file

@ -1,5 +1,5 @@
{ }:
let secrets-db = (import ../../secrets-db.nix { });
{ config }:
let secrets = config.services.secrets.secrets;
in rec {
fqdn = "mail.owo.monster";
domains = [
@ -21,7 +21,7 @@ in rec {
accounts = {
"chaoticryptidz@owo.monster" = {
name = "chaoticryptidz@owo.monster";
passwordFile = "${secrets-db.chaos_mail_passwd.path}";
passwordFile = "${secrets.chaos_mail_passwd.path}";
aliases = [
"all@owo.monster"
# for sending from

View file

@ -1,7 +1,6 @@
{ config, pkgs, lib, ... }:
let
mail_config = (import ./config.nix { });
mail_config = (import ./config.nix { config = config; });
passwdDir = "/run/dovecot2";
passwdFile = "${passwdDir}/passwd";

View file

@ -1,7 +1,6 @@
{ config, lib, pkgs, ... }:
let
mail_config = (import ./config.nix { });
mail_config = (import ./config.nix { config = config; });
dkimUser = config.services.opendkim.user;
dkimGroup = config.services.opendkim.group;

View file

@ -1,7 +1,7 @@
{ config, pkgs, lib, ... }:
let
mail_config = (import ./config.nix { });
mail_config = (import ./config.nix { config = config; });
submissionHeaderCleanupRules =
pkgs.writeText "submission_header_cleanup_rules" (''
/^Received:/ IGNORE

View file

@ -1,7 +1,7 @@
{ config, pkgs, lib, ... }:
let
mail_config = (import ./config.nix { });
mail_config = (import ./config.nix { config = config; });
ports = (import ../../ports.nix { });

View file

@ -1,6 +1,6 @@
{ pkgs, ... }:
{ config, pkgs, ... }:
let
mail_config = (import ./config.nix { });
mail_config = (import ./config.nix { config = config; });
acmeRoot = "/var/lib/acme/acme-challenge";
in {

View file

@ -1,6 +1,6 @@
{ config, pkgs, lib, ... }:
let
mail_config = (import ./config.nix { });
mail_config = (import ./config.nix { config = config; });
v = mail_config.vmail_config;
sieve_directory = mail_config.sieve_directory;

View file

@ -1,5 +1,5 @@
{ ... }:
let mail_config = (import ./config.nix { });
{ config, ... }:
let mail_config = (import ./config.nix { config = config; });
in {
services.roundcube = {
enable = true;

View file

@ -1,7 +1,7 @@
{ pkgs, lib, tree, ... }:
{ config, pkgs, lib, tree, ... }:
let
ports = (import ../ports.nix { });
secrets-db = (import ../secrets-db.nix { });
secrets = config.services.secrets.secrets;
in {
environment.systemPackages = with pkgs; [ mpc_cli ];
@ -10,7 +10,7 @@ in {
network.listenAddress = "0.0.0.0";
musicDirectory = "https://storage-webdav.owo.monster/music_ro/";
credentials = [{
passwordFile = "${secrets-db.mpd_control_password.path}";
passwordFile = "${secrets.mpd_control_password.path}";
permissions = [ "read" "add" "control" "admin" ];
}];
extraConfig = ''
@ -53,14 +53,14 @@ in {
proxyPass = "http://127.0.0.1:${toString ports.mpd-opus}";
extraConfig = ''
auth_basic "Music Password";
auth_basic_user_file ${secrets-db.music_stream_passwd.path};
auth_basic_user_file ${secrets.music_stream_passwd.path};
'';
};
"/flac" = {
proxyPass = "http://127.0.0.1:${toString ports.mpd-flac}";
extraConfig = ''
auth_basic "Music Password";
auth_basic_user_file ${secrets-db.music_stream_passwd.path};
auth_basic_user_file ${secrets.music_stream_passwd.path};
'';
};
};

View file

@ -1,7 +1,7 @@
{ lib, config, pkgs, ... }:
let
secrets-db = (import ../secrets-db.nix { });
mail_config = (import ./mailserver/config.nix { });
secrets = config.services.secrets.secrets;
mail_config = (import ./mailserver/config.nix { config = config; });
backupPrepareCommand = "${
(pkgs.writeShellScriptBin "backupPrepareCommand" ''
@ -12,8 +12,8 @@ in {
environment.systemPackages = [
(pkgs.writeShellScriptBin "restic-hetzner-vm" ''
env \
RESTIC_PASSWORD_FILE=${secrets-db.restic_password.path} \
$(cat ${secrets-db.restic_env.path}) \
RESTIC_PASSWORD_FILE=${secrets.restic_password.path} \
$(cat ${secrets.restic_env.path}) \
${pkgs.restic}/bin/restic $@
'')
];
@ -40,8 +40,8 @@ in {
# repository is overrided in environmentFile to contain auth
# make sure to keep up to date when changing repository
repository = "rest:https://storage-restic.owo.monster/HetznerVM";
passwordFile = "${secrets-db.restic_password.path}";
environmentFile = "${secrets-db.restic_env.path}";
passwordFile = "${secrets.restic_password.path}";
environmentFile = "${secrets.restic_env.path}";
timerConfig = {
OnBootSec = "1m";

View file

@ -1,35 +1,35 @@
{ ... }:
let secrets-db = (import ../secrets-db.nix { });
{ config, ... }:
let secrets = config.services.secrets.secrets;
in {
networking.wg-quick.interfaces = {
wg0 = {
address = [ "10.69.42.1/32" ];
listenPort = 51820;
privateKeyFile = "${secrets-db.wg_privkey.path}";
privateKeyFile = "${secrets.wg_privkey.path}";
peers = [
# tablet
{
publicKey = "jXA0DeprEaL/ARQ3K81l8xWuUI5C/90DcY3bIfcIjz8=";
presharedKeyFile = "${secrets-db.wg_preshared_tablet.path}";
presharedKeyFile = "${secrets.wg_preshared_tablet.path}";
allowedIPs = [ "10.69.42.2/32" ];
}
# vault
{
publicKey = "IGq+WanFM/bKNUkwjO/0AAtDhJLvtvU+mVxH27QyHTc=";
presharedKeyFile = "${secrets-db.wg_preshared_vault.path}";
presharedKeyFile = "${secrets.wg_preshared_vault.path}";
endpoint = "vault.servers.genderfucked.monster:51820";
allowedIPs = [ "10.69.42.3/32" ];
}
# storage
{
publicKey = "biNNeCkjAWi2jUVoL5+1pBtXGa3OFZi4DltB2dqGjGg=";
presharedKeyFile = "${secrets-db.wg_preshared_storage.path}";
presharedKeyFile = "${secrets.wg_preshared_storage.path}";
allowedIPs = [ "10.69.42.4/32" ];
}
# iphone8
{
publicKey = "2BgT08bDKh8WlFFSeRArI9a1GpFgUyqEApvJy4KgAmw=";
presharedKeyFile = "${secrets-db.wg_preshared_iphone8.path}";
presharedKeyFile = "${secrets.wg_preshared_iphone8.path}";
allowedIPs = [ "10.69.42.5/32" ];
}
];

View file

@ -21,6 +21,7 @@ let
tree.impure.modules.nixos.rclone-serve
tree.impure.modules.nixos.rclone-sync
tree.impure.modules.nixos.secrets
];
nixosUnstableSystem = nixpkgs-unstable.lib.nixosSystem;

251
modules/nixos/secrets.nix Normal file
View file

@ -0,0 +1,251 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.secrets;
defaultPackages = with pkgs; [ pkgs.vault pkgs.jq ];
in {
options = {
services.secrets = {
enable = mkOption {
type = types.bool;
default = false;
};
debug = mkOption {
type = types.bool;
default = false;
};
createSecretsDir = mkOption {
type = types.bool;
default = true;
};
secretsDirUser = mkOption {
type = types.str;
default = "root";
};
secretsDirGroup = mkOption {
type = types.str;
default = "root";
};
secretsDir = mkOption {
type = types.str;
default = "/secrets";
};
vaultURL = mkOption {
type = types.str;
default = "https://vault.owo.monster";
description = "default Vault URL, can be overrided with env variables";
};
extraFunctions = mkOption {
type = types.lines;
default = "";
description = "extra bash functions to add to top of script";
};
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
description = "extra packages for script";
};
secrets = mkOption {
type = types.attrsOf (types.submodule ({ config, name, ... }: {
options = {
user = mkOption {
type = types.str;
default = "root";
};
group = mkOption {
type = types.str;
default = "root";
};
permissions = mkOption {
type = types.str;
default = "660";
};
path = mkOption {
type = types.str;
default = "${cfg.secretsDir}/${name}";
};
fetchScript = mkOption {
type = types.lines;
description = ''
script used to fetch secrets, $file is secret.path
'';
};
checkScript = mkOption {
type = types.nullOr types.lines;
default = null;
description = ''
script used to check contents of secret file, set LOCAL_FAIL to true on failure
'';
};
manual = mkOption {
type = types.bool;
default = false;
description = "should the secret be manually deployed";
};
};
}));
};
};
};
config = mkMerge [
(mkIf (cfg.enable) (let
scriptBase = ''
set -e -o pipefail
${if cfg.debug then "set -x" else ""}
'';
manualSecrets = filterAttrs (name: secret: secret.manual) cfg.secrets;
nonManualSecrets = filterAttrs (name: secret: !secret.manual) cfg.secrets;
initScript = ''
${scriptBase}
VAULT_ADDR_DEFAULT="${cfg.vaultURL}"
[ -n "$VAULT_ADDR" ] && export VAULT_ADDR="$VAULT_ADDR_DEFAULT"
kv_get() {
vault kv get -format json "$1"
}
simple_get() {
kv_get "$1" | jq ".data.data$2" -r
}
${cfg.extraFunctions}
'' + (lib.concatStringsSep "\n" (lib.mapAttrsToList (name: secret: ''
if [[ ! -f "${secret.path}" ]]; then
echo "Initializing Secret ${secret.path}"
else
echo "Updating Secret ${secret.path}"
fi
secretFile="${secret.path}"
${secret.fetchScript}
chown ${secret.user}:${secret.group} "${secret.path}"
chmod ${secret.permissions} "${secret.path}"
'') nonManualSecrets)) + (lib.concatStringsSep "\n" (lib.mapAttrsToList
(name: secret: ''
if [[ ! -f "${secret.path}" ]]; then
echo "Manual Secret ${secret.path} Doesn't Exist"
exit 1
fi
echo "Updating Permissions on Manual Secret ${secret.path}"
chown ${secret.user}:${secret.group} "${secret.path}"
chmod ${secret.permissions} "${secret.path}"
'') manualSecrets)) + ''
echo "Secrets Deployed"
'';
checkScript = ''
${scriptBase}
getUser() {
stat --format "%U" "$1" 2>/dev/null
}
getGroup() {
stat --format "%U" "$1" 2>/dev/null
}
getPermissions() {
stat --format "%a" "$1" 2>/dev/null
}
GLOBAL_FAIL=false
'' + (lib.concatStringsSep "\n" (lib.mapAttrsToList (name: secret: ''
LOCAL_FAIL=false
echo "Secret: ${name}"
echo "Checking ${secret.path}"
# some variables which can be used by checkScript
# shellcheck disable=SC2034
secretFile="${secret.path}"
if [[ -f "${secret.path}" ]]; then
echo " File Exists"
else
echo " File Does Not Exist"
LOCAL_FAIL=true
fi
if getUser "${secret.path}" >/dev/null && [[ "$(getUser "${secret.path}")" == "${secret.user}" ]]; then
echo " File Is Owned By Correct User"
else
echo " File Is Not Owned By Correct User (${secret.user})"
LOCAL_FAIL=true
fi
if getGroup "${secret.path}" >/dev/null && [[ "$(getGroup "${secret.path}")" == "${secret.group}" ]]; then
echo " File Is Owned By Correct Group"
else
echo " File Is Not Owned By Correct Group (${secret.user})"
LOCAL_FAIL=true
fi
if getPermissions "${secret.path}" >/dev/null && [[ "$(getPermissions "${secret.path}")" -eq "${secret.permissions}" ]]; then
echo " File Has Correct Permissions"
else
echo " File Does Not Have Correct Permissions (${secret.permissions})"
LOCAL_FAIL=true
fi
${if secret.checkScript != null then secret.checkScript else ""}
if [[ "$LOCAL_FAIL" == "true" ]]; then
echo " File Did Not Pass The Vibe Check"
GLOBAL_FAIL=true
else
echo " File Passed The Vibe Check"
fi
echo
'') cfg.secrets)) + ''
if [[ "$GLOBAL_FAIL" == "true" ]]; then
echo " One Or More Secrets Did Not Pass The Vibe Check"
exit 1
else
echo " All Secrets Passed The Vibe Check"
fi
'';
in {
environment.systemPackages = [
(pkgs.writeShellApplication {
name = "secrets-check";
runtimeInputs = defaultPackages ++ cfg.extraPackages;
text = checkScript;
})
(pkgs.writeShellApplication {
name = "secrets-init";
runtimeInputs = defaultPackages ++ cfg.extraPackages;
text = initScript;
})
];
}))
(mkIf (cfg.enable && cfg.createSecretsDir) {
systemd.tmpfiles.rules = [
"d ${cfg.secretsDir} - ${cfg.secretsDirUser} ${cfg.secretsDirGroup}"
];
})
];
}

View file

@ -35,7 +35,8 @@ in {
initrd.luks.devices = {
"${drive_data.root_mapper_name}" = {
device = "${drive_data.encrypted_root_path}";
keyFile = "${usb_data.encryption_keys_path}/${config.networking.hostName}.key";
keyFile =
"${usb_data.encryption_keys_path}/${config.networking.hostName}.key";
preLVM = false;
allowDiscards = true;
};