update secret contract and add sops module

This commit is contained in:
ibizaman 2024-11-20 18:26:34 +01:00
parent fe137ded23
commit 5c25d74667
42 changed files with 1023 additions and 682 deletions

View file

@ -40,6 +40,7 @@
- `shb.forgejo.databasePasswordFile` -> `shb.forgejo.databasePassword.result.path`.
- Backup:
- `shb.restic.instances` options has been split between `shb.restic.instances.request` and `shb.restic.instances.settings`, matching better with contracts.
- Use of secret contract everywhere.
## User Facing Backwards Compatible Changes

View file

@ -69,7 +69,7 @@ SHB's first goal is to provide unified [building blocks](#available-blocks)
and by extension configuration interface, for self-hosting.
Compare the configuration for Nextcloud and Forgejo in Self Host Blocks.
The following snippets focus on similitudes and assume the relevant blocks are configured off-screen.
The following snippets focus on similitudes and assume the relevant blocks - like secrets - are configured off-screen.
It also does not show specific options for each service.
These are still complete snippets that configure HTTPS,
subdomain serving the service, LDAP and SSO integration.
@ -87,14 +87,14 @@ shb.nextcloud = {
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPasswordFile = config.sops.secrets."nextcloud/ldap_admin_password".path;
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
};
apps.sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
secretFile = config.sops.secrets."nextcloud/sso/secret".path;
secretFileForAuthelia = config.sops.secrets."authelia/nextcloud_sso_secret".path;
secret.result = config.shb.sops.secrets."nextcloud/sso/secret".result;
secretForAuthelia.result = config.shb.sops.secrets."nextcloud/sso/secretForAuthelia".result;
};
};
```
@ -112,15 +112,15 @@ shb.forgejo = {
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPasswordFile = config.sops.secrets."forgejo/ldap_admin_password".path;
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
};
sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
secretFile = config.sops.secrets."forgejo/ssoSecret".path;
secretFileForAuthelia = config.sops.secrets."forgejo/authelia/ssoSecret".path;
secret.result = config.shb.sops.secrets."forgejo/sso/secret".result;
secretForAuthelia.result = config.shb.sops.secrets."forgejo/sso/secretForAuthelia".result;
};
};
```

View file

@ -30,6 +30,10 @@ services you already have deployed.
Not all blocks are yet documented. You can find all available blocks [in the repository](@REPO@/modules/blocks).
:::
```{=include=} chapters html:into-file=//blocks-sops.html
modules/blocks/sops/docs/default.md
```
```{=include=} chapters html:into-file=//blocks-ssl.html
modules/blocks/ssl/docs/default.md
```

View file

@ -148,11 +148,14 @@ Provided contracts are:
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates.
Two providers are implemented: self-signed and Let's Encrypt.
- [Backup contract](contracts-backup.html) to backup directories.
One provider is implemented: Restic.
One provider is implemented: [Restic][].
- [Database Backup contract](contracts-databasebackup.html) to backup database dumps.
One provider is implemented: Restic.
One provider is implemented: [Restic][].
- [Secret contract](contracts-secret.html) to provide secrets that are deployed outside of the Nix store.
One provider is implemented: SOPS.
One provider is implemented: [SOPS][].
[restic]: blocks-restic.html
[sops]: blocks-sops.html
```{=include=} chapters html:into-file=//contracts-ssl.html
modules/contracts/ssl/docs/default.md

View file

@ -142,6 +142,11 @@ in stdenv.mkDerivation {
'@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ../modules/blocks/restic.nix ]}/share/doc/nixos/options.json
substituteInPlace ./modules/blocks/sops/docs/default.md \
--replace \
'@OPTIONS_JSON@' \
${individualModuleOptionsDocs [ ../modules/blocks/sops.nix ]}/share/doc/nixos/options.json
substituteInPlace ./modules/services/nextcloud-server/docs/default.md \
--replace \
'@OPTIONS_JSON@' \

View file

@ -39,14 +39,14 @@ shb.nextcloud = {
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPasswordFile = config.sops.secrets."nextcloud/ldap_admin_password".path;
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
};
apps.sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
secretFile = config.sops.secrets."nextcloud/sso/secret".path;
secretFileForAuthelia = config.sops.secrets."authelia/nextcloud_sso_secret".path;
secret.result = config.shb.sops.secrets."nextcloud/sso/secret".result;
secretForAuthelia.result = config.shb.sops.secrets."nextcloud/sso/secretForAuthelia".result;
};
};
```
@ -64,15 +64,15 @@ shb.forgejo = {
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPasswordFile = config.sops.secrets."forgejo/ldap_admin_password".path;
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/admin_password".result;
};
sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
secretFile = config.sops.secrets."forgejo/ssoSecret".path;
secretFileForAuthelia = config.sops.secrets."forgejo/authelia/ssoSecret".path;
secret.result = config.shb.sops.secrets."forgejo/sso/secret".result;
secretForAuthelia.result = config.shb.sops.secrets."forgejo/sso/secretForAuthelia".result;
};
};
```

View file

@ -286,8 +286,8 @@ One way to setup secrets management using `sops-nix`:
Setting the default this way makes all sops instances use that same file.
7. Reference the secrets in nix:
```nix
shb.nextcloud.adminPass.result.path = config.sops.secrets."nextcloud/adminpass".path;
sops.secrets."nextcloud/adminpass" = config.shb.nextcloud.adminPass.request;
shb.sops.secrets."nextcloud/adminpass".request = config.shb.nextcloud.adminPass.request;
shb.nextcloud.adminPass.result = config.shb.sops.secrets."nextcloud/adminpass".result;
```
The above snippet uses the [secrets contract](./contracts-secret.html) to ease configuration.
The above snippet uses the [secrets contract](./contracts-secret.html)
and [sops block](./blocks-sops.html) to ease the configuration.

View file

@ -68,45 +68,69 @@ in
description = "Secrets needed by Authelia";
type = lib.types.submodule {
options = {
jwtSecret = contracts.secret.mkOption {
jwtSecret = lib.mkOption {
description = "JWT secret.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
};
};
};
ldapAdminPassword = contracts.secret.mkOption {
ldapAdminPassword = lib.mkOption {
description = "LDAP admin user password.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
};
};
};
sessionSecret = contracts.secret.mkOption {
sessionSecret = lib.mkOption {
description = "Session secret.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
};
};
};
storageEncryptionKey = contracts.secret.mkOption {
storageEncryptionKey = lib.mkOption {
description = "Storage encryption key.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
};
};
};
identityProvidersOIDCHMACSecret = contracts.secret.mkOption {
identityProvidersOIDCHMACSecret = lib.mkOption {
description = "Identity provider OIDC HMAC secret.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
};
};
};
identityProvidersOIDCIssuerPrivateKey = contracts.secret.mkOption {
identityProvidersOIDCIssuerPrivateKey = lib.mkOption {
description = ''
Identity provider OIDC issuer private key.
Generate one with `nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048`
'';
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
};
};
};
};
};
@ -220,11 +244,15 @@ in
type = lib.types.str;
description = "Username to connect to the SMTP host.";
};
password = contracts.secret.mkOption {
password = lib.mkOption {
description = "File containing the password to connect to the SMTP host.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${fqdn}" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${fqdn}" ];
};
};
};
};
}))

View file

@ -1,10 +1,11 @@
{ config, options, lib, pkgs, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.shb.hardcodedsecret;
opt = options.shb.hardcodedsecret;
contracts = pkgs.callPackage ../contracts {};
inherit (lib) mapAttrs' mkOption nameValuePair;
inherit (lib.types) attrsOf listOf path nullOr str submodule;
inherit (lib.types) attrsOf nullOr str submodule;
inherit (pkgs) writeText;
in
{
@ -16,74 +17,49 @@ in
example = lib.literalExpression ''
{
mySecret = {
user = "me";
mode = "0400";
restartUnits = [ "myservice.service" ];
content = "My Secrets";
request = {
user = "me";
mode = "0400";
restartUnits = [ "myservice.service" ];
};
settings.content = "My Secret";
};
}
'';
type = attrsOf (submodule ({ name, ... }: {
options = {
mode = mkOption {
options = contracts.secret.mkProvider {
settings = mkOption {
description = ''
Mode of the secret file.
Settings specific to the hardcoded secret module.
Give either `content` or `source`.
'';
type = str;
default = "0400";
type = submodule {
options = {
content = mkOption {
type = nullOr str;
description = ''
Content of the secret as a string.
This will be stored in the nix store and should only be used for testing or maybe in dev.
'';
default = null;
};
source = mkOption {
type = nullOr str;
description = ''
Source of the content of the secret as a path in the nix store.
'';
default = null;
};
};
};
};
owner = mkOption {
description = ''
Linux user owning the secret file.
'';
type = str;
default = "root";
};
group = mkOption {
description = ''
Linux group owning the secret file.
'';
type = str;
default = "root";
};
restartUnits = mkOption {
description = ''
Systemd units to restart after the secret is updated.
'';
type = listOf str;
default = [];
};
path = mkOption {
type = path;
description = ''
Path to the file containing the secret generated out of band.
This path will exist after deploying to a target host,
it is not available through the nix store.
'';
default = "/run/hardcodedsecrets/hardcodedsecret_${name}";
};
content = mkOption {
type = nullOr str;
description = ''
Content of the secret.
This will be stored in the nix store and should only be used for testing or maybe in dev.
'';
default = null;
};
source = mkOption {
type = nullOr str;
description = ''
Source of the content of the secret.
'';
default = null;
resultCfg = {
path = "/run/hardcodedsecrets/hardcodedsecret_${name}";
};
};
}));
@ -92,16 +68,16 @@ in
config = {
system.activationScripts = mapAttrs' (n: cfg':
let
source = if cfg'.source != null
then cfg'.source
else writeText "hardcodedsecret_${n}_content" cfg'.content;
source = if cfg'.settings.source != null
then cfg'.settings.source
else writeText "hardcodedsecret_${n}_content" cfg'.settings.content;
in
nameValuePair "hardcodedsecret_${n}" ''
mkdir -p "$(dirname "${cfg'.path}")"
touch "${cfg'.path}"
chmod ${cfg'.mode} "${cfg'.path}"
chown ${cfg'.owner}:${cfg'.group} "${cfg'.path}"
cp ${source} "${cfg'.path}"
mkdir -p "$(dirname "${cfg'.result.path}")"
touch "${cfg'.result.path}"
chmod ${cfg'.request.mode} "${cfg'.result.path}"
chown ${cfg'.request.owner}:${cfg'.request.group} "${cfg'.result.path}"
cp ${source} "${cfg'.result.path}"
''
) cfg;
};

View file

@ -47,20 +47,28 @@ in
default = 17170;
};
ldapUserPassword = contracts.secret.mkOption {
ldapUserPassword = lib.mkOption {
description = "LDAP admin user secret.";
mode = "0440";
owner = "lldap";
group = "lldap";
restartUnits = [ "lldap.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "lldap";
group = "lldap";
restartUnits = [ "lldap.service" ];
};
};
};
jwtSecret = contracts.secret.mkOption {
jwtSecret = lib.mkOption {
description = "JWT secret.";
mode = "0440";
owner = "lldap";
group = "lldap";
restartUnits = [ "lldap.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "lldap";
group = "lldap";
restartUnits = [ "lldap.service" ];
};
};
};
restrictAccessIPRange = lib.mkOption {

View file

@ -82,14 +82,28 @@ in
default = [];
};
adminPasswordFile = lib.mkOption {
type = lib.types.path;
description = "File containing the initial admin password.";
adminPassword = lib.mkOption {
description = "Initial admin password.";
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "grafana";
group = "grafana";
restartUnits = [ "grafana.service" ];
};
};
};
secretKeyFile = lib.mkOption {
type = lib.types.path;
description = "File containing the secret key used for signing.";
secretKey = lib.mkOption {
description = "Secret key used for signing.";
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "grafana";
group = "grafana";
restartUnits = [ "grafana.service" ];
};
};
};
smtp = lib.mkOption {
@ -159,9 +173,9 @@ in
};
security = {
secret_key = "$__file{${cfg.secretKeyFile}}";
secret_key = "$__file{${cfg.secretKey.result.path}}";
disable_initial_admin_creation = false; # Enable when LDAP support is configured.
admin_password = "$__file{${cfg.adminPasswordFile}}"; # Remove when LDAP support is configured.
admin_password = "$__file{${cfg.adminPassword.result.path}}"; # Remove when LDAP support is configured.
};
server = {

View file

@ -16,8 +16,8 @@ shb.monitoring = {
subdomain = "grafana";
inherit domain;
contactPoints = [ "me@example.com" ];
adminPasswordFile = config.sops.secrets."monitoring/admin_password".path;
secretKeyFile = config.sops.secrets."monitoring/secret_key".path;
adminPassword.result = config.sops.secrets."monitoring/admin_password".reuslt;
secretKey.result = config.sops.secrets."monitoring/secret_key".result;
};
sops.secrets."monitoring/admin_password" = {

View file

@ -6,11 +6,11 @@ let
shblib = pkgs.callPackage ../../lib {};
contracts = pkgs.callPackage ../contracts {};
inherit (lib) concatStringsSep filterAttrs flatten literalExpression optionals optionalString listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge;
inherit (lib) concatStringsSep filterAttrs flatten literalExpression optionals listToAttrs mapAttrsToList mkEnableOption mkOption mkMerge;
inherit (lib) generators hasPrefix mkIf nameValuePair optionalAttrs removePrefix;
inherit (lib.types) attrsOf enum int ints listOf oneOf nonEmptyListOf nonEmptyStr nullOr path str submodule;
inherit (lib.types) attrsOf enum int ints oneOf nonEmptyStr nullOr str submodule;
commonOptions = { name, options, prefix, ... }: {
commonOptions = { name, prefix, config, ... }: {
enable = mkEnableOption ''
this backup intance.
@ -18,13 +18,17 @@ let
but still provides the helper tool to restore snapshots
'';
passphrase = contracts.secret.mkOption {
passphrase = lib.mkOption {
description = "Encryption key for the backup repository.";
mode = "0400";
owner = options.request.value.user;
ownerText = "[shb.restic.${prefix}.<name>.request.user](#blocks-restic-options-shb.restic.${prefix}._name_.request.user)";
restartUnits = [ (fullName name options.settings.value.repository) ];
restartUnitsText = "[ [shb.restic.${prefix}.<name>.settings.repository](#blocks-restic-options-shb.restic.${prefix}._name_.settings.repository) ]";
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = config.request.user;
ownerText = "[shb.restic.${prefix}.<name>.request.user](#blocks-restic-options-shb.restic.${prefix}._name_.request.user)";
restartUnits = [ (fullName name config.settings.repository) ];
restartUnitsText = "[ [shb.restic.${prefix}.<name>.settings.repository](#blocks-restic-options-shb.restic.${prefix}._name_.settings.repository) ]";
};
};
};
repository = mkOption {
@ -99,7 +103,6 @@ let
};
repoSlugName = name: builtins.replaceStrings ["/" ":"] ["_" "_"] (removePrefix "/" name);
backupName = name: repository: "${name}_${repoSlugName repository.path}";
fullName = name: repository: "restic-backups-${name}_${repoSlugName repository.path}";
in
{
@ -107,7 +110,7 @@ in
instances = mkOption {
description = "Files to backup following the [backup contract](./contracts-backup.html).";
default = {};
type = attrsOf (submodule ({ name, options, ... }: {
type = attrsOf (submodule ({ name, config, ... }: {
options = {
request = mkOption {
description = ''
@ -125,7 +128,7 @@ in
'';
type = submodule {
options = commonOptions { inherit name options; prefix = "instances"; };
options = commonOptions { inherit name config; prefix = "instances"; };
};
};
@ -136,16 +139,16 @@ in
Contains the output of the Restic provider.
'';
default = {
restoreScript = fullName name options.settings.value.repository;
backupService = "${fullName name options.settings.value.repository}.service";
restoreScript = fullName name config.settings.repository;
backupService = "${fullName name config.settings.repository}.service";
};
defaultText = {
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
};
type = contracts.backup.result {
restoreScript = fullName name options.settings.value.repository;
backupService = "${fullName name options.settings.value.repository}.service";
restoreScript = fullName name config.settings.repository;
backupService = "${fullName name config.settings.repository}.service";
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
};
@ -157,7 +160,7 @@ in
databases = mkOption {
description = "Databases to backup following the [database backup contract](./contracts-databasebackup.html).";
default = {};
type = attrsOf (submodule ({ name, options, ... }: {
type = attrsOf (submodule ({ name, config, ... }: {
options = {
request = mkOption {
description = ''
@ -175,7 +178,7 @@ in
'';
type = submodule {
options = commonOptions { inherit name options; prefix = "databases"; };
options = commonOptions { inherit name config; prefix = "databases"; };
};
};
@ -186,16 +189,16 @@ in
Contains the output of the Restic provider.
'';
default = {
restoreScript = fullName name options.settings.value.repository;
backupService = "${fullName name options.settings.value.repository}.service";
restoreScript = fullName name config.settings.repository;
backupService = "${fullName name config.settings.repository}.service";
};
defaultText = {
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
};
type = contracts.databasebackup.result {
restoreScript = fullName name options.settings.value.repository;
backupService = "${fullName name options.settings.value.repository}.service";
restoreScript = fullName name config.settings.repository;
backupService = "${fullName name config.settings.repository}.service";
restoreScriptText = "${fullName "<name>" { path = "path/to/repository"; }}";
backupServiceText = "${fullName "<name>" { path = "path/to/repository"; }}.service";
};

View file

@ -12,7 +12,7 @@ Specific integration tests are defined in [`/test/blocks/restic.nix`](@REPO@/tes
## Provider Contracts {#blocks-restic-contract-provider}
This block provides:
This block provides the following contracts:
- [backup contract](contracts-backup.html) under the [`shb.restic.instances`][instances] option.
It is tested with [contract tests][backup contract tests].
@ -31,10 +31,11 @@ the only requirement to run it is to be able to `sudo` in the expected user.
## Usage {#blocks-restic-usage}
The following examples assume usage of SOPS to provide secrets
The following examples assume usage of the [sops block][] to provide secrets
although any blocks providing the [secrets contract][] works too.
The [secrets setup section](usage.html#usage-secrets) explains
how to setup SOPS.
[sops block]: ./blocks-sops.html
[secrets contract]: ./contracts-secrets.html
### One folder backed up manually {#blocks-restic-usage-provider-manual}
@ -55,7 +56,7 @@ shb.restic.instances."myservice" = {
settings = {
enable = true;
passphraseFile = "<path/to/passphrase>";
passphrase.result = shb.sops.secret."passphrase".result;
repository = {
path = "/srv/backups/myservice";
@ -74,6 +75,9 @@ shb.restic.instances."myservice" = {
};
};
};
shb.sops.secret."passphrase".request =
shb.restic.instances."myservice".settings.passphrase.request;
```
### One folder backed up with contract {#blocks-restic-usage-provider-contract}
@ -89,7 +93,7 @@ shb.restic.instances."myservice" = {
settings = {
enable = true;
passphraseFile = "<path/to/passphrase>";
passphrase.result = shb.sops.secret."passphrase".result;
repository = {
path = "/srv/backups/myservice";
@ -108,6 +112,9 @@ shb.restic.instances."myservice" = {
};
};
};
shb.sops.secret."passphrase".request =
shb.restic.instances."myservice".settings.passphrase.request;
```
### One folder backed up to S3 {#blocks-restic-usage-provider-remote}

36
modules/blocks/sops.nix Normal file
View file

@ -0,0 +1,36 @@
{ lib, pkgs, ... }:
let
inherit (lib) mkOption;
inherit (lib.types) attrsOf anything submodule;
contracts = pkgs.callPackage ../contracts {};
in
{
options.shb.sops = {
secret = mkOption {
description = "Secret following the [secret contract](./contracts-secret.html).";
default = {};
type = attrsOf (submodule ({ name, options, ... }: {
options = contracts.secret.mkProvider {
settings = mkOption {
description = ''
Settings specific to the Sops provider.
This is a passthrough option to set [sops-nix options](https://github.com/Mic92/sops-nix/blob/master/modules/sops/default.nix).
Note though that the `mode`, `owner`, `group`, and `restartUnits`
are managed by the [shb.sops.secret.<name>.request](#blocks-sops-options-shb.sops.secret._name_.request) option.
'';
type = anything;
};
resultCfg = {
path = "/run/secrets/${name}";
pathText = "/run/secrets/<name>";
};
};
}));
};
};
}

View file

@ -0,0 +1,47 @@
# SOPS Block {#blocks-sops}
Defined in [`/modules/blocks/sops.nix`](@REPO@/modules/blocks/sops.nix).
This block sets up a [sops-nix][] secret.
It is only a small layer on top of `sops-nix` options
to adapt it to the [secret contract](./contract-secret.html).
[sops-nix]: https://github.com/Mic92/sops-nix
## Provider Contracts {#blocks-sops-contract-provider}
This block provides the following contracts:
- [secret contract][] under the [`shb.sops.secrets`][secret] option.
It is not yet tested with [contract tests][secret contract tests] but it is used extensively on several machines.
[secret]: #blocks-sops-options-shb.sops.secret
[secret contract]: contracts-secret.html
[secret contract tests]: @REPO@/test/contracts/secret.nix
As requested by the contract, when asking for a secret with the `shb.sops` module,
the path where the secret will be located can be found under the [`shb.sops.secrets.<name>.result`][result] option.
[result]: #blocks-sops-options-shb.sops.secret._name_.result
## Usage {#blocks-sops-usage}
First, a file with encrypted secrets must be created by following the [secrets setup section](usage.html#usage-secrets).
### With Requester Module {#blocks-sops-usage-requester}
This example shows how to use this sops block
to fulfill the request of a module using the [secret contract][] under the option `services.mymodule.mysecret`.
```nix
```
## Options Reference {#blocks-sops-options}
```{=include=} options
id-prefix: blocks-sops-options-
list-id: selfhostblocks-block-sops-options
source: @OPTIONS_JSON@
```

View file

@ -1,7 +1,7 @@
{ lib, ... }:
let
inherit (lib) mkOption;
inherit (lib.types) anything listOf nonEmptyListOf submodule str;
inherit (lib.types) listOf nonEmptyListOf submodule str;
in
{
request = submodule {
@ -9,8 +9,6 @@ in
user = mkOption {
description = ''
Unix user doing the backups.
Most of the time, this should be the user owning the files.
'';
type = str;
};

View file

@ -16,7 +16,7 @@ in
"/opt/files/B"
],
settings, # { repository, config } -> attrset
extraConfig ? null, # { username } -> attrset
extraConfig ? null, # { username, config } -> attrset
}: pkgs.testers.runNixOSTest {
inherit name;
@ -41,7 +41,7 @@ in
group = "root";
};
})
(optionalAttrs (extraConfig != null) (extraConfig { inherit username; }))
(optionalAttrs (extraConfig != null) (extraConfig { inherit username config; }))
];
};

View file

@ -9,11 +9,10 @@ in
{ name,
requesterRoot,
providerRoot,
extraConfig ? null, # { username, database } -> attrset
extraConfig ? null, # { config, database } -> attrset
modules ? [],
username ? "me",
database ? "me",
settings, # repository -> attrset
settings, # { repository, config } -> attrset
}: pkgs.testers.runNixOSTest {
inherit name;
@ -22,16 +21,19 @@ in
config = lib.mkMerge [
(setAttrByPath providerRoot {
request = (getAttrFromPath requesterRoot config).databasebackup;
settings = settings "/opt/repos/database";
settings = settings {
inherit config;
repository = "/opt/repos/database";
};
})
(mkIf (username != "root") {
users.users.${username} = {
(mkIf (database != "root") {
users.users.${database} = {
isSystemUser = true;
extraGroups = [ "sudoers" ];
group = "root";
};
})
(optionalAttrs (extraConfig != null) (extraConfig { inherit username database; }))
(optionalAttrs (extraConfig != null) (extraConfig { inherit config database; }))
];
};
@ -45,7 +47,7 @@ in
machine.wait_for_open_port(5432)
def peer_cmd(cmd, db="me"):
return "sudo -u me psql -U me {db} --csv --command \"{cmd}\"".format(cmd=cmd, db=db)
return "sudo -u ${database} psql -U ${database} {db} --csv --command \"{cmd}\"".format(cmd=cmd, db=db)
def query(query):
res = machine.succeed(peer_cmd(query))
@ -68,10 +70,11 @@ in
with subtest("backup"):
print(machine.succeed("systemctl cat ${provider.backupService}"))
print(machine.succeed("ls -l /run/hardcodedsecrets/hardcodedsecret_passphrase"))
machine.succeed("systemctl start ${provider.backupService}")
with subtest("drop database"):
machine.succeed(peer_cmd("DROP DATABASE me", db="postgres"))
machine.succeed(peer_cmd("DROP DATABASE ${database}", db="postgres"))
machine.fail(peer_cmd("SELECT * FROM test"))
with subtest("restore"):

View file

@ -1,9 +1,48 @@
{ pkgs, lib }:
let
inherit (lib) mkOption optionalAttrs;
inherit (lib.types) anything;
mkContractFunctions =
{ mkRequest,
mkResult,
}: {
mkRequester = requestCfg: {
request = mkRequest requestCfg;
result = mkResult {};
};
mkProvider =
{ resultCfg,
settings ? {},
}: {
request = mkRequest {};
result = mkResult resultCfg;
} // optionalAttrs (settings != {}) { inherit settings; };
contract = {
request = mkRequest {};
result = mkResult {};
settings = mkOption {
description = ''
Optional attribute set with options specific to the provider.
'';
type = anything;
};
};
};
in
{
inherit mkContractFunctions;
databasebackup = import ./databasebackup.nix { inherit lib; };
backup = import ./backup.nix { inherit lib; };
mount = import ./mount.nix { inherit lib; };
secret = import ./secret.nix { inherit lib; };
secret = import ./secret.nix { inherit pkgs lib; };
ssl = import ./ssl.nix { inherit lib; };
test = {
secret = import ./secret/test.nix { inherit pkgs lib; };

View file

@ -1,114 +1,115 @@
{ lib, ... }:
{
mkOption =
{ description,
mode ? "0400",
{ pkgs, lib, ... }:
let
inherit (lib) concatStringsSep literalMD mkOption optionalAttrs optionalString;
inherit (lib.types) anything listOf submodule str;
contractsLib = import ./default.nix { inherit pkgs lib; };
mkRequest =
{ mode ? "0400",
owner ? "root",
ownerText ? null,
group ? "root",
restartUnits ? [],
restartUnitsText ? null,
}: lib.mkOption {
inherit description;
}: mkOption {
description = ''
Request part of the secret contract.
type = lib.types.submodule {
Options set by the requester module
enforcing some properties the secret should have.
'';
default = {
inherit mode owner group restartUnits;
};
defaultText = optionalString (ownerText != null || restartUnitsText != null) (literalMD ''
{
mode = ${mode};
owner = ${if ownerText != null then ownerText else owner};
group = ${group};
restartUnits = ${if restartUnitsText != null then restartUnitsText else "[ " + concatStringsSep " " restartUnits + " ]"};
}
'');
type = submodule {
options = {
request = lib.mkOption {
default = {
inherit mode owner group restartUnits;
};
defaultText = lib.optionalString (ownerText != null || restartUnitsText != null) (lib.literalMD ''
{
mode = ${mode};
owner = ${if ownerText != null then ownerText else owner};
group = ${group};
restartUnits = ${if restartUnitsText != null then restartUnitsText else "[ " + lib.concatStringsSep " " restartUnits + " ]"};
}
'');
readOnly = true;
mode = mkOption {
description = ''
Options set by the requester module
enforcing some properties the secret should have.
Use the `contracts.secret.mkOption` function to
create a secret option for a requester module.
See the [requester usage section](contracts-secret.html#secret-contract-usage-requester) for an example.
Some providers will need more options to be defined and this is allowed.
These extra options will be set by the user.
For example, the `sops` implementation requires to be given
the sops key in which the secret is encrypted.
`request` options are set read-only
because they must be set through option defaults,
they shouldn't be changed in the `config` section.
This would otherwise lead to infinite recursion
during evaluation.
This is handled automatically when using the `contracts.secret.mkOption` function.
Mode of the secret file.
'';
type = lib.types.submodule {
freeformType = lib.types.anything;
options = {
mode = lib.mkOption {
description = ''
Mode of the secret file.
'';
type = lib.types.str;
default = mode;
};
owner = lib.mkOption {
description = ''
Linux user owning the secret file.
'';
type = lib.types.str;
default = owner;
defaultText = if ownerText != null then lib.literalMD ownerText else null;
};
group = lib.mkOption {
description = ''
Linux group owning the secret file.
'';
type = lib.types.str;
default = group;
};
restartUnits = lib.mkOption {
description = ''
Systemd units to restart after the secret is updated.
'';
type = lib.types.listOf lib.types.str;
default = restartUnits;
defaultText = if restartUnitsText != null then lib.literalMD restartUnitsText else null;
};
};
};
type = str;
default = mode;
};
result = lib.mkOption {
owner = mkOption ({
description = ''
Options set by the provider module that indicates where the secret can be found.
Linux user owning the secret file.
'';
type = lib.types.submodule {
options = {
path = lib.mkOption {
type = lib.types.path;
description = ''
Path to the file containing the secret generated out of band.
type = str;
default = owner;
} // optionalAttrs (ownerText != null) {
defaultText = literalMD ownerText;
});
This path will exist after deploying to a target host,
it is not available through the nix store.
'';
};
};
};
group = mkOption {
description = ''
Linux group owning the secret file.
'';
type = str;
default = group;
};
restartUnits = mkOption ({
description = ''
Systemd units to restart after the secret is updated.
'';
type = listOf str;
default = restartUnits;
} // optionalAttrs (restartUnitsText != null) {
defaultText = literalMD restartUnitsText;
});
};
};
};
mkResult =
{
path ? "/run/secrets/secret",
pathText ? null,
}:
mkOption ({
description = ''
Result part of the secret contract.
Options set by the provider module that indicates where the secret can be found.
'';
default = {
inherit path;
};
type = submodule {
options = {
path = mkOption {
type = lib.types.path;
description = ''
Path to the file containing the secret generated out of band.
This path will exist after deploying to a target host,
it is not available through the nix store.
'';
default = path;
} // optionalAttrs (pathText != null) {
defaultText = pathText;
};
};
};
} // optionalAttrs (pathText != null) {
defaultText = {
path = pathText;
};
});
in
contractsLib.mkContractFunctions {
inherit mkRequest mkResult;
}

View file

@ -7,7 +7,7 @@ and that must be placed in an expected location with expected permission.
More formally, this contract is made between a requester module - the one needing a secret -
and a provider module - the one creating the secret and making it available.
## Problem Statement {#secret-contract-problem}
## Motivation {#secret-contract-motivation}
Let's provide the [ldap SHB module][ldap-module] option `ldapUserPasswordFile`
with a secret managed by [sops-nix][].
@ -19,14 +19,14 @@ Without the secret contract, configuring the option would look like so:
```nix
sops.secrets."ldap/user_password" = {
sopsFile = ./secrets.yaml;
mode = "0440";
owner = "lldap";
group = "lldap";
restartUnits = [ "lldap.service" ];
sopsFile = ./secrets.yaml;
};
shb.ldap.ldapUserPasswordFile = config.sops.secrets."ldap/user_password".path;
shb.ldap.userPassword.result = config.sops.secrets."ldap/user_password".result;
```
The problem this contract intends to fix is how to ensure
@ -38,19 +38,21 @@ or more likely, they will need to figure it out by looking
at the module source code.
Not a great user experience.
Now, with this contract, the configuration becomes:
Now, with this contract, a layer on top of `sops` is added which is found under `shb.sops`.
The configuration then becomes:
```nix
sops.secrets."ldap/user_password" = config.shb.ldap.secret.ldapUserPassword.request // {
sopsFile = ./secrets.yaml;
shb.sops.secrets."ldap/user_password" = {
request = config.shb.ldap.userPassword.request;
settings.sopsFile = ./secrets.yaml;
};
shb.ldap.ldapUserPassword.result.path = config.sops.secrets."ldap/user_password".path;
shb.ldap.userPassword.result = config.shb.sops.secrets."ldap/user_password".result;
```
The issue is now gone at the expense of some plumbing.
The module maintainer is now in charge of describing
how the module expects the secret to be provided.
The issue is now gone as the responsibility falls
on the module maintainer
for describing how the secret should be provided.
If taking advantage of the `sops.defaultSopsFile` option like so:
@ -61,9 +63,9 @@ sops.defaultSopsFile = ./secrets.yaml;
Then the snippet above is even more simplified:
```nix
sops.secrets."ldap/user_password" = config.shb.ldap.secret.ldapUserPassword.request;
shb.sops.secrets."ldap/user_password".request = config.shb.ldap.userPassword.request;
shb.ldap.ldapUserPassword.result.path = config.sops.secrets."ldap/user_password".path;
shb.ldap.userPassword.result = config.shb.sops.secrets."ldap/user_password".result;
```
## Contract Reference {#secret-contract-options}
@ -92,18 +94,22 @@ Here is an example module requesting two secrets through the `secret` contract.
```nix
{ config, ... }:
let
inherit (lib) mkOption;
inherit (lib.types) submodule;
in
{
options = {
myservice = lib.mkOption {
type = lib.types.submodule {
myservice = mkOption {
type = submodule {
options = {
adminPassword = contracts.secret.mkOption {
adminPassword = contracts.secret.mkRequester {
owner = "myservice";
group = "myservice";
mode = "0440";
restartUnits = [ "myservice.service" ];
};
databasePassword = contracts.secret.mkOption {
databasePassword = contracts.secret.mkRequester {
owner = "myservice";
# group defaults to "root"
# mode defaults to "0400"
@ -130,44 +136,43 @@ and that one can create multiple instances.
```nix
{ config, ... }:
let
inherit (lib) mkOption;
inherit (lib.types) attrsOf submodule;
contracts = pkgs.callPackage ./contracts {};
in
{
options = {
secretservice = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
mode = lib.mkOption {
description = "Mode of the secret file.";
type = lib.types.str;
};
options.secretservice.secret = mkOption {
description = "Secret following the secret contract.";
default = {};
type = attrsOf (submodule ({ name, options, ... }: {
options = contracts.secret.mkProvider {
settings = mkOption {
description = ''
Settings specific to the secrets provider.
'';
owner = lib.mkOption {
description = "Linux user owning the secret file.";
type = lib.types.str;
};
group = lib.mkOption {
description = "Linux group owning the secret file.";
type = lib.types.str;
};
restartUnits = lib.mkOption {
description = "Systemd units to restart after the secret is updated.";
type = lib.types.listOf lib.types.str;
};
path = lib.mkOption {
description = "Path where the secret file will be located.";
type = lib.types.str;
};
// The contract allows more options to be defined to accomodate specific implementations.
secretFile = lib.mkOption {
description = "File containing the encrypted secret.";
type = lib.types.path;
type = submodule {
options = {
secretFile = lib.mkOption {
description = "File containing the encrypted secret.";
type = lib.types.path;
};
};
};
};
});
};
resultCfg = {
path = "/run/secrets/${name}";
pathText = "/run/secrets/<name>";
};
};
}));
};
config = {
// ...
};
}
```
@ -178,15 +183,20 @@ The end user's responsibility is now to do some plumbing.
They will setup the provider module - here `secretservice` - with the options set by the requester module,
while also setting other necessary options to satisfy the provider service.
And then they will give back the result to the requester module `myservice`.
```nix
secretservice.adminPassword = myservice.secret.adminPassword.request // {
secretFile = ./secret.yaml;
secretservice.secret."adminPassword" = {
request = myservice.adminPasswor".request;
settings.secretFile = ./secret.yaml;
};
myservice.adminPassword.result = secretservice.secret."adminPassword".result;
secretservice.databasePassword = myservice.secret.databasePassword.request // {
secretFile = ./secret.yaml;
secretservice.secret."databasePassword" = {
request = myservice.databasePassword.request;
settings.secretFile = ./secret.yaml;
};
myservice.databasePassword.result = secretservice.service."databasePassword".result;
```
Assuming the `secretservice` module accepts default options,
@ -195,14 +205,13 @@ the above snippet could be reduced to:
```nix
secretservice.default.secretFile = ./secret.yaml;
secretservice.adminPassword = myservice.secret.adminPassword.request;
secretservice.databasePassword = myservice.secret.databasePassword.request;
secretservice.secret."adminPassword".request = myservice.adminPasswor".request;
myservice.adminPassword.result = secretservice.secret."adminPassword".result;
secretservice.secret."databasePassword".request = myservice.databasePassword.request;
myservice.databasePassword.result = secretservice.service."databasePassword".result;
```
Then they will setup the requester module - here `myservice` - with the result of the provider module.
```nix
myservice.secret.adminPassword.result.path = secretservice.adminPassword.result.path;
myservice.secret.databasePassword.result.path = secretservice.adminPassword.result.path;
```
The plumbing of request from the requester to the provider
and then the result from the provider back to the requester
is quite explicit in this snippet.

View file

@ -1,21 +1,27 @@
{ pkgs, lib, ... }:
let
contracts = pkgs.callPackage ../. {};
inherit (lib) mkOption;
inherit (lib.types) submodule;
in
{
options.shb.contracts.secret = contracts.secret.mkOption {
options.shb.contracts.secret = mkOption {
description = ''
Contract for secrets between a requester module
and a provider module.
The requester communicates to the provider
some properties the secret should have
through the `request` options.
through the `request.*` options.
The provider reads from the `request` options
The provider reads from the `request.*` options
and creates the secret as requested.
It then communicates to the requester where the secret can be found
through the `result` options.
through the `result.*` options.
'';
type = submodule {
options = contracts.secret.contract;
};
};
}

View file

@ -9,7 +9,7 @@ let
in
{ name,
configRoot,
createContent, # config to create a secret with value "secretA".
settingsCfg, # str -> attrset
modules ? [],
owner ? "root",
group ? "root",
@ -23,8 +23,11 @@ in
config = lib.mkMerge [
(setAttrByPath configRoot {
A = {
inherit owner group mode restartUnits;
} // createContent;
request = {
inherit owner group mode restartUnits;
};
settings = settingsCfg "secretA";
};
})
(mkIf (owner != "root") {
users.users.${owner}.isNormalUser = true;
@ -37,26 +40,26 @@ in
testScript = { nodes, ... }:
let
cfg = (getAttrFromPath configRoot nodes.machine)."A";
result = (getAttrFromPath configRoot nodes.machine)."A".result;
in
''
owner = machine.succeed("stat -c '%U' ${cfg.path}").strip()
owner = machine.succeed("stat -c '%U' ${result.path}").strip()
print(f"Got owner {owner}")
if owner != "${owner}":
raise Exception(f"Owner should be '${owner}' but got '{owner}'")
group = machine.succeed("stat -c '%G' ${cfg.path}").strip()
group = machine.succeed("stat -c '%G' ${result.path}").strip()
print(f"Got group {group}")
if group != "${group}":
raise Exception(f"Group should be '${group}' but got '{group}'")
mode = str(int(machine.succeed("stat -c '%a' ${cfg.path}").strip()))
mode = str(int(machine.succeed("stat -c '%a' ${result.path}").strip()))
print(f"Got mode {mode}")
wantedMode = str(int("${mode}"))
if mode != wantedMode:
raise Exception(f"Mode should be '{wantedMode}' but got '{mode}'")
content = machine.succeed("cat ${cfg.path}").strip()
content = machine.succeed("cat ${result.path}").strip()
print(f"Got content {content}")
if content != "secretA":
raise Exception(f"Content should be 'secretA' but got '{content}'")

View file

@ -4,13 +4,16 @@ let
cfg = config.shb.forgejo;
contracts = pkgs.callPackage ../contracts {};
inherit (lib) getExe lists literalExpression mkBefore mkEnableOption mkForce mkIf mkMerge mkOption mkOverride optionals;
inherit (lib.types) bool enum listOf nullOr package port submodule str;
in
{
options.shb.forgejo = {
enable = lib.mkEnableOption "selfhostblocks.forgejo";
enable = mkEnableOption "selfhostblocks.forgejo";
subdomain = lib.mkOption {
type = lib.types.str;
subdomain = mkOption {
type = str;
description = ''
Subdomain under which Forgejo will be served.
@ -21,7 +24,7 @@ in
example = "forgejo";
};
domain = lib.mkOption {
domain = mkOption {
description = ''
Domain under which Forgejo is served.
@ -29,75 +32,79 @@ in
<subdomain>.<domain>[:<port>]
```
'';
type = lib.types.str;
type = str;
example = "domain.com";
};
ssl = lib.mkOption {
ssl = mkOption {
description = "Path to SSL files";
type = lib.types.nullOr contracts.ssl.certs;
type = nullOr contracts.ssl.certs;
default = null;
};
ldap = lib.mkOption {
ldap = mkOption {
description = ''
LDAP Integration.
'';
default = {};
type = lib.types.nullOr (lib.types.submodule {
type = nullOr (submodule {
options = {
enable = lib.mkEnableOption "LDAP integration.";
enable = mkEnableOption "LDAP integration.";
provider = lib.mkOption {
type = lib.types.enum [ "LLDAP" ];
provider = mkOption {
type = enum [ "LLDAP" ];
description = "LDAP provider name, used for display.";
default = "LLDAP";
};
host = lib.mkOption {
type = lib.types.str;
host = mkOption {
type = str;
description = ''
Host serving the LDAP server.
'';
default = "127.0.0.1";
};
port = lib.mkOption {
type = lib.types.port;
port = mkOption {
type = port;
description = ''
Port of the service serving the LDAP server.
'';
default = 389;
};
dcdomain = lib.mkOption {
type = lib.types.str;
dcdomain = mkOption {
type = str;
description = "dc domain for ldap.";
example = "dc=mydomain,dc=com";
};
adminName = lib.mkOption {
type = lib.types.str;
adminName = mkOption {
type = str;
description = "Admin user of the LDAP server.";
default = "admin";
};
adminPassword = contracts.secret.mkOption {
adminPassword = mkOption {
description = "LDAP admin password.";
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
type = submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
};
};
};
userGroup = lib.mkOption {
type = lib.types.str;
userGroup = mkOption {
type = str;
description = "Group users must belong to be able to login.";
default = "forgejo_user";
};
adminGroup = lib.mkOption {
type = lib.types.str;
adminGroup = mkOption {
type = str;
description = "Group users must belong to be admins.";
default = "forgejo_admin";
};
@ -105,81 +112,97 @@ in
});
};
sso = lib.mkOption {
sso = mkOption {
description = ''
Setup SSO integration.
'';
default = {};
type = lib.types.submodule {
type = submodule {
options = {
enable = lib.mkEnableOption "SSO integration.";
enable = mkEnableOption "SSO integration.";
provider = lib.mkOption {
type = lib.types.enum [ "Authelia" ];
provider = mkOption {
type = enum [ "Authelia" ];
description = "OIDC provider name, used for display.";
default = "Authelia";
};
endpoint = lib.mkOption {
type = lib.types.str;
endpoint = mkOption {
type = str;
description = "OIDC endpoint for SSO.";
example = "https://authelia.example.com";
};
clientID = lib.mkOption {
type = lib.types.str;
clientID = mkOption {
type = str;
description = "Client ID for the OIDC endpoint.";
default = "forgejo";
};
authorization_policy = lib.mkOption {
type = lib.types.enum [ "one_factor" "two_factor" ];
authorization_policy = mkOption {
type = enum [ "one_factor" "two_factor" ];
description = "Require one factor (password) or two factor (device) authentication.";
default = "one_factor";
};
sharedSecret = contracts.secret.mkOption {
sharedSecret = mkOption {
description = "OIDC shared secret for Forgejo.";
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
type = submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
};
};
};
sharedSecretForAuthelia = contracts.secret.mkOption {
sharedSecretForAuthelia = mkOption {
description = "OIDC shared secret for Authelia.";
mode = "0400";
owner = "authelia";
type = submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "authelia";
};
};
};
};
};
};
adminPassword = contracts.secret.mkOption {
adminPassword = mkOption {
description = "File containing the Forgejo admin user password.";
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
type = submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
};
};
};
databasePassword = contracts.secret.mkOption {
databasePassword = mkOption {
description = "File containing the Forgejo database password.";
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
type = submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
};
};
};
repositoryRoot = lib.mkOption {
type = lib.types.nullOr lib.types.str;
repositoryRoot = mkOption {
type = nullOr str;
description = "Path where to store the repositories. If null, uses the default under the Forgejo StateDir.";
default = null;
example = "/srv/forgejo";
};
localActionRunner = lib.mkOption {
type = lib.types.bool;
localActionRunner = mkOption {
type = bool;
default = true;
description = ''
Enable local action runner that runs for all labels.
@ -187,8 +210,8 @@ in
};
hostPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
hostPackages = mkOption {
type = listOf package;
default = with pkgs; [
bash
coreutils
@ -199,7 +222,7 @@ in
nodejs
wget
];
defaultText = lib.literalExpression ''
defaultText = literalExpression ''
with pkgs; [
bash
coreutils
@ -217,7 +240,7 @@ in
'';
};
backup = lib.mkOption {
backup = mkOption {
type = contracts.backup.request;
description = ''
Backup configuration. This is an output option.
@ -239,13 +262,13 @@ in
user = options.services.forgejo.user.value;
sourceDirectories = [
options.services.forgejo.dump.backupDir.value
] ++ lib.optionals (cfg.repositoryRoot != null) [
] ++ optionals (cfg.repositoryRoot != null) [
cfg.repositoryRoot
];
};
};
mount = lib.mkOption {
mount = mkOption {
type = contracts.mount;
description = ''
Mount configuration. This is an output option.
@ -263,51 +286,51 @@ in
default = { path = config.services.forgejo.stateDir; };
};
smtp = lib.mkOption {
smtp = mkOption {
description = ''
Send notifications by smtp.
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
type = nullOr (submodule {
options = {
from_address = lib.mkOption {
type = lib.types.str;
from_address = mkOption {
type = str;
description = "SMTP address from which the emails originate.";
example = "authelia@mydomain.com";
};
host = lib.mkOption {
type = lib.types.str;
host = mkOption {
type = str;
description = "SMTP host to send the emails to.";
};
port = lib.mkOption {
type = lib.types.port;
port = mkOption {
type = port;
description = "SMTP port to send the emails to.";
default = 25;
};
username = lib.mkOption {
type = lib.types.str;
username = mkOption {
type = str;
description = "Username to connect to the SMTP host.";
};
passwordFile = lib.mkOption {
type = lib.types.str;
passwordFile = mkOption {
type = str;
description = "File containing the password to connect to the SMTP host.";
};
};
});
};
debug = lib.mkOption {
debug = mkOption {
description = "Enable debug logging.";
type = lib.types.bool;
type = bool;
default = false;
};
};
config = lib.mkMerge [
(lib.mkIf cfg.enable {
config = mkMerge [
(mkIf cfg.enable {
services.forgejo = {
enable = true;
repositoryRoot = lib.mkIf (cfg.repositoryRoot != null) cfg.repositoryRoot;
repositoryRoot = mkIf (cfg.repositoryRoot != null) cfg.repositoryRoot;
settings = {
server = {
DOMAIN = cfg.domain;
@ -328,10 +351,10 @@ in
};
# 1 lower than default, to solve conflict between shb.postgresql and nixpkgs' forgejo module.
services.postgresql.enable = lib.mkOverride 999 true;
services.postgresql.enable = mkOverride 999 true;
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-2271967113
systemd.services.forgejo.serviceConfig.Type = lib.mkForce "exec";
systemd.services.forgejo.serviceConfig.Type = mkForce "exec";
shb.nginx.vhosts = [{
inherit (cfg) domain subdomain ssl;
@ -339,7 +362,7 @@ in
}];
})
(lib.mkIf cfg.enable {
(mkIf cfg.enable {
services.forgejo.database = {
type = "postgres";
@ -347,7 +370,7 @@ in
};
})
(lib.mkIf cfg.enable {
(mkIf cfg.enable {
services.forgejo.dump = {
enable = true;
type = "tar.gz";
@ -358,12 +381,12 @@ in
# For Forgejo setup: https://github.com/lldap/lldap/blob/main/example_configs/gitea.md
# For cli info: https://docs.gitea.com/usage/command-line
# Security protocols in: https://codeberg.org/forgejo/forgejo/src/branch/forgejo/services/auth/source/ldap/security_protocol.go#L27-L31
(lib.mkIf (cfg.enable && cfg.ldap.enable != false) {
(mkIf (cfg.enable && cfg.ldap.enable != false) {
# The delimiter in the `cut` command is a TAB!
systemd.services.forgejo.preStart = let
provider = "SHB-${cfg.ldap.provider}";
in ''
auth="${lib.getExe config.services.forgejo.package} admin auth"
auth="${getExe config.services.forgejo.package} admin auth"
echo "Trying to find existing ldap configuration for ${provider}"...
set +e -o pipefail
@ -417,7 +440,7 @@ in
# For Authelia to Forgejo integration: https://www.authelia.com/integration/openid-connect/gitea/
# For Forgejo config: https://forgejo.org/docs/latest/admin/config-cheat-sheet
# For cli info: https://docs.gitea.com/usage/command-line
(lib.mkIf (cfg.enable && cfg.sso.enable != false) {
(mkIf (cfg.enable && cfg.sso.enable != false) {
services.forgejo.settings = {
oauth2 = {
ENABLED = true;
@ -430,7 +453,7 @@ in
};
service = {
# DISABLE_REGISTRATION = lib.mkForce false;
# DISABLE_REGISTRATION = mkForce false;
# ALLOW_ONLY_EXTERNAL_REGISTRATION = false;
SHOW_REGISTRATION_BUTTON = false;
};
@ -440,7 +463,7 @@ in
systemd.services.forgejo.preStart = let
provider = "SHB-${cfg.sso.provider}";
in ''
auth="${lib.getExe config.services.forgejo.package} admin auth"
auth="${getExe config.services.forgejo.package} admin auth"
echo "Trying to find existing sso configuration for ${provider}"...
set +e -o pipefail
@ -468,7 +491,7 @@ in
fi
'';
shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [
shb.authelia.oidcClients = lists.optionals (!(isNull cfg.sso)) [
(let
provider = "SHB-${cfg.sso.provider}";
in {
@ -482,15 +505,15 @@ in
];
})
(lib.mkIf cfg.enable {
(mkIf cfg.enable {
systemd.services.forgejo.preStart = ''
admin="${lib.getExe config.services.forgejo.package} admin user"
admin="${getExe config.services.forgejo.package} admin user"
$admin create --admin --email "root@localhost" --username meadmin --password "$(tr -d '\n' < ${cfg.adminPassword.result.path})" || true
$admin change-password --username meadmin --password "$(tr -d '\n' < ${cfg.adminPassword.result.path})" || true
'';
})
(lib.mkIf (cfg.enable && cfg.smtp != null) {
(mkIf (cfg.enable && cfg.smtp != null) {
services.forgejo.settings.mailer = {
ENABLED = true;
SMTP_ADDR = "${cfg.smtp.host}:${toString cfg.smtp.port}";
@ -502,13 +525,13 @@ in
})
# https://wiki.nixos.org/wiki/Forgejo#Runner
(lib.mkIf cfg.enable {
(mkIf cfg.enable {
services.forgejo.settings.actions = {
ENABLED = true;
DEFAULT_ACTIONS_URL = "github";
};
services.gitea-actions-runner = lib.mkIf cfg.localActionRunner {
services.gitea-actions-runner = mkIf cfg.localActionRunner {
package = pkgs.forgejo-actions-runner;
instances.local = {
enable = true;
@ -530,9 +553,9 @@ in
# This combined with the next statement takes care of
# automatically registering a forgejo runner.
systemd.services.forgejo.postStart = lib.mkIf cfg.localActionRunner (lib.mkBefore ''
systemd.services.forgejo.postStart = mkIf cfg.localActionRunner (mkBefore ''
${pkgs.bash}/bin/bash -c '(while ! ${pkgs.netcat-openbsd}/bin/nc -z -U ${config.services.forgejo.settings.server.HTTP_ADDR}; do echo "Waiting for unix ${config.services.forgejo.settings.server.HTTP_ADDR} to open..."; sleep 2; done); sleep 2'
actions="${lib.getExe config.services.forgejo.package} actions"
actions="${getExe config.services.forgejo.package} actions"
echo -n TOKEN= > /run/forgejo/forgejo-runner-token
$actions generate-runner-token >> /run/forgejo/forgejo-runner-token
'');

View file

@ -15,7 +15,7 @@ LDAP and SSO integration as well as one local runner.
- Declarative [local runner](#services-forgejo-options-shb.forgejo.localActionRunner) Configuration.
- Access through [subdomain](#services-forgejo-options-shb.forgejo.subdomain) using reverse proxy. [Manual](#services-forgejo-usage-basic).
- Access through [HTTPS](#services-forgejo-options-shb.forgejo.ssl) using reverse proxy. [Manual](#services-forgejo-usage-basic).
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html) with the . [Manual](#services-forgejo-usage-backup).
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html). [Manual](#services-forgejo-usage-backup).
## Usage {#services-forgejo-usage}
@ -44,7 +44,7 @@ Then you can use that secret:
shb.forgejo.adminPasswordFile = config.sops.secrets."forgejo/adminPasswordFile".path;
```
### Forgejo through HTTP(S) {#services-forgejo-usage-basic}
### Forgejo through HTTPS {#services-forgejo-usage-basic}
This will set up a Forgejo service that runs on the NixOS target machine,
reachable at `http://forgejo.example.com`.
@ -60,25 +60,22 @@ shb.forgejo = {
If the `shb.ssl` block is used (see [manual](blocks-ssl.html#usage) on how to set it up),
the instance will be reachable at `https://fogejo.example.com`.
Here is an example with self-signed certificates:
Here is an example with Let's Encrypt certificates, validated using the HTTP method:
```nix
shb.certs = {
cas.selfsigned.myca = {
name = "My CA";
};
certs.selfsigned = {
foregejo = {
ca = config.shb.certs.cas.selfsigned.myca;
domain = "forgejo.example.com";
};
};
shb.certs.certs.letsencrypt."example.com" = {
domain = "example.com";
group = "nginx";
reloadServices = [ "nginx.service" ];
adminEmail = "myemail@mydomain.com";
};
```
Then you can tell Forgejo to use those certificates.
```nix
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "forgejo.example.com" ];
shb.forgejo = {
ssl = config.shb.certs.certs.selfsigned.forgejo;
};
@ -87,7 +84,7 @@ shb.forgejo = {
### With LDAP Support {#services-forgejo-usage-ldap}
:::: {.note}
We will build upon the [Forgejo through HTTP(S)](#services-forgejo-usage-basic) section,
We will build upon the [HTTPS](#services-forgejo-usage-basic) section,
so please follow that first.
::::
@ -99,12 +96,18 @@ shb.ldap = {
enable = true;
domain = "example.com";
subdomain = "ldap";
ssl = config.shb.certs.certs.letsencrypt."example.com";
ldapPort = 3890;
webUIListenPort = 17170;
dcdomain = "dc=example,dc=com";
ldapUserPasswordFile = <path/to/ldapUserPasswordSecret>;
jwtSecretFile = <path/to/ldapJwtSecret>;
ldapUserPassword.result = config.shb.sops.secrets."ldap/userPassword".result;
jwtSecret.result = config.shb.sops.secrets."ldap/jwtSecret".result;
};
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "ldap.example.com" ];
shb.sops.secrets."ldap/userPassword".request = config.shb.ldap.userPassword.request;
shb.sops.secrets."ldap/jwtSecret".request = config.shb.ldap.jwtSecret.request;
```
We also need to configure the `forgejo` service
@ -116,7 +119,12 @@ shb.forgejo.ldap
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPasswordFile = <path/to/ldapUserPasswordSecret>;
adminPassword.result = config.shb.sops.secrets."forgejo/ldap/adminPassword".result
};
shb.sops.secrets."forgejo/ldap/adminPassword" = {
request = config.shb.forgejo.ldap.adminPassword.request;
settings.key = "ldap/userPassword";
};
```
@ -135,29 +143,10 @@ When that's done, go back to the Forgejo server at
### With SSO Support {#services-forgejo-usage-sso}
:::: {.note}
We will build upon the [With LDAP Support](#services-forgejo-usage-ldap) section,
We will build upon the [LDAP](#services-forgejo-usage-ldap) section,
so please follow that first.
::::
Here though, we must setup SSL certificates
because the SSO provider only works with the https protocol.
Let's add self-signed certificates for Authelia and LLDAP:
```nix
shb.certs = {
certs.selfsigned = {
auth = {
ca = config.shb.certs.cas.selfsigned.myca;
domain = "auth.example.com";
};
ldap = {
ca = config.shb.certs.cas.selfsigned.myca;
domain = "ldap.example.com";
};
};
};
```
We then need to setup the SSO provider,
here Authelia thanks to the corresponding SHB block:
@ -166,21 +155,31 @@ shb.authelia = {
enable = true;
domain = "example.com";
subdomain = "auth";
ssl = config.shb.certs.certs.selfsigned.auth;
ssl = config.shb.certs.certs.letsencrypt."example.com";
ldapHostname = "127.0.0.1";
ldapPort = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
secrets = {
jwtSecretFile = <path/to/autheliaJwtSecret>;
ldapAdminPasswordFile = <path/to/ldapUserPasswordSecret>;
sessionSecretFile = <path/to/autheliaSessionSecret>;
storageEncryptionKeyFile = <path/to/autheliaStorageEncryptionKeySecret>;
identityProvidersOIDCHMACSecretFile = <path/to/providersOIDCHMACSecret>;
identityProvidersOIDCIssuerPrivateKeyFile = <path/to/providersOIDCIssuerSecret>;
jwtSecret.result = config.shb.sops.secrets."authelia/jwt_secret".result;
ldapAdminPassword.result = config.shb.sops.secrets."authelia/ldap_admin_password".result;
sessionSecret.result = config.shb.sops.secrets."authelia/session_secret".result;
storageEncryptionKey.result = config.shb.sops.secrets."authelia/storage_encryption_key".result;
identityProvidersOIDCHMACSecret.result = config.shb.sops.secrets."authelia/hmac_secret".result;
identityProvidersOIDCIssuerPrivateKey.result = config.shb.sops.secrets."authelia/private_key".result;
};
};
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "auth.example.com" ];
shb.sops.secrets."authelia/jwt_secret".request = config.shb.authelia.secrets.jwtSecret.request;
shb.sops.secrets."authelia/ldap_admin_password".request = config.shb.authelia.secrets.ldapAdminPassword.request;
shb.sops.secrets."authelia/session_secret".request = config.shb.authelia.secrets.sessionSecret.request;
shb.sops.secrets."authelia/storage_encryption_key".request = config.shb.authelia.secrets.storageEncryptionKey.request;
shb.sops.secrets."authelia/hmac_secret".request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
shb.sops.secrets."authelia/private_key".request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
shb.sops.secrets."authelia/smtp_password".request = config.shb.authelia.smtp.password.request;
```
The `shb.authelia.secrets.ldapAdminPasswordFile` must be the same

View file

@ -67,12 +67,16 @@ in
default = "jellyfin_admin";
};
adminPassword = contracts.secret.mkOption {
adminPassword = lib.mkOption {
description = "LDAP admin password.";
mode = "0440";
owner = "jellyfin";
group = "jellyfin";
restartUnits = [ "jellyfin.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "jellyfin";
group = "jellyfin";
restartUnits = [ "jellyfin.service" ];
};
};
};
};
};
@ -121,18 +125,26 @@ in
default = "one_factor";
};
sharedSecret = contracts.secret.mkOption {
sharedSecret = lib.mkOption {
description = "OIDC shared secret for Jellyfin.";
mode = "0440";
owner = "jellyfin";
group = "jellyfin";
restartUnits = [ "jellyfin.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "jellyfin";
group = "jellyfin";
restartUnits = [ "jellyfin.service" ];
};
};
};
sharedSecretForAuthelia = contracts.secret.mkOption {
sharedSecretForAuthelia = lib.mkOption {
description = "OIDC shared secret for Authelia.";
mode = "0400";
owner = config.shb.authelia.autheliaUser;
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = config.shb.authelia.autheliaUser;
};
};
};
};
};

View file

@ -95,13 +95,18 @@ in
default = "root";
};
adminPass = contracts.secret.mkOption {
adminPass = lib.mkOption {
description = "Nextcloud admin password.";
mode = "0400";
owner = "nextcloud";
restartUnits = [ "phpfpm-nextcloud.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "nextcloud";
restartUnits = [ "phpfpm-nextcloud.service" ];
};
};
};
maxUploadSize = lib.mkOption {
default = "4G";
type = lib.types.str;
@ -374,13 +379,18 @@ in
default = "admin";
};
adminPassword = contracts.secret.mkOption {
adminPassword = lib.mkOption {
description = "LDAP server admin password.";
mode = "0400";
owner = "nextcloud";
restartUnits = [ "phpfpm-nextcloud.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "nextcloud";
restartUnits = [ "phpfpm-nextcloud.service" ];
};
};
};
userGroup = lib.mkOption {
type = lib.types.str;
description = "Group users must belong to to be able to login to Nextcloud.";
@ -441,19 +451,29 @@ in
default = "one_factor";
};
secret = contracts.secret.mkOption {
secret = lib.mkOption {
description = "OIDC shared secret.";
mode = "0400";
owner = "nextcloud";
restartUnits = [ "phpfpm-nextcloud.service" ];
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "nextcloud";
restartUnits = [ "phpfpm-nextcloud.service" ];
};
};
};
secretForAuthelia = contracts.secret.mkOption {
secretForAuthelia = lib.mkOption {
description = "OIDC shared secret. Content must be the same as `secretFile` option.";
mode = "0400";
owner = "authelia";
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "authelia";
};
};
};
fallbackDefaultAuth = lib.mkOption {
type = lib.types.bool;
description = ''

View file

@ -11,8 +11,8 @@ It is based on the nixpkgs Nextcloud server and provides opinionated defaults.
to configure those with the UI.
- [LDAP](#services-nextcloud-server-usage-ldap) app:
enables app and sets up integration with an existing LDAP server, in this case LLDAP.
- [OIDC](#services-nextcloud-server-usage-oidc) app:
enables app and sets up integration with an existing OIDC server, in this case Authelia.
- [SSO](#services-nextcloud-server-usage-oidc) app:
enables app and sets up integration with an existing SSO server, in this case Authelia.
- [Preview Generator](#services-nextcloud-server-usage-previewgenerator) app:
enables app and sets up required cron job.
- [External Storage](#services-nextcloud-server-usage-externalstorage) app:
@ -66,10 +66,10 @@ shb.nextcloud = {
domain = "example.com";
subdomain = "n";
defaultPhoneRegion = "US";
adminPass.result.path = config.sops.secrets."nextcloud/adminpass".path;
adminPass.result = config.shb.sops.secrets."nextcloud/adminpass".result;
};
sops.secrets."nextcloud/adminpass" = config.shb.nextcloud.adminPass.request;
shb.sops.secrets."nextcloud/adminpass".request = config.shb.nextcloud.adminPass.request;
```
This assumes secrets are setup with SOPS as mentioned in [the secrets setup section](usage.html#usage-secrets) of the manual.
@ -154,15 +154,18 @@ shb.ldap = {
enable = true;
domain = "example.com";
subdomain = "ldap";
ssl = config.shb.certs.certs.letsencrypt."example.com";
ldapPort = 3890;
webUIListenPort = 17170;
dcdomain = "dc=example,dc=com";
ldapUserPassword.result.path = config.sops.secrets."ldap/userPassword".path;
jwtSecret.result.path = config.sops.secrets."ldap/jwtSecret".path;
ldapUserPassword.result = config.shb.sops.secrets."ldap/userPassword".result;
jwtSecret.result = config.shb.sops.secrets."ldap/jwtSecret".result;
};
sops.secrets."ldap/userPassword" = config.shb.ldap.userPassword.request;
sops.secrets."ldap/jwtSecret" = config.shb.ldap.jwtSecret.request;
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "ldap.example.com" ];
shb.sops.secrets."ldap/userPassword".request = config.shb.ldap.userPassword.request;
shb.sops.secrets."ldap/jwtSecret".request = config.shb.ldap.jwtSecret.request;
```
On the `nextcloud` module side, we need to configure it to talk to the LDAP server we
@ -175,12 +178,13 @@ shb.nextcloud.apps.ldap = {
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminName = "admin";
adminPassword.result.path = config.sops.secrets."nextcloud/ldapUserPassword".path
adminPassword.result = config.shb.sops.secrets."nextcloud/ldap/adminPassword".result
userGroup = "nextcloud_user";
};
sops.secrets."nextcloud/ldapUserPassword" = config.shb.nextcloud.adminPasswordFile.request // {
key = "ldap/userPassword";
shb.sops.secrets."nextcloud/ldap/adminPassword" = {
request = config.shb.nextcloud.apps.ldap.adminPassword.request;
settings.key = "ldap/userPassword";
};
```
@ -202,7 +206,7 @@ so you need to create a normal user like above,
login with it once so it is known to Nextcloud, then logout,
login with the admin Nextcloud user and promote that new user to admin level.
### With OIDC Support {#services-nextcloud-server-usage-oidc}
### With SSO Support {#services-nextcloud-server-usage-oidc}
:::: {.note}
This section corresponds to the `sso` section of the [Nextcloud
@ -230,26 +234,28 @@ shb.authelia = {
port = 587;
username = "postmaster@mg.example.com";
from_address = "authelia@example.com";
password.result.path = config.sops.secrets."authelia/smtp_password".path;
password.result = config.shb.sops.secrets."authelia/smtp_password".result;
};
secrets = {
jwtSecret.result.path = config.sops.secrets."authelia/jwt_secret".path;
ldapAdminPassword.result.path = config.sops.secrets."authelia/ldap_admin_password".path;
sessionSecret.result.path = config.sops.secrets."authelia/session_secret".path;
storageEncryptionKey.result.path = config.sops.secrets."authelia/storage_encryption_key".path;
identityProvidersOIDCHMACSecret.result.path = config.sops.secrets."authelia/hmac_secret".path;
identityProvidersOIDCIssuerPrivateKey.result.path = config.sops.secrets."authelia/private_key".path;
jwtSecret.result = config.shb.sops.secrets."authelia/jwt_secret".result;
ldapAdminPassword.result = config.shb.sops.secrets."authelia/ldap_admin_password".result;
sessionSecret.result = config.shb.sops.secrets."authelia/session_secret".result;
storageEncryptionKey.result = config.shb.sops.secrets."authelia/storage_encryption_key".result;
identityProvidersOIDCHMACSecret.result = config.shb.sops.secrets."authelia/hmac_secret".result;
identityProvidersOIDCIssuerPrivateKey.result = config.shb.sops.secrets."authelia/private_key".result;
};
};
sops.secrets."authelia/jwt_secret" = config.shb.authelia.secrets.jwtSecret.request;
sops.secrets."authelia/ldap_admin_password" = config.shb.authelia.secrets.ldapAdminPassword.request;
sops.secrets."authelia/session_secret" = config.shb.authelia.secrets.sessionSecret.request;
sops.secrets."authelia/storage_encryption_key" = config.shb.authelia.secrets.storageEncryptionKey.request;
sops.secrets."authelia/hmac_secret" = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
sops.secrets."authelia/private_key" = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
sops.secrets."authelia/smtp_password" = config.shb.authelia.smtp.password.request;
shb.certs.certs.letsencrypt."example.com".extraDomains = [ "auth.example.com" ];
shb.sops.secrets."authelia/jwt_secret".request = config.shb.authelia.secrets.jwtSecret.request;
shb.sops.secrets."authelia/ldap_admin_password".request = config.shb.authelia.secrets.ldapAdminPassword.request;
shb.sops.secrets."authelia/session_secret".request = config.shb.authelia.secrets.sessionSecret.request;
shb.sops.secrets."authelia/storage_encryption_key".request = config.shb.authelia.secrets.storageEncryptionKey.request;
shb.sops.secrets."authelia/hmac_secret".request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
shb.sops.secrets."authelia/private_key".request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
shb.sops.secrets."authelia/smtp_password".request = config.shb.authelia.smtp.password.request;
```
The secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
@ -263,13 +269,14 @@ shb.nextcloud.apps.sso = {
clientID = "nextcloud";
fallbackDefaultAuth = false;
secret.result.path = config.sops.secrets."nextcloud/sso/secret".path;
secretForAuthelia.result.path = config.sops.secrets."nextcloud/sso/secretForAuthelia".path;
secret.result = config.shb.sops.secrets."nextcloud/sso/secret".result;
secretForAuthelia.result = config.shb.sops.secrets."nextcloud/sso/secretForAuthelia".result;
};
sops.secret."nextcloud/sso/secret" = config.shb.nextcloud.apps.sso.secret.request;
sops.secret."nextcloud/sso/secretForAuthelia" = config.shb.nextcloud.apps.sso.secretForAuthelia.request // {
key = "nextcloud/sso/secret";
shb.sops.secret."nextcloud/sso/secret".request = config.shb.nextcloud.apps.sso.secret.request;
shb.sops.secret."nextcloud/sso/secretForAuthelia" = {
request = config.shb.nextcloud.apps.sso.secretForAuthelia.request;
settings.key = "nextcloud/sso/secret";
};
```

View file

@ -45,9 +45,16 @@ in
example = "https://authelia.example.com";
};
databasePasswordFile = lib.mkOption {
type = lib.types.path;
description = "File containing the password to connect to the postgresql database.";
databasePassword = lib.mkOption {
description = "File containing the Vaultwarden database password.";
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0440";
owner = "vaultwarden";
group = "postgres";
restartUnits = [ "vaultwarden.service" "postgresql.service" ];
};
};
};
smtp = lib.mkOption {
@ -88,9 +95,15 @@ in
description = "Auth mechanism.";
default = "Login";
};
passwordFile = lib.mkOption {
type = lib.types.str;
password = lib.mkOption {
description = "File containing the password to connect to the SMTP host.";
type = lib.types.submodule {
options = contracts.secret.mkRequester {
mode = "0400";
owner = "vaultwarden";
restartUnits = [ "vaultwarden.service" ];
};
};
};
};
});
@ -187,10 +200,10 @@ in
systemd.services.vaultwarden.preStart =
shblib.replaceSecrets {
userConfig = {
DATABASE_URL.source = cfg.databasePasswordFile;
DATABASE_URL.source = cfg.databasePassword.result.path;
DATABASE_URL.transform = v: "postgresql://vaultwarden:${v}@127.0.0.1:5432/vaultwarden";
} // lib.optionalAttrs (cfg.smtp != null) {
SMTP_PASSWORD.source = cfg.smtp.passwordFile;
SMTP_PASSWORD.source = cfg.smtp.password.result.path;
};
resultPath = "${dataFolder}/vaultwarden.env";
generator = name: v: pkgs.writeText "template" (lib.generators.toINIWithGlobalSection {} { globalSection = v; });
@ -224,7 +237,7 @@ in
{
username = "vaultwarden";
database = "vaultwarden";
passwordFile = builtins.toString cfg.databasePasswordFile;
passwordFile = cfg.databasePassword.result.path;
}
];
# TODO: make this work.

View file

@ -33,8 +33,17 @@ in
dcdomain = "dc=example,dc=com";
subdomain = "ldap";
domain = "machine.com";
ldapUserPassword.result.path = pkgs.writeText "user_password" ldapAdminPassword;
jwtSecret.result.path = pkgs.writeText "jwt_secret" "securejwtsecret";
ldapUserPassword.result = config.shb.hardcodedsecret.ldapUserPassword.result;
jwtSecret.result = config.shb.hardcodedsecret.jwtSecret.result;
};
shb.hardcodedsecret.ldapUserPassword = {
request = config.shb.ldap.ldapUserPassword.request;
settings.content = ldapAdminPassword;
};
shb.hardcodedsecret.jwtSecret = {
request = config.shb.ldap.jwtSecret.request;
settings.content = "jwtsecret";
};
shb.authelia = {
@ -45,12 +54,12 @@ in
ldapPort = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
secrets = {
jwtSecret.result.path = config.shb.hardcodedsecret.autheliaJwtSecret.path;
ldapAdminPassword.result.path = config.shb.hardcodedsecret.ldapAdminPassword.path;
sessionSecret.result.path = config.shb.hardcodedsecret.sessionSecret.path;
storageEncryptionKey.result.path = config.shb.hardcodedsecret.storageEncryptionKey.path;
identityProvidersOIDCHMACSecret.result.path = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.path;
identityProvidersOIDCIssuerPrivateKey.result.path = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.path;
jwtSecret.result = config.shb.hardcodedsecret.autheliaJwtSecret.result;
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
};
oidcClients = [
@ -73,23 +82,29 @@ in
];
};
shb.hardcodedsecret.autheliaJwtSecret = config.shb.authelia.secrets.jwtSecret.request // {
content = "jwtSecret";
shb.hardcodedsecret.autheliaJwtSecret = {
request = config.shb.authelia.secrets.jwtSecret.request;
settings.content = "jwtSecret";
};
shb.hardcodedsecret.ldapAdminPassword = config.shb.authelia.secrets.ldapAdminPassword.request // {
content = ldapAdminPassword;
shb.hardcodedsecret.ldapAdminPassword = {
request = config.shb.authelia.secrets.ldapAdminPassword.request;
settings.content = ldapAdminPassword;
};
shb.hardcodedsecret.sessionSecret = config.shb.authelia.secrets.sessionSecret.request // {
content = "sessionSecret";
shb.hardcodedsecret.sessionSecret = {
request = config.shb.authelia.secrets.sessionSecret.request;
settings.content = "sessionSecret";
};
shb.hardcodedsecret.storageEncryptionKey = config.shb.authelia.secrets.storageEncryptionKey.request // {
content = "storageEncryptionKey";
shb.hardcodedsecret.storageEncryptionKey = {
request = config.shb.authelia.secrets.storageEncryptionKey.request;
settings.content = "storageEncryptionKey";
};
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request // {
content = "identityProvidersOIDCHMACSecret";
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = {
request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
settings.content = "identityProvidersOIDCHMACSecret";
};
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request // {
source = (pkgs.runCommand "gen-private-key" {} ''
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
settings.source = (pkgs.runCommand "gen-private-key" {} ''
mkdir $out
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
'') + "/private.pem";

View file

@ -1,6 +1,8 @@
{ pkgs, lib, ... }:
let
pkgs' = pkgs;
password = "securepassword";
in
{
auth = pkgs.testers.runNixOSTest {
@ -15,6 +17,7 @@ in
shb.ssl.enable = lib.mkEnableOption "ssl";
};
}
../../modules/blocks/hardcodedsecret.nix
../../modules/blocks/ldap.nix
];
@ -23,10 +26,19 @@ in
dcdomain = "dc=example,dc=com";
subdomain = "ldap";
domain = "example.com";
ldapUserPassword.result.path = pkgs.writeText "user_password" "securepw";
jwtSecret.result.path = pkgs.writeText "jwt_secret" "securejwtsecret";
ldapUserPassword.result = config.shb.hardcodedsecret.ldapUserPassword.result;
jwtSecret.result = config.shb.hardcodedsecret.jwtSecret.result;
debug = true;
};
shb.hardcodedsecret.ldapUserPassword = {
request = config.shb.ldap.ldapUserPassword.request;
settings.content = password;
};
shb.hardcodedsecret.jwtSecret = {
request = config.shb.ldap.jwtSecret.request;
settings.content = "jwtSecret";
};
networking.firewall.allowedTCPPorts = [ 80 ]; # nginx port
};
@ -63,7 +75,7 @@ in
+ """ -H "Content-type: application/json" """
+ """ -H "Host: ldap.example.com" """
+ " http://server/auth/simple/login "
+ """ -d '{"username": "admin", "password": "securepw"}' """
+ """ -d '{"username": "admin", "password": "${password}"}' """
))['token']
data = json.loads(client.succeed(

View file

@ -19,23 +19,32 @@ let
];
shb.hardcodedsecret.A = {
owner = "root";
group = "keys";
mode = "0440";
content = "secretA";
request = {
owner = "root";
group = "keys";
mode = "0440";
};
settings.content = "secretA";
};
shb.hardcodedsecret.B = {
owner = "root";
group = "keys";
mode = "0440";
content = "secretB";
request = {
owner = "root";
group = "keys";
mode = "0440";
};
settings.content = "secretB";
};
shb.hardcodedsecret.passphrase = {
request = config.shb.restic.instances."testinstance".settings.passphrase.request;
settings.content = "secretB";
};
shb.restic.instances."testinstance" = {
settings = {
enable = true;
passphrase.result.path = pkgs.writeText "passphrase" "PassPhrase";
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
repository = {
path = "/opt/repos/A";
@ -46,8 +55,8 @@ let
# Those are not needed by the repository but are still included
# so we can test them in the hooks section.
secrets = {
A.source = config.shb.hardcodedsecret.A.path;
B.source = config.shb.hardcodedsecret.B.path;
A.source = config.shb.hardcodedsecret.A.result.path;
B.source = config.shb.hardcodedsecret.B.result.path;
};
};
};

View file

@ -146,11 +146,13 @@ in
"127.0.0.1" = [ "ldap.${domain}" ];
};
shb.hardcodedsecret.ldapUserPassword = config.shb.ldap.ldapUserPassword.request // {
content = "ldapUserPassword";
shb.hardcodedsecret.ldapUserPassword = {
request = config.shb.ldap.ldapUserPassword.request;
settings.content = "ldapUserPassword";
};
shb.hardcodedsecret.jwtSecret = config.shb.ldap.jwtSecret.request // {
content = "jwtSecrets";
shb.hardcodedsecret.jwtSecret = {
request = config.shb.ldap.jwtSecret.request;
settings.content = "jwtSecrets";
};
shb.ldap = {
@ -160,8 +162,8 @@ in
ldapPort = 3890;
webUIListenPort = 17170;
dcdomain = "dc=example,dc=com";
ldapUserPassword.result.path = config.shb.hardcodedsecret.ldapUserPassword.path;
jwtSecret.result.path = config.shb.hardcodedsecret.jwtSecret.path;
ldapUserPassword.result = config.shb.hardcodedsecret.ldapUserPassword.result;
jwtSecret.result = config.shb.hardcodedsecret.jwtSecret.result;
};
};
@ -185,32 +187,38 @@ in
dcdomain = config.shb.ldap.dcdomain;
secrets = {
jwtSecret.result.path = config.shb.hardcodedsecret.autheliaJwtSecret.path;
ldapAdminPassword.result.path = config.shb.hardcodedsecret.ldapAdminPassword.path;
sessionSecret.result.path = config.shb.hardcodedsecret.sessionSecret.path;
storageEncryptionKey.result.path = config.shb.hardcodedsecret.storageEncryptionKey.path;
identityProvidersOIDCHMACSecret.result.path = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.path;
identityProvidersOIDCIssuerPrivateKey.result.path = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.path;
jwtSecret.result = config.shb.hardcodedsecret.autheliaJwtSecret.result;
ldapAdminPassword.result = config.shb.hardcodedsecret.ldapAdminPassword.result;
sessionSecret.result = config.shb.hardcodedsecret.sessionSecret.result;
storageEncryptionKey.result = config.shb.hardcodedsecret.storageEncryptionKey.result;
identityProvidersOIDCHMACSecret.result = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.result;
identityProvidersOIDCIssuerPrivateKey.result = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.result;
};
};
shb.hardcodedsecret.autheliaJwtSecret = config.shb.authelia.secrets.jwtSecret.request // {
content = "jwtSecret";
shb.hardcodedsecret.autheliaJwtSecret = {
request = config.shb.authelia.secrets.jwtSecret.request;
settings.content = "jwtSecret";
};
shb.hardcodedsecret.ldapAdminPassword = config.shb.authelia.secrets.ldapAdminPassword.request // {
content = "ldapUserPassword";
shb.hardcodedsecret.ldapAdminPassword = {
request = config.shb.authelia.secrets.ldapAdminPassword.request;
settings.content = "ldapUserPassword";
};
shb.hardcodedsecret.sessionSecret = config.shb.authelia.secrets.sessionSecret.request // {
content = "sessionSecret";
shb.hardcodedsecret.sessionSecret = {
request = config.shb.authelia.secrets.sessionSecret.request;
settings.content = "sessionSecret";
};
shb.hardcodedsecret.storageEncryptionKey = config.shb.authelia.secrets.storageEncryptionKey.request // {
content = "storageEncryptionKey";
shb.hardcodedsecret.storageEncryptionKey = {
request = config.shb.authelia.secrets.storageEncryptionKey.request;
settings.content = "storageEncryptionKey";
};
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request // {
content = "identityProvidersOIDCHMACSecret";
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = {
request = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request;
settings.content = "identityProvidersOIDCHMACSecret";
};
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request // {
source = (pkgs.runCommand "gen-private-key" {} ''
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = {
request = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request;
settings.source = (pkgs.runCommand "gen-private-key" {} ''
mkdir $out
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
'') + "/private.pem";

View file

@ -1,4 +1,4 @@
{ pkgs, ... }:
{ pkgs, lib, ... }:
let
contracts = pkgs.callPackage ../../modules/contracts {};
in
@ -13,7 +13,7 @@ in
];
settings = { repository, config, ... }: {
enable = true;
passphrase.result.path = config.shb.hardcodedsecret.passphrase.path;
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
repository = {
path = repository;
timerConfig = {
@ -21,16 +21,16 @@ in
};
};
};
extraConfig = { username, ... }: {
extraConfig = { username, config, ... }: {
shb.hardcodedsecret.passphrase = {
owner = username;
content = "passphrase";
request = config.shb.restic.instances."mytest".settings.passphrase.request;
settings.content = "passphrase";
};
};
};
restic_me = contracts.test.backup {
name = "restic_me";
restic_nonroot = contracts.test.backup {
name = "restic_nonroot";
username = "me";
providerRoot = [ "shb" "restic" "instances" "mytest" ];
modules = [
@ -39,7 +39,7 @@ in
];
settings = { repository, config, ... }: {
enable = true;
passphrase.result.path = config.shb.hardcodedsecret.passphrase.path;
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
repository = {
path = repository;
timerConfig = {
@ -47,10 +47,10 @@ in
};
};
};
extraConfig = { username, ... }: {
extraConfig = { username, config, ... }: {
shb.hardcodedsecret.passphrase = {
owner = username;
content = "passphrase";
request = config.shb.restic.instances."mytest".settings.passphrase.request;
settings.content = "passphrase";
};
};
};

View file

@ -10,10 +10,11 @@ in
modules = [
../../modules/blocks/postgresql.nix
../../modules/blocks/restic.nix
../../modules/blocks/hardcodedsecret.nix
];
settings = repository: {
settings = { repository, config, ... }: {
enable = true;
passphrase.result.path = pkgs.writeText "passphrase" "PassPhrase";
passphrase.result = config.shb.hardcodedsecret.passphrase.result;
repository = {
path = repository;
timerConfig = {
@ -21,12 +22,17 @@ in
};
};
};
extraConfig = { username, database, ... }: {
extraConfig = { config, database, ... }: {
shb.postgresql.ensures = [
{
inherit username database;
inherit database;
username = database;
}
];
shb.hardcodedsecret.passphrase = {
request = config.shb.restic.databases.postgresql.settings.passphrase.request;
settings.content = "passphrase";
};
};
};
}

View file

@ -7,8 +7,8 @@ in
name = "hardcoded";
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
configRoot = [ "shb" "hardcodedsecret" ];
createContent = {
content = "secretA";
settingsCfg = secret: {
content = secret;
};
};
@ -16,8 +16,8 @@ in
name = "hardcoded";
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
configRoot = [ "shb" "hardcodedsecret" ];
createContent = {
content = "secretA";
settingsCfg = secret: {
content = secret;
};
owner = "user";
group = "group";

View file

@ -34,8 +34,8 @@ let
enable = true;
inherit domain subdomain;
adminPassword.result.path = config.shb.hardcodedsecret.forgejoAdminPassword.path;
databasePassword.result.path = config.shb.hardcodedsecret.forgejoDatabasePassword.path;
adminPassword.result = config.shb.hardcodedsecret.forgejoAdminPassword.result;
databasePassword.result = config.shb.hardcodedsecret.forgejoDatabasePassword.result;
};
# Needed for gitea-runner-local to be able to ping forgejo.
@ -43,12 +43,14 @@ let
"127.0.0.1" = [ "${subdomain}.${domain}" ];
};
shb.hardcodedsecret.forgejoAdminPassword = config.shb.forgejo.adminPassword.request // {
content = adminPassword;
shb.hardcodedsecret.forgejoAdminPassword = {
request = config.shb.forgejo.adminPassword.request;
settings.content = adminPassword;
};
shb.hardcodedsecret.forgejoDatabasePassword = config.shb.forgejo.databasePassword.request // {
content = "databasePassword";
shb.hardcodedsecret.forgejoDatabasePassword = {
request = config.shb.forgejo.databasePassword.request;
settings.content = "databasePassword";
};
};
@ -65,12 +67,13 @@ let
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPassword.result.path = config.shb.hardcodedsecret.forgejoLdapUserPassword.path;
adminPassword.result = config.shb.hardcodedsecret.forgejoLdapUserPassword.result;
};
};
shb.hardcodedsecret.forgejoLdapUserPassword = config.shb.forgejo.ldap.adminPassword.request // {
content = "ldapUserPassword";
shb.hardcodedsecret.forgejoLdapUserPassword = {
request = config.shb.forgejo.ldap.adminPassword.request;
settings.content = "ldapUserPassword";
};
};
@ -79,17 +82,19 @@ let
sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
sharedSecret.result.path = config.shb.hardcodedsecret.forgejoSSOPassword.path;
sharedSecretForAuthelia.result.path = config.shb.hardcodedsecret.forgejoSSOPasswordAuthelia.path;
sharedSecret.result = config.shb.hardcodedsecret.forgejoSSOPassword.result;
sharedSecretForAuthelia.result = config.shb.hardcodedsecret.forgejoSSOPasswordAuthelia.result;
};
};
shb.hardcodedsecret.forgejoSSOPassword = config.shb.forgejo.sso.sharedSecret.request // {
content = "ssoPassword";
shb.hardcodedsecret.forgejoSSOPassword = {
request = config.shb.forgejo.sso.sharedSecret.request;
settings.content = "ssoPassword";
};
shb.hardcodedsecret.forgejoSSOPasswordAuthelia = config.shb.forgejo.sso.sharedSecretForAuthelia.request // {
content = "ssoPassword";
shb.hardcodedsecret.forgejoSSOPasswordAuthelia = {
request = config.shb.forgejo.sso.sharedSecretForAuthelia.request;
settings.content = "ssoPassword";
};
};
in

View file

@ -43,12 +43,13 @@ let
host = "127.0.0.1";
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminPassword.result.path = config.shb.hardcodedsecret.jellyfinLdapUserPassword.path;
adminPassword.result = config.shb.hardcodedsecret.jellyfinLdapUserPassword.result;
};
};
shb.hardcodedsecret.jellyfinLdapUserPassword = config.shb.jellyfin.ldap.adminPassword.request // {
content = "ldapUserPassword";
shb.hardcodedsecret.jellyfinLdapUserPassword = {
request = config.shb.jellyfin.ldap.adminPassword.request;
settings.content = "ldapUserPassword";
};
};
@ -57,17 +58,19 @@ let
sso = {
enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
sharedSecret.result.path = config.shb.hardcodedsecret.jellyfinSSOPassword.path;
sharedSecretForAuthelia.result.path = config.shb.hardcodedsecret.jellyfinSSOPasswordAuthelia.path;
sharedSecret.result = config.shb.hardcodedsecret.jellyfinSSOPassword.result;
sharedSecretForAuthelia.result = config.shb.hardcodedsecret.jellyfinSSOPasswordAuthelia.result;
};
};
shb.hardcodedsecret.jellyfinSSOPassword = config.shb.jellyfin.sso.sharedSecret.request // {
content = "ssoPassword";
shb.hardcodedsecret.jellyfinSSOPassword = {
request = config.shb.jellyfin.sso.sharedSecret.request;
settings.content = "ssoPassword";
};
shb.hardcodedsecret.jellyfinSSOPasswordAuthelia = config.shb.jellyfin.sso.sharedSecretForAuthelia.request // {
content = "ssoPassword";
shb.hardcodedsecret.jellyfinSSOPasswordAuthelia = {
request = config.shb.jellyfin.sso.sharedSecretForAuthelia.request;
settings.content = "ssoPassword";
};
};
in

View file

@ -30,8 +30,17 @@ let
inherit subdomain domain;
grafanaPort = 3000;
adminPasswordFile = pkgs.writeText "admin_password" password;
secretKeyFile = pkgs.writeText "secret_key" "secret_key";
adminPassword.result = config.shb.hardcodedsecret."admin_password".result;
secretKey.result = config.shb.hardcodedsecret."secret_key".result;
};
shb.hardcodedsecret."admin_password" = {
request = config.shb.monitoring.adminPassword.request;
settings.content = password;
};
shb.hardcodedsecret."secret_key" = {
request = config.shb.monitoring.secretKey.request;
settings.content = "secret_key_pw";
};
};

View file

@ -132,12 +132,13 @@ let
externalFqdn = "${fqdn}:8080";
adminUser = adminUser;
adminPass.result.path = config.shb.hardcodedsecret.adminPass.path;
adminPass.result = config.shb.hardcodedsecret.adminPass.result;
debug = true;
};
shb.hardcodedsecret.adminPass = config.shb.nextcloud.adminPass.request // {
content = adminPass;
shb.hardcodedsecret.adminPass = {
request = config.shb.nextcloud.adminPass.request;
settings.content = adminPass;
};
};
@ -157,7 +158,7 @@ let
port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain;
adminName = "admin";
adminPassword.result.path = config.shb.ldap.ldapUserPassword.result.path;
adminPassword.result = config.shb.ldap.ldapUserPassword.result;
userGroup = "nextcloud_user";
};
};
@ -172,18 +173,20 @@ let
clientID = "nextcloud";
# adminUserGroup = "nextcloud_admin";
secret.result.path = config.shb.hardcodedsecret.oidcSecret.path;
secretForAuthelia.result.path = config.shb.hardcodedsecret.oidcAutheliaSecret.path;
secret.result = config.shb.hardcodedsecret.oidcSecret.result;
secretForAuthelia.result = config.shb.hardcodedsecret.oidcAutheliaSecret.result;
fallbackDefaultAuth = false;
};
};
shb.hardcodedsecret.oidcSecret = config.shb.nextcloud.apps.sso.secret.request // {
content = oidcSecret;
shb.hardcodedsecret.oidcSecret = {
request = config.shb.nextcloud.apps.sso.secret.request;
settings.content = oidcSecret;
};
shb.hardcodedsecret.oidcAutheliaSecret = config.shb.nextcloud.apps.sso.secretForAuthelia.request // {
content = oidcSecret;
shb.hardcodedsecret.oidcAutheliaSecret = {
request = config.shb.nextcloud.apps.sso.secretForAuthelia.request;
settings.content = oidcSecret;
};
};

View file

@ -51,6 +51,7 @@ let
};
base = testLib.base pkgs' [
../../modules/blocks/hardcodedsecret.nix
../../modules/services/vaultwarden.nix
];
@ -61,7 +62,11 @@ let
inherit subdomain domain;
port = 8222;
databasePasswordFile = pkgs.writeText "pwfile" "DBPASSWORDFILE";
databasePassword.result = config.shb.hardcodedsecret.passphrase.result;
};
shb.hardcodedsecret.passphrase = {
request = config.shb.vaultwarden.databasePassword.request;
settings.content = "PassPhrase";
};
# networking.hosts = {
@ -97,7 +102,7 @@ let
request = config.shb.vaultwarden.backup;
settings = {
enable = true;
passphrase.result.path = config.shb.hardcodedsecret.passphrase.path;
passphrase.result = config.shb.hardcodedsecret.backupPassphrase.result;
repository = {
path = "/opt/repos/A";
timerConfig = {
@ -107,8 +112,9 @@ let
};
};
};
shb.hardcodedsecret.passphrase = config.shb.restic.instances."testinstance".settings.passphrase.request // {
content = "PassPhrase";
shb.hardcodedsecret.backupPassphrase = {
request = config.shb.restic.instances."testinstance".settings.passphrase.request;
settings.content = "PassPhrase";
};
};
in