mirror of
https://github.com/ilyakooo0/nixpkgs.git
synced 2024-10-13 07:57:32 +03:00
nixos/forgejo: refactor secrets, add cfg.secrets
This is not a breaking change. Existing setups continue to work as-is. Users of `cfg.mailerPasswordFile` will get an option rename/deprecation warning, but that's it (assuming there is no regression). This adds `cfg.secrets`, which is a wrapper over systemd's `LoadCredential=` leveraging Forgejo's `environment-to-ini`. `environment-to-ini` is intended for configuring Forgejo in OCI containers. It requires some fairly annoying escaping of the section names to fit into the allowed environment variable charset. E.g. `"log.console".COLORIZE = false` becomes `FORGEJO__LOG_0x2E_CONSOLE__COLORIZE=false`. - `.` needs to be replaced with `_0X2E_` and - `-` needs to be replaced with `_0X2D_` Those are simply the hex representation of each char from an ASCII table: . = ASCII 46 = 46 (decimal) = 2E (hex) = 0x2E = _OX2E_ To make interacting with `environment-to-ini` less annoying, we template and escape the sections/keys in nix: `cfg.secrets` takes the same free-form sections/keys as `cfg.settings`. Meaning there is now a generalized abstraction for all keys, not just those that have been manually implemented in the past. It goes as far as theoretically allowing one to have `DEFAULT.APP_NAME` read from a secret file. I don't know why one would want to do that, but it has been made possible by this :^) More reasonable examples are listed in the `cfg.secrets` option example. We also continue to bootstrap a handful of secrets like `security.SECRET_KEY`. This is done is a sort of sidecar bootstrap unit fittingly called `forgejo-secrets.service`. Overriding those is, just like before, not really intended and requires the use of `lib.mkForce` and might lead to breakage. But it is, in a way, more possible than before.
This commit is contained in:
parent
ae8404ff58
commit
694db856ed
@ -12,6 +12,15 @@ let
|
|||||||
usePostgresql = cfg.database.type == "postgres";
|
usePostgresql = cfg.database.type == "postgres";
|
||||||
useSqlite = cfg.database.type == "sqlite3";
|
useSqlite = cfg.database.type == "sqlite3";
|
||||||
|
|
||||||
|
secrets = let
|
||||||
|
mkSecret = section: values: lib.mapAttrsToList (key: value: {
|
||||||
|
env = envEscape "FORGEJO__${section}__${key}__FILE";
|
||||||
|
path = value;
|
||||||
|
}) values;
|
||||||
|
# https://codeberg.org/forgejo/forgejo/src/tag/v7.0.2/contrib/environment-to-ini/environment-to-ini.go
|
||||||
|
envEscape = string: lib.replaceStrings [ "." "-" ] [ "_0X2E_" "_0X2D_" ] (lib.strings.toUpper string);
|
||||||
|
in lib.flatten (lib.mapAttrsToList mkSecret cfg.secrets);
|
||||||
|
|
||||||
inherit (lib)
|
inherit (lib)
|
||||||
literalExpression
|
literalExpression
|
||||||
mkChangedOptionModule
|
mkChangedOptionModule
|
||||||
@ -34,6 +43,7 @@ in
|
|||||||
(mkRenamedOptionModule [ "services" "forgejo" "appName" ] [ "services" "forgejo" "settings" "DEFAULT" "APP_NAME" ])
|
(mkRenamedOptionModule [ "services" "forgejo" "appName" ] [ "services" "forgejo" "settings" "DEFAULT" "APP_NAME" ])
|
||||||
(mkRemovedOptionModule [ "services" "forgejo" "extraConfig" ] "services.forgejo.extraConfig has been removed. Please use the freeform services.forgejo.settings option instead")
|
(mkRemovedOptionModule [ "services" "forgejo" "extraConfig" ] "services.forgejo.extraConfig has been removed. Please use the freeform services.forgejo.settings option instead")
|
||||||
(mkRemovedOptionModule [ "services" "forgejo" "database" "password" ] "services.forgejo.database.password has been removed. Please use services.forgejo.database.passwordFile instead")
|
(mkRemovedOptionModule [ "services" "forgejo" "database" "password" ] "services.forgejo.database.password has been removed. Please use services.forgejo.database.passwordFile instead")
|
||||||
|
(mkRenamedOptionModule [ "services" "forgejo" "mailerPasswordFile" ] [ "services" "forgejo" "secrets" "mailer" "PASSWD" ])
|
||||||
|
|
||||||
# copied from services.gitea; remove at some point
|
# copied from services.gitea; remove at some point
|
||||||
(mkRenamedOptionModule [ "services" "forgejo" "cookieSecure" ] [ "services" "forgejo" "settings" "session" "COOKIE_SECURE" ])
|
(mkRenamedOptionModule [ "services" "forgejo" "cookieSecure" ] [ "services" "forgejo" "settings" "session" "COOKIE_SECURE" ])
|
||||||
@ -224,13 +234,6 @@ in
|
|||||||
description = "Path to the git repositories.";
|
description = "Path to the git repositories.";
|
||||||
};
|
};
|
||||||
|
|
||||||
mailerPasswordFile = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
example = "/run/keys/forgejo-mailpw";
|
|
||||||
description = "Path to a file containing the SMTP password.";
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = mkOption {
|
settings = mkOption {
|
||||||
default = { };
|
default = { };
|
||||||
description = ''
|
description = ''
|
||||||
@ -347,6 +350,44 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
secrets = mkOption {
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
This is a small wrapper over systemd's `LoadCredential`.
|
||||||
|
|
||||||
|
It takes the same sections and keys as {option}`services.forgejo.settings`,
|
||||||
|
but the value of each key is a path instead of a string or bool.
|
||||||
|
|
||||||
|
The path is then loaded as credential, exported as environment variable
|
||||||
|
and then feed through
|
||||||
|
<https://codeberg.org/forgejo/forgejo/src/branch/forgejo/contrib/environment-to-ini/environment-to-ini.go>.
|
||||||
|
|
||||||
|
It does the required environment variable escaping for you.
|
||||||
|
|
||||||
|
::: {.note}
|
||||||
|
Keys specified here take priority over the ones in {option}`services.forgejo.settings`!
|
||||||
|
:::
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
metrics = {
|
||||||
|
TOKEN = "/run/keys/forgejo-metrics-token";
|
||||||
|
};
|
||||||
|
camo = {
|
||||||
|
HMAC_KEY = "/run/keys/forgejo-camo-hmac";
|
||||||
|
};
|
||||||
|
service = {
|
||||||
|
HCAPTCHA_SECRET = "/run/keys/forgejo-hcaptcha-secret";
|
||||||
|
HCAPTCHA_SITEKEY = "/run/keys/forgejo-hcaptcha-sitekey";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
type = types.submodule {
|
||||||
|
freeformType = with types; attrsOf (attrsOf path);
|
||||||
|
options = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -381,7 +422,6 @@ in
|
|||||||
HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
|
HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
|
||||||
NAME = cfg.database.name;
|
NAME = cfg.database.name;
|
||||||
USER = cfg.database.user;
|
USER = cfg.database.user;
|
||||||
PASSWD = "#dbpass#";
|
|
||||||
})
|
})
|
||||||
(mkIf useSqlite {
|
(mkIf useSqlite {
|
||||||
PATH = cfg.database.path;
|
PATH = cfg.database.path;
|
||||||
@ -397,7 +437,6 @@ in
|
|||||||
|
|
||||||
server = mkIf cfg.lfs.enable {
|
server = mkIf cfg.lfs.enable {
|
||||||
LFS_START_SERVER = true;
|
LFS_START_SERVER = true;
|
||||||
LFS_JWT_SECRET = "#lfsjwtsecret#";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
session = {
|
session = {
|
||||||
@ -405,24 +444,33 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
security = {
|
security = {
|
||||||
SECRET_KEY = "#secretkey#";
|
|
||||||
INTERNAL_TOKEN = "#internaltoken#";
|
|
||||||
INSTALL_LOCK = true;
|
INSTALL_LOCK = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
mailer = mkIf (cfg.mailerPasswordFile != null) {
|
|
||||||
PASSWD = "#mailerpass#";
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth2 = {
|
|
||||||
JWT_SECRET = "#oauth2jwtsecret#";
|
|
||||||
};
|
|
||||||
|
|
||||||
lfs = mkIf cfg.lfs.enable {
|
lfs = mkIf cfg.lfs.enable {
|
||||||
PATH = cfg.lfs.contentDir;
|
PATH = cfg.lfs.contentDir;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.forgejo.secrets = {
|
||||||
|
security = {
|
||||||
|
SECRET_KEY = "${cfg.customDir}/conf/secret_key";
|
||||||
|
INTERNAL_TOKEN = "${cfg.customDir}/conf/internal_token";
|
||||||
|
};
|
||||||
|
|
||||||
|
oauth2 = {
|
||||||
|
JWT_SECRET = "${cfg.customDir}/conf/oauth2_jwt_secret";
|
||||||
|
};
|
||||||
|
|
||||||
|
database = mkIf (cfg.database.passwordFile != null) {
|
||||||
|
PASSWD = cfg.database.passwordFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
server = mkIf cfg.lfs.enable {
|
||||||
|
LFS_JWT_SECRET = "${cfg.customDir}/conf/lfs_jwt_secret";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
|
services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
|
||||||
enable = mkDefault true;
|
enable = mkDefault true;
|
||||||
|
|
||||||
@ -476,6 +524,37 @@ in
|
|||||||
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
"z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
systemd.services.forgejo-secrets = mkIf (!cfg.useWizard) {
|
||||||
|
description = "Forgejo secret bootstrap helper";
|
||||||
|
script = ''
|
||||||
|
if [ ! -s '${cfg.secrets.security.SECRET_KEY}' ]; then
|
||||||
|
${exe} generate secret SECRET_KEY > '${cfg.secrets.security.SECRET_KEY}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -s '${cfg.secrets.oauth2.JWT_SECRET}' ]; then
|
||||||
|
${exe} generate secret JWT_SECRET > '${cfg.secrets.oauth2.JWT_SECRET}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
${optionalString cfg.lfs.enable ''
|
||||||
|
if [ ! -s '${cfg.secrets.server.LFS_JWT_SECRET}' ]; then
|
||||||
|
${exe} generate secret LFS_JWT_SECRET > '${cfg.secrets.server.LFS_JWT_SECRET}'
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
|
||||||
|
if [ ! -s '${cfg.secrets.security.INTERNAL_TOKEN}' ]; then
|
||||||
|
${exe} generate secret INTERNAL_TOKEN > '${cfg.secrets.security.INTERNAL_TOKEN}'
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
ReadWritePaths = [ cfg.customDir ];
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.services.forgejo = {
|
systemd.services.forgejo = {
|
||||||
description = "Forgejo (Beyond coding. We forge.)";
|
description = "Forgejo (Beyond coding. We forge.)";
|
||||||
after = [
|
after = [
|
||||||
@ -484,11 +563,15 @@ in
|
|||||||
"postgresql.service"
|
"postgresql.service"
|
||||||
] ++ optionals useMysql [
|
] ++ optionals useMysql [
|
||||||
"mysql.service"
|
"mysql.service"
|
||||||
|
] ++ optionals (!cfg.useWizard) [
|
||||||
|
"forgejo-secrets.service"
|
||||||
];
|
];
|
||||||
requires = optionals (cfg.database.createDatabase && usePostgresql) [
|
requires = optionals (cfg.database.createDatabase && usePostgresql) [
|
||||||
"postgresql.service"
|
"postgresql.service"
|
||||||
] ++ optionals (cfg.database.createDatabase && useMysql) [
|
] ++ optionals (cfg.database.createDatabase && useMysql) [
|
||||||
"mysql.service"
|
"mysql.service"
|
||||||
|
] ++ optionals (!cfg.useWizard) [
|
||||||
|
"forgejo-secrets.service"
|
||||||
];
|
];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
path = [ cfg.package pkgs.git pkgs.gnupg ];
|
path = [ cfg.package pkgs.git pkgs.gnupg ];
|
||||||
@ -501,61 +584,15 @@ in
|
|||||||
# lfs_jwt_secret.
|
# lfs_jwt_secret.
|
||||||
# We have to consider this to stay compatible with older installations.
|
# We have to consider this to stay compatible with older installations.
|
||||||
preStart =
|
preStart =
|
||||||
let
|
|
||||||
runConfig = "${cfg.customDir}/conf/app.ini";
|
|
||||||
secretKey = "${cfg.customDir}/conf/secret_key";
|
|
||||||
oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
|
|
||||||
oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
|
|
||||||
lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
|
|
||||||
internalToken = "${cfg.customDir}/conf/internal_token";
|
|
||||||
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
|
|
||||||
in
|
|
||||||
''
|
''
|
||||||
# copy custom configuration and generate random secrets if needed
|
${optionalString (!cfg.useWizard) ''
|
||||||
${lib.optionalString (!cfg.useWizard) ''
|
|
||||||
function forgejo_setup {
|
function forgejo_setup {
|
||||||
cp -f '${format.generate "app.ini" cfg.settings}' '${runConfig}'
|
config='${cfg.customDir}/conf/app.ini'
|
||||||
|
cp -f '${format.generate "app.ini" cfg.settings}' "$config"
|
||||||
|
|
||||||
if [ ! -s '${secretKey}' ]; then
|
chmod u+w "$config"
|
||||||
${exe} generate secret SECRET_KEY > '${secretKey}'
|
${lib.getExe' cfg.package "environment-to-ini"} --config "$config"
|
||||||
fi
|
chmod u-w "$config"
|
||||||
|
|
||||||
# Migrate LFS_JWT_SECRET filename
|
|
||||||
if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
|
|
||||||
mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -s '${oauth2JwtSecret}' ]; then
|
|
||||||
${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
|
|
||||||
fi
|
|
||||||
|
|
||||||
${optionalString cfg.lfs.enable ''
|
|
||||||
if [ ! -s '${lfsJwtSecret}' ]; then
|
|
||||||
${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
|
|
||||||
fi
|
|
||||||
''}
|
|
||||||
|
|
||||||
if [ ! -s '${internalToken}' ]; then
|
|
||||||
${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod u+w '${runConfig}'
|
|
||||||
${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
|
|
||||||
${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
|
|
||||||
${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
|
|
||||||
|
|
||||||
${optionalString cfg.lfs.enable ''
|
|
||||||
${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
|
|
||||||
''}
|
|
||||||
|
|
||||||
${optionalString (cfg.database.passwordFile != null) ''
|
|
||||||
${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
|
|
||||||
''}
|
|
||||||
|
|
||||||
${optionalString (cfg.mailerPasswordFile != null) ''
|
|
||||||
${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
|
|
||||||
''}
|
|
||||||
chmod u-w '${runConfig}'
|
|
||||||
}
|
}
|
||||||
(umask 027; forgejo_setup)
|
(umask 027; forgejo_setup)
|
||||||
''}
|
''}
|
||||||
@ -616,6 +653,8 @@ in
|
|||||||
# System Call Filtering
|
# System Call Filtering
|
||||||
SystemCallArchitectures = "native";
|
SystemCallArchitectures = "native";
|
||||||
SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ];
|
SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ];
|
||||||
|
# cfg.secrets
|
||||||
|
LoadCredential = map (e: "${e.env}:${e.path}") secrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
environment = {
|
environment = {
|
||||||
@ -625,7 +664,7 @@ in
|
|||||||
# is resolved.
|
# is resolved.
|
||||||
GITEA_WORK_DIR = cfg.stateDir;
|
GITEA_WORK_DIR = cfg.stateDir;
|
||||||
GITEA_CUSTOM = cfg.customDir;
|
GITEA_CUSTOM = cfg.customDir;
|
||||||
};
|
} // lib.listToAttrs (map (e: lib.nameValuePair e.env "%d/${e.env}") secrets);
|
||||||
};
|
};
|
||||||
|
|
||||||
services.openssh.settings.AcceptEnv = mkIf (!cfg.settings.START_SSH_SERVER or false) "GIT_PROTOCOL";
|
services.openssh.settings.AcceptEnv = mkIf (!cfg.settings.START_SSH_SERVER or false) "GIT_PROTOCOL";
|
||||||
|
Loading…
Reference in New Issue
Block a user