move passwords/keys to files so that they can be encrypted

This commit is contained in:
Jeffrey C. Ollie 2023-08-13 11:02:38 -05:00
parent 105e95b886
commit 9d3a72de1f
Signed by: jeff
GPG key ID: 6F86035A6D97044E

294
flake.nix
View file

@ -155,14 +155,16 @@
cipher = lib.options.mkOption { cipher = lib.options.mkOption {
type = lib.types.submodule { type = lib.types.submodule {
options = { options = {
password = lib.options.mkOption { passwordFile = lib.options.mkOption {
type = lib.types.str; type = lib.types.path;
description = "Path to file containing encryption password.";
}; };
type = lib.options.mkOption { type = lib.options.mkOption {
type = lib.types.enum [ type = lib.types.enum [
"aes-256-cbc" "aes-256-cbc"
]; ];
default = "aes-256-cbc"; default = "aes-256-cbc";
description = "Type of encryption to use.";
}; };
}; };
}; };
@ -176,15 +178,36 @@
"azure" "azure"
"b2" "b2"
]; ];
description = "The type of storage used for backups.";
};
azure = lib.options.mkOption {
type = lib.types.submodule {
options = {
accountName = lib.options.mkOption {
type = lib.types.str;
};
accountKeyFile = lib.options.mkOption {
type = lib.types.path;
};
container = lib.options.mkOption {
type = lib.types.str;
};
path = lib.options.mkOption {
type = lib.types.str;
};
};
};
default = { };
description = "Options for connecting to Azure Blob storage";
}; };
b2 = lib.options.mkOption { b2 = lib.options.mkOption {
type = lib.types.submodule { type = lib.types.submodule {
options = { options = {
account_id = lib.options.mkOption { accountId = lib.options.mkOption {
type = lib.types.str; type = lib.types.str;
}; };
account_key = lib.options.mkOption { accountKeyFile = lib.options.mkOption {
type = lib.types.str; type = lib.types.path;
}; };
endpoint = lib.options.mkOption { endpoint = lib.options.mkOption {
type = lib.types.str; type = lib.types.str;
@ -201,25 +224,7 @@
}; };
}; };
default = { }; default = { };
}; description = "Options for connection to BackBlaze B2";
azure = lib.options.mkOption {
type = lib.types.submodule {
options = {
account_name = lib.options.mkOption {
type = lib.types.str;
};
account_key = lib.options.mkOption {
type = lib.types.str;
};
container = lib.options.mkOption {
type = lib.types.str;
};
path = lib.options.mkOption {
type = lib.types.str;
};
};
};
default = { };
}; };
}; };
}; };
@ -262,6 +267,7 @@
}; };
password = lib.options.mkOption { password = lib.options.mkOption {
type = lib.types.addCheck lib.types.str (x: lib.hasPrefix "SCRAM-SHA-256$" x); type = lib.types.addCheck lib.types.str (x: lib.hasPrefix "SCRAM-SHA-256$" x);
description = "Encrypted password for the database account.";
}; };
}; };
} }
@ -326,66 +332,48 @@
] ]
); );
rcloneConfig = pkgs.writeTextFile { # rcloneConfig = pkgs.writeTextFile {
name = "rclone.conf"; # name = "rclone.conf";
text = { # text = {
"b2" = '' # "b2" = ''
[b2] # [b2]
type = b2 # type = b2
account = ${cfg.backup.storage.b2.account_id} # account = ${cfg.backup.storage.b2.accountId}
key = ${cfg.backup.storage.b2.account_key} # key = ${cfg.backup.storage.b2.accountKey}
''; # '';
"azure" = '' # "azure" = ''
[azure] # [azure]
type = azureblob # type = azureblob
account = ${cfg.backup.storage.azure.account_name} # account = ${cfg.backup.storage.azure.accountName}
key = ${cfg.backup.storage.azure.account_key} # key = ${cfg.backup.storage.azure.accountKey}
''; # '';
}.${cfg.backup.storage.type}; # }.${cfg.backup.storage.type};
}; # };
rcloneEnvironment = {
RCLONE_CONFIG = "/dev/null";
} // (
{
"azure" = {
RCLONE_CONFIG_AZURE_TYPE = "azureblob";
RCLONE_CONFIG_AZURE_ACCOUNT = cfg.backup.storage.azure.accountName;
};
"b2" = {
RCLONE_CONFIG_B2_TYPE = "b2";
RCLONE_CONFIG_B2_ACCOUNT = cfg.backup.storage.b2.accountId;
};
}.${cfg.backup.storage.type}
);
rcloneEnvironmentFiles = {
"azure" = {
RCLONE_CONFIG_AZURE_KEY = cfg.backup.storage.azure.accountKeyFile;
};
"b2" = {
RCLONE_CONFIG_B2_KEY = cfg.backup.storage.b2.accountKeyFile;
};
}.${cfg.backup.storage.type};
rclone = lib.mkIf (cfg.backup.enable) ( rclone = lib.mkIf (cfg.backup.enable) (
pkgs.writeShellScriptBin "rclone" ''
exec ${pkgs.rclone}/bin/rclone --config ${rcloneConfig} "''$@"
''
);
rootDir = "/var/lib/postgresql";
dataDir = "${rootDir}/15";
pgbackrestEnvironment =
{
PGBACKREST_LOG_LEVEL_CONSOLE = cfg.backup.log.console;
PGBACKREST_LOG_LEVEL_FILE = cfg.backup.log.file;
PGBACKREST_LOG_LEVEL_STDERR = cfg.backup.log.stderr;
PGBACKREST_PG1_PATH = dataDir;
PGBACKREST_PROCESS_MAX = "${builtins.toString cfg.backup.processMax}";
PGBACKREST_REPO1_CIPHER_PASS = cfg.backup.cipher.password;
PGBACKREST_REPO1_CIPHER_TYPE = cfg.backup.cipher.type;
PGBACKREST_REPO1_RETENTION_FULL = "14";
PGBACKREST_REPO1_RETENTION_FULL_TYPE = "time";
PGBACKREST_STANZA = "${config.networking.hostName}.${config.networking.domain}";
} // (
{
"azure" = {
PGBACKREST_REPO1_AZURE_ACCOUNT = cfg.backup.storage.azure.account_name;
PGBACKREST_REPO1_AZURE_CONTAINER = cfg.backup.storage.azure.container;
PGBACKREST_REPO1_AZURE_KEY = cfg.backup.storage.azure.account_key;
PGBACKREST_REPO1_PATH = cfg.backup.storage.azure.path;
PGBACKREST_REPO1_TYPE = "azure";
};
"b2" = {
PGBACKREST_REPO1_PATH = cfg.backup.storage.b2.path;
PGBACKREST_REPO1_S3_BUCKET = cfg.backup.storage.b2.bucket;
PGBACKREST_REPO1_S3_ENDPOINT = cfg.backup.storage.b2.endpoint;
PGBACKREST_REPO1_S3_KEY = cfg.backup.storage.b2.account_id;
PGBACKREST_REPO1_S3_KEY_SECRET = cfg.backup.storage.b2.account_key;
PGBACKREST_REPO1_S3_REGION = cfg.backup.storage.b2.region;
PGBACKREST_REPO1_TYPE = "s3";
};
}.${cfg.backup.storage.type}
);
pgbackrest = lib.mkIf (cfg.backup.enable) (
let let
environment = lib.concatStringsSep "\n" ( environment = lib.concatStringsSep "\n" (
lib.mapAttrsToList lib.mapAttrsToList
@ -393,16 +381,120 @@
n: v: n: v:
''export ${n}="${builtins.toString v}"'' ''export ${n}="${builtins.toString v}"''
) )
pgbackrestEnvironment rcloneEnvironment
);
environmentFiles = lib.concatStringsSep "\n" (
lib.mapAttrsToList
(
n: v:
''export ${n}=''$(<${v})''
)
rcloneEnvironmentFiles
); );
in in
pkgs.writeShellScriptBin "pgbackrest" '' pkgs.writeShellScriptBin "rclone" ''
${environment} ${environment}
exec ${pkgs.pgbackrest}/bin/pgbackrest "''$@" ${environmentFiles}
exec ${pkgs.rclone}/bin/rclone "''$@"
'' ''
); );
rootDir = "/var/lib/postgresql";
dataDir = "${rootDir}/15";
pgbackrestEnvironment = {
PGBACKREST_LOG_LEVEL_CONSOLE = cfg.backup.log.console;
PGBACKREST_LOG_LEVEL_FILE = cfg.backup.log.file;
PGBACKREST_LOG_LEVEL_STDERR = cfg.backup.log.stderr;
PGBACKREST_PG1_PATH = dataDir;
PGBACKREST_PROCESS_MAX = "${builtins.toString cfg.backup.processMax}";
PGBACKREST_REPO1_CIPHER_TYPE = cfg.backup.cipher.type;
PGBACKREST_REPO1_RETENTION_FULL = "14";
PGBACKREST_REPO1_RETENTION_FULL_TYPE = "time";
PGBACKREST_STANZA = "${config.networking.hostName}.${config.networking.domain}";
} // (
{
"azure" = {
PGBACKREST_REPO1_AZURE_ACCOUNT = cfg.backup.storage.azure.accountName;
PGBACKREST_REPO1_AZURE_CONTAINER = cfg.backup.storage.azure.container;
PGBACKREST_REPO1_PATH = cfg.backup.storage.azure.path;
PGBACKREST_REPO1_TYPE = "azure";
};
"b2" = {
PGBACKREST_REPO1_PATH = cfg.backup.storage.b2.path;
PGBACKREST_REPO1_S3_BUCKET = cfg.backup.storage.b2.bucket;
PGBACKREST_REPO1_S3_ENDPOINT = cfg.backup.storage.b2.endpoint;
PGBACKREST_REPO1_S3_KEY = cfg.backup.storage.b2.accountId;
PGBACKREST_REPO1_S3_REGION = cfg.backup.storage.b2.region;
PGBACKREST_REPO1_TYPE = "s3";
};
}.${cfg.backup.storage.type}
);
pgbackrestEnvironmentFiles = {
PGBACKREST_REPO1_CIPHER_PASS = cfg.backup.cipher.passwordFile;
} // {
"azure" = {
PGBACKREST_REPO1_AZURE_KEY = cfg.backup.storage.azure.accountKeyFile;
};
"b2" = {
PGBACKREST_REPO1_S3_KEY_SECRET = cfg.backup.storage.b2.accountKeyFile;
};
}.${cfg.backup.storage.type};
pgbackrest =
if (cfg.backup.enable)
then
(
let
environment = lib.concatStringsSep "\n" (
lib.mapAttrsToList
(
n: v:
''export ${n}="${builtins.toString v}"''
)
pgbackrestEnvironment
);
environmentFiles = lib.concatStringsSep "\n" (
lib.mapAttrsToList
(
n: v:
''export ${n}=''$(<${v})''
)
pgbackrestEnvironmentFiles
);
in
pkgs.writeShellScriptBin "pgbackrest" ''
${environment}
${environmentFiles}
exec ${pkgs.pgbackrest}/bin/pgbackrest "''$@"
''
)
else
{ };
# pgbackrestnu = lib.mkIf (cfg.backup.enable) (
# let
# environment = writeText "pgbackrest-environment.json" (builtins.toJSON pgbackrestEnvironment);
# in
# pkgs.writeScriptBin "pgbackrest" ''
# #!${pkgs.nushell}/bin/nu
# def main [...args: string] {
# load-env (open ${data})
# exec ${pkgs.pgbackrest}/bin/pgbackrest $args
# }
# ''
# );
in in
lib.mkIf cfg.enable { lib.mkIf cfg.enable {
assertions = [
# {
# assertion = !(cfg.backup.storage.b2.accountKey != null && cfg.backup.storage.b2.accountKeyFile != null);
# message = "cannot set both accountKey and accountKeyFile on B2 storage";
# }
];
environment.systemPackages = [ environment.systemPackages = [
postgresql postgresql
pgbackrest pgbackrest
@ -447,7 +539,7 @@
default root postgres default root postgres
default postgres postgres default postgres postgres
''; '';
archiveCommand = "${pkgs.pgbackrest}/bin/pgbackrest archive-push %p"; archiveCommand = "${pgbackrest}/bin/pgbackrest archive-push %p";
settings = { settings = {
bgwriter_flush_after = "512kB"; bgwriter_flush_after = "512kB";
checkpoint_flush_after = "256kB"; checkpoint_flush_after = "256kB";
@ -506,16 +598,28 @@
if (cfg.backup.enable) if (cfg.backup.enable)
then then
'' ''
init=''$(${pkgs.pgbackrest}/bin/pgbackrest info --output=json | ${pkgs.jq}/bin/jq '.[0].status.code == 0') init=''$(${pgbackrest}/bin/pgbackrest info --output=json | ${pkgs.jq}/bin/jq '.[0].status.code == 0')
if [ "$init" != "true" ] if [ "$init" != "true" ]
then then
${postgresql}/bin/pg_ctl -o "-c ssl=off -c listen_addresses=''' -p 55432" -w start ${postgresql}/bin/pg_ctl -o "-c ssl=off -c listen_addresses=''' -p 55432" -w start
${pkgs.pgbackrest}/bin/pgbackrest --pg1-port=55432 stanza-create ${pgbackrest}/bin/pgbackrest --pg1-port=55432 stanza-create
${postgresql}/bin/pg_ctl -m fast -w stop ${postgresql}/bin/pg_ctl -m fast -w stop
fi fi
'' ''
else else
""; "";
pgpass =
if (cfg.replication.enable && cfg.replication.role == "replica")
then
lib.concatStringsSep ":" [
cfg.replication.primary.hostname
(builtins.toString cfg.replication.primary.port)
"replication"
cfg.replication.username
''''$(<${cfg.replication.passwordFile}''
]
else
"";
in in
{ {
description = "PostgreSQL Server"; description = "PostgreSQL Server";
@ -524,11 +628,6 @@
environment = { environment = {
PGDATA = dataDir; PGDATA = dataDir;
} // ( } // (
if (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary"))
then
pgbackrestEnvironment
else { }
) // (
if (cfg.replication.enable && cfg.replication.role == "replica") if (cfg.replication.enable && cfg.replication.role == "replica")
then { then {
PGPASSFILE = "${rootDir}/.pgpass"; PGPASSFILE = "${rootDir}/.pgpass";
@ -563,8 +662,7 @@
'' ''
else else
'' ''
read -r password < ${cfg.replication.passwordFile} (umask 077; echo "${pgpass}" > ${rootDir}/.pgpass)
(umask 077; echo "${cfg.replication.primary.hostname}:${builtins.toString cfg.replication.primary.port}:replication:${cfg.replication.username}:''${password}" > ${rootDir}/.pgpass)
chmod 0600 ${rootDir}/.pgpass chmod 0600 ${rootDir}/.pgpass
if [ ! -s "${dataDir}/PG_VERSION" ] if [ ! -s "${dataDir}/PG_VERSION" ]
@ -720,7 +818,7 @@
sleep 0.1 sleep 0.1
done done
${pkgs.pgbackrest}/bin/pgbackrest --type=full --start-fast --stop-auto --delta backup ${pgbackrest}/bin/pgbackrest --type=full --start-fast --stop-auto --delta backup
''; '';
environment = pgbackrestEnvironment; environment = pgbackrestEnvironment;
serviceConfig = { serviceConfig = {
@ -749,7 +847,7 @@
sleep 0.1 sleep 0.1
done done
${pkgs.pgbackrest}/bin/pgbackrest --type=diff --start-fast --stop-auto --delta backup ${pgbackrest}/bin/pgbackrest --type=diff --start-fast --stop-auto --delta backup
''; '';
environment = pgbackrestEnvironment; environment = pgbackrestEnvironment;
serviceConfig = { serviceConfig = {
@ -778,7 +876,7 @@
sleep 0.1 sleep 0.1
done done
${pkgs.pgbackrest}/bin/pgbackrest --type=incr --start-fast --stop-auto --delta backup ${pgbackrest}/bin/pgbackrest --type=incr --start-fast --stop-auto --delta backup
''; '';
environment = pgbackrestEnvironment; environment = pgbackrestEnvironment;
serviceConfig = { serviceConfig = {