1
0
Fork 0

Compare commits

..

No commits in common. "7f2aa36d27cee296072304853bd7923e0c1450f9" and "b8a570ae0d0125a1ae4ab7a13ed11bca8bb00a54" have entirely different histories.

24 changed files with 200 additions and 666 deletions

View file

@ -1,105 +0,0 @@
# name: build
# on: push
# jobs:
# checks:
# uses: nixbuild/nixbuild-action/.github/workflows/ci-workflow.yml@v19
# with:
# nix_conf: |
# allow-import-from-derivation = true
# secrets:
# nixbuild_token: ${{ secrets.nixbuild_token }}
name: "build"
on:
pull_request:
push:
branches: [ "main" ]
jobs:
build-matrix:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: "system-features = nixos-test benchmark big-parallel kvm"
- name: Setup Caching
uses: cachix/cachix-action@v14
with:
name: selfhostblocks
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Generate Matrix
id: generate-matrix
run: |
nix flake show --allow-import-from-derivation --json \
| jq -c '.["checks"]["x86_64-linux"] | keys' > .output
cat .output
echo dynamic_list="$(cat .output)" >> "$GITHUB_OUTPUT"
outputs:
check: ${{ steps.generate-matrix.outputs.dynamic_list }}
manual:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: "system-features = nixos-test benchmark big-parallel kvm"
- name: Setup Caching
uses: cachix/cachix-action@v14
with:
name: selfhostblocks
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
run: |
nix \
--print-build-logs \
--option keep-going true \
--show-trace \
build .#manualHtml
tests:
runs-on: ubuntu-latest
needs: [ "build-matrix" ]
strategy:
matrix:
check: ${{ fromJson(needs.build-matrix.outputs.check) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: "system-features = nixos-test benchmark big-parallel kvm"
- name: Setup Caching
uses: cachix/cachix-action@v14
with:
name: selfhostblocks
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
run: |
nix build --print-build-logs --show-trace .#checks.x86_64-linux.${{ matrix.check }}
results:
if: ${{ always() }}
runs-on: ubuntu-latest
name: Final Results
needs: [ manual, tests ]
steps:
- run: |
result="${{ needs.tests.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi

View file

@ -3,31 +3,15 @@ name: Demo
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
paths: &paths
- 'demo/**'
push: push:
branches: branches:
- main - main
paths: *paths
jobs: jobs:
path-filter:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.filter.outputs.changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
changed:
- '.github/workflows/demo.yml'
- 'demo/**'
build: build:
needs: [ "path-filter" ]
if: needs.path-filter.outputs.changed == 'true'
strategy: strategy:
matrix: matrix:
demo: demo:
@ -54,7 +38,7 @@ jobs:
- uses: cachix/cachix-action@v14 - uses: cachix/cachix-action@v14
with: with:
name: selfhostblocks name: mycache
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build ${{ matrix.demo.name }} .#${{ matrix.demo.flake }} - name: Build ${{ matrix.demo.name }} .#${{ matrix.demo.flake }}
@ -66,15 +50,4 @@ jobs:
--show-trace \ --show-trace \
build .#nixosConfigurations.${{ matrix.demo.flake }}.config.system.build.vm build .#nixosConfigurations.${{ matrix.demo.flake }}.config.system.build.vm
result:
runs-on: ubuntu-latest
needs: [ "build" ]
if: '!cancelled()'
steps:
- run: |
result="${{ needs.build.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi

22
.github/workflows/garnix.yaml vendored Normal file
View file

@ -0,0 +1,22 @@
# Leaving commented because it does not work.
#
# name: "Final Results"
#
# on:
# check_suite:
# types: [completed]
#
# jobs:
# results:
# name: Final Results
# runs-on: ubuntu-latest
# steps:
# - run: echo
# - run: exit 1
# # see https://stackoverflow.com/a/67532120/4907315
# if: >-
# ${{
# contains(needs.*.result, 'failure')
# || contains(needs.*.result, 'cancelled')
# || contains(needs.*.result, 'skipped')
# }}

View file

@ -2,8 +2,14 @@
name: Deploy docs name: Deploy docs
on: on:
# Runs on pushes targeting the default branch
push: push:
branches: ["main"] branches: ["main"]
# TODO: needed ?
# schedule:
# - cron: 0 0 * * 1
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch: workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
@ -34,10 +40,9 @@ jobs:
- name: Install nix - name: Install nix
uses: cachix/install-nix-action@v20 uses: cachix/install-nix-action@v20
- name: Setup Caching - uses: cachix/cachix-action@v14
uses: cachix/cachix-action@v14
with: with:
name: selfhostblocks name: mycache
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build docs - name: Build docs

View file

@ -14,26 +14,9 @@
- `shb.authelia.oidcClients.description` -> `shb.authelia.oidcClients.client_name` - `shb.authelia.oidcClients.description` -> `shb.authelia.oidcClients.client_name`
- `shb.authelia.oidcClients.secret` -> `shb.authelia.oidcClients.client_secret` - `shb.authelia.oidcClients.secret` -> `shb.authelia.oidcClients.client_secret`
- `shb.authelia.ldapEndpoint` -> `shb.authelia.ldapHostname` and `shb.authelia.ldapPort` - `shb.authelia.ldapEndpoint` -> `shb.authelia.ldapHostname` and `shb.authelia.ldapPort`
- `shb.authelia.jwtSecretFile` -> `shb.authelia.jwtSecret.result.path`
- `shb.authelia.ldapAdminPasswordFile` -> `shb.authelia.ldapAdminPassword.result.path`
- `shb.authelia.sessionSecretFile` -> `shb.authelia.sessionSecret.result.path`
- `shb.authelia.storageEncryptionKeyFile` -> `shb.authelia.storageEncryptionKey.result.path`
- `shb.authelia.identityProvidersOIDCIssuerPrivateKeyFile` -> `shb.authelia.identityProvidersOIDCIssuerPrivateKey.result.path`
- `shb.authelia.smtp.passwordFile` -> `shb.authelia.smtp.password.result.path`
- Make Nextcloud automatically disable maintenance mode upon service restart. - Make Nextcloud automatically disable maintenance mode upon service restart.
- `shb.ldap.ldapUserPasswordFile` -> `shb.ldap.ldapUserPassword.result.path` - `shb.ldap.ldapUserPasswordFile` -> `shb.ldap.ldapUserPassword.result.path`
- `shb.ldap.jwtSecretFile` -> `shb.ldap.jwtSecret.result.path` - `shb.ldap.jwtSecretFile` -> `shb.ldap.jwtSecret.result.path`
- Jellyfin changes:
- `shb.jellyfin.ldap.passwordFile` -> `shb.jellyfin.ldap.adminPassword.result.path`.
- `shb.jellyfin.sso.secretFile` -> `shb.jellyfin.ldap.sharedSecret.result.path`.
- + `shb.jellyfin.ldap.sharedSecretForAuthelia`.
- Forgejo changes:
- `shb.forgejo.ldap.adminPasswordFile` -> `shb.forgejo.ldap.adminPassword.result.path`.
- `shb.forgejo.sso.secretFile` -> `shb.forgejo.ldap.sharedSecret.result.path`.
- `shb.forgejo.sso.secretFileForAuthelia` -> `shb.forgejo.ldap.sharedSecretForAuthelia.result.path`.
- `shb.forgejo.adminPasswordFile` -> `shb.forgejo.adminPassword.result.path`.
- `shb.forgejo.databasePasswordFile` -> `shb.forgejo.databasePassword.result.path`.
## User Facing Backwards Compatible Changes ## User Facing Backwards Compatible Changes

View file

@ -3,7 +3,7 @@
*Modular server management based on NixOS modules and focused on best practices.* *Modular server management based on NixOS modules and focused on best practices.*
[![Documentation](https://github.com/ibizaman/selfhostblocks/actions/workflows/pages.yml/badge.svg)](https://github.com/ibizaman/selfhostblocks/actions/workflows/pages.yml) [![Documentation](https://github.com/ibizaman/selfhostblocks/actions/workflows/pages.yml/badge.svg)](https://github.com/ibizaman/selfhostblocks/actions/workflows/pages.yml)
[![Tests](https://github.com/ibizaman/selfhostblocks/actions/workflows/build.yaml/badge.svg)](https://github.com/ibizaman/selfhostblocks/actions/workflows/build.yaml) [![Tests](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgarnix.io%2Fapi%2Fbadges%2Fibizaman%2Fselfhostblocks%3Fbranch%3Dmain)](https://garnix.io) (using Garnix)
[![Demo](https://github.com/ibizaman/selfhostblocks/actions/workflows/demo.yml/badge.svg)](https://github.com/ibizaman/selfhostblocks/actions/workflows/demo.yml) [![Demo](https://github.com/ibizaman/selfhostblocks/actions/workflows/demo.yml/badge.svg)](https://github.com/ibizaman/selfhostblocks/actions/workflows/demo.yml)
SHB (Self Host Blocks) is yet another server management tool SHB (Self Host Blocks) is yet another server management tool

View file

@ -35,11 +35,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1731139594, "lastModified": 1728492678,
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=", "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2", "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -26,6 +26,7 @@
src = nixpkgs; src = nixpkgs;
inherit patches; inherit patches;
}; };
pkgs = import patchedNixpkgs { pkgs = import patchedNixpkgs {
inherit system; inherit system;
}; };
@ -33,7 +34,6 @@
allModules = [ allModules = [
modules/blocks/authelia.nix modules/blocks/authelia.nix
modules/blocks/davfs.nix modules/blocks/davfs.nix
modules/blocks/hardcodedsecret.nix
modules/blocks/ldap.nix modules/blocks/ldap.nix
modules/blocks/monitoring.nix modules/blocks/monitoring.nix
modules/blocks/nginx.nix modules/blocks/nginx.nix
@ -79,14 +79,12 @@
checks = checks =
let let
inherit (pkgs.lib) foldl foldlAttrs mergeAttrs optionalAttrs;
importFiles = files: importFiles = files:
map (m: pkgs.callPackage m {}) files; map (m: pkgs.callPackage m {}) files;
mergeTests = foldl mergeAttrs {}; mergeTests = pkgs.lib.lists.foldl pkgs.lib.trivial.mergeAttrs {};
flattenAttrs = root: attrset: foldlAttrs (acc: name: value: acc // { flattenAttrs = root: attrset: pkgs.lib.attrsets.foldlAttrs (acc: name: value: acc // {
"${root}_${name}" = value; "${root}_${name}" = value;
}) {} attrset; }) {} attrset;
@ -98,21 +96,19 @@
); );
shblib = pkgs.callPackage ./lib {}; shblib = pkgs.callPackage ./lib {};
in (optionalAttrs (system == "x86_64-linux") ({ in (rec {
modules = shblib.check { modules = shblib.check {
inherit pkgs; inherit pkgs;
tests = tests =
mergeTests (importFiles [ mergeTests (importFiles [
./test/modules/arr.nix ./test/modules/arr.nix
./test/modules/davfs.nix ./test/modules/davfs.nix
# TODO: Make this not use IFD
./test/modules/lib.nix ./test/modules/lib.nix
./test/modules/nginx.nix ./test/modules/nginx.nix
./test/modules/postgresql.nix ./test/modules/postgresql.nix
]); ]);
}; };
# TODO: Make this not use IFD
lib = nix-flake-tests.lib.check { lib = nix-flake-tests.lib.check {
inherit pkgs; inherit pkgs;
tests = pkgs.callPackage ./test/modules/lib.nix {}; tests = pkgs.callPackage ./test/modules/lib.nix {};
@ -123,7 +119,7 @@
// (vm_test "deluge" ./test/services/deluge.nix) // (vm_test "deluge" ./test/services/deluge.nix)
// (vm_test "forgejo" ./test/services/forgejo.nix) // (vm_test "forgejo" ./test/services/forgejo.nix)
// (vm_test "grocy" ./test/services/grocy.nix) // (vm_test "grocy" ./test/services/grocy.nix)
// (vm_test "homeassistant" ./test/services/home-assistant.nix) // (vm_test "home-assistant" ./test/services/home-assistant.nix)
// (vm_test "jellyfin" ./test/services/jellyfin.nix) // (vm_test "jellyfin" ./test/services/jellyfin.nix)
// (vm_test "monitoring" ./test/services/monitoring.nix) // (vm_test "monitoring" ./test/services/monitoring.nix)
// (vm_test "nextcloud" ./test/services/nextcloud.nix) // (vm_test "nextcloud" ./test/services/nextcloud.nix)
@ -135,11 +131,7 @@
// (vm_test "postgresql" ./test/blocks/postgresql.nix) // (vm_test "postgresql" ./test/blocks/postgresql.nix)
// (vm_test "restic" ./test/blocks/restic.nix) // (vm_test "restic" ./test/blocks/restic.nix)
// (vm_test "ssl" ./test/blocks/ssl.nix) // (vm_test "ssl" ./test/blocks/ssl.nix)
);
// (vm_test "contracts-secret" ./test/contracts/secret.nix)
));
} }
) // { );
herculesCI.ciSystems = [ "x86_64-linux" ];
};
} }

View file

@ -1,7 +1,7 @@
{ pkgs, lib }: { pkgs, lib }:
let let
inherit (builtins) isAttrs hasAttr; inherit (builtins) isAttrs hasAttr;
inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList; inherit (lib) concatStringsSep;
in in
rec { rec {
# Replace secrets in a file. # Replace secrets in a file.
@ -31,34 +31,17 @@ rec {
resultPath = newPath; resultPath = newPath;
}; };
genReplacement = secret:
let
t = { transform ? null, ... }: if isNull transform then x: x else transform;
in
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
replaceSecretsScript = { file, resultPath, replacements, user ? null, permissions ? "u=r,g=r,o=" }: replaceSecretsScript = { file, resultPath, replacements, user ? null, permissions ? "u=r,g=r,o=" }:
let let
templatePath = resultPath + ".template"; templatePath = resultPath + ".template";
sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
# We check that the files containing the secrets have the sedCmd = if replacements == {}
# correct permissions for us to read them in this separate
# step. Otherwise, the $(cat ...) commands inside the sed
# replacements could fail but not fail individually but
# not fail the whole script.
checkPermissions = concatMapStringsSep "\n" (pattern: "cat ${pattern.source} > /dev/null") replacements;
sedPatterns = concatMapStringsSep " " (pattern: "-e \"s|${pattern.name}|${pattern.value}|\"") (map genReplacement replacements);
sedCmd = if replacements == []
then "cat" then "cat"
else "${pkgs.gnused}/bin/sed ${sedPatterns}"; else "${pkgs.gnused}/bin/sed ${sedPatterns}";
in in
'' ''
set -euo pipefail set -euo pipefail
${checkPermissions}
mkdir -p $(dirname ${templatePath}) mkdir -p $(dirname ${templatePath})
ln -fs ${file} ${templatePath} ln -fs ${file} ${templatePath}
rm -f ${resultPath} rm -f ${resultPath}
@ -88,8 +71,8 @@ rec {
}; };
}; };
secretName = names: secretName = name:
"%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%"; "%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) name)}%";
withReplacements = attrs: withReplacements = attrs:
let let
@ -108,8 +91,15 @@ rec {
else value // { name = name; }; else value // { name = name; };
secretsWithName = mapAttrsRecursiveCond (v: ! v ? "source") addNameField attrs; secretsWithName = mapAttrsRecursiveCond (v: ! v ? "source") addNameField attrs;
allSecrets = collect (v: builtins.isAttrs v && v ? "source") secretsWithName;
t = { transform ? null, ... }: if isNull transform then x: x else transform;
genReplacement = secret:
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
in in
collect (v: builtins.isAttrs v && v ? "source") secretsWithName; lib.attrsets.listToAttrs (map genReplacement allSecrets);
# Inspired lib.attrsets.mapAttrsRecursiveCond but also recurses on lists. # Inspired lib.attrsets.mapAttrsRecursiveCond but also recurses on lists.
mapAttrsRecursiveCond = mapAttrsRecursiveCond =
@ -248,7 +238,7 @@ rec {
results = pkgs.lib.runTests tests; results = pkgs.lib.runTests tests;
in in
if results != [ ] then if results != [ ] then
builtins.throw (concatStringsSep "\n" (map resultToString (lib.traceValSeqN 3 results))) builtins.throw (builtins.concatStringsSep "\n" (map resultToString (lib.traceValSeqN 3 results)))
else else
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out"; pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";

View file

@ -1,8 +1,7 @@
{ config, options, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let
cfg = config.shb.authelia; cfg = config.shb.authelia;
opt = options.shb.authelia;
contracts = pkgs.callPackage ../contracts {}; contracts = pkgs.callPackage ../contracts {};
shblib = pkgs.callPackage ../../lib {}; shblib = pkgs.callPackage ../../lib {};
@ -68,45 +67,33 @@ in
description = "Secrets needed by Authelia"; description = "Secrets needed by Authelia";
type = lib.types.submodule { type = lib.types.submodule {
options = { options = {
jwtSecret = contracts.secret.mkOption { jwtSecretFile = lib.mkOption {
description = "JWT secret."; type = lib.types.path;
mode = "0400"; description = "File containing the JWT secret.";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
}; };
ldapAdminPassword = contracts.secret.mkOption { ldapAdminPasswordFile = lib.mkOption {
description = "LDAP admin user password."; type = lib.types.path;
mode = "0400"; description = "File containing the LDAP admin user password.";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
}; };
sessionSecret = contracts.secret.mkOption { sessionSecretFile = lib.mkOption {
description = "Session secret."; type = lib.types.path;
mode = "0400"; description = "File containing the session secret.";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
}; };
storageEncryptionKey = contracts.secret.mkOption { storageEncryptionKeyFile = lib.mkOption {
description = "Storage encryption key."; type = lib.types.path;
mode = "0400"; description = "File containing the storage encryption key.";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
}; };
identityProvidersOIDCHMACSecret = contracts.secret.mkOption { identityProvidersOIDCHMACSecretFile = lib.mkOption {
description = "Identity provider OIDC HMAC secret."; type = lib.types.path;
mode = "0400"; description = "File containing the identity provider OIDC HMAC secret.";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
}; };
identityProvidersOIDCIssuerPrivateKey = contracts.secret.mkOption { identityProvidersOIDCIssuerPrivateKeyFile = lib.mkOption {
type = lib.types.path;
description = '' description = ''
Identity provider OIDC issuer private key. File containing the identity provider OIDC issuer private key.
Generate one with `nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048` Generate one with `nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048`
''; '';
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${opt.subdomain}.${opt.domain}" ];
}; };
}; };
}; };
@ -220,11 +207,9 @@ in
type = lib.types.str; type = lib.types.str;
description = "Username to connect to the SMTP host."; description = "Username to connect to the SMTP host.";
}; };
password = contracts.secret.mkOption { passwordFile = lib.mkOption {
type = lib.types.str;
description = "File containing the password to connect to the SMTP host."; description = "File containing the password to connect to the SMTP host.";
mode = "0400";
owner = cfg.autheliaUser;
restartUnits = [ "authelia-${fqdn}" ];
}; };
}; };
})) }))
@ -297,20 +282,19 @@ in
user = cfg.autheliaUser; user = cfg.autheliaUser;
secrets = { secrets = {
jwtSecretFile = cfg.secrets.jwtSecret.result.path; inherit (cfg.secrets) jwtSecretFile storageEncryptionKeyFile;
storageEncryptionKeyFile = cfg.secrets.storageEncryptionKey.result.path;
}; };
# See https://www.authelia.com/configuration/methods/secrets/ # See https://www.authelia.com/configuration/methods/secrets/
environmentVariables = { environmentVariables = {
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = toString cfg.secrets.ldapAdminPassword.result.path; AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = toString cfg.secrets.ldapAdminPasswordFile;
AUTHELIA_SESSION_SECRET_FILE = toString cfg.secrets.sessionSecret.result.path; AUTHELIA_SESSION_SECRET_FILE = toString cfg.secrets.sessionSecretFile;
# Not needed since we use peer auth. # Not needed since we use peer auth.
# AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE = "/run/secrets/authelia/postgres_password"; # AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE = "/run/secrets/authelia/postgres_password";
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = toString cfg.secrets.storageEncryptionKey.result.path; AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = toString cfg.secrets.storageEncryptionKeyFile;
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecret.result.path; AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecretFile;
AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = toString cfg.secrets.identityProvidersOIDCIssuerPrivateKey.result.path; AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = toString cfg.secrets.identityProvidersOIDCIssuerPrivateKeyFile;
AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(builtins.isString cfg.smtp)) (toString cfg.smtp.password.result.path); AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(builtins.isString cfg.smtp)) (toString cfg.smtp.passwordFile);
}; };
settings = { settings = {
server.address = "tcp://127.0.0.1:9091"; server.address = "tcp://127.0.0.1:9091";

View file

@ -1,108 +0,0 @@
{ config, options, lib, pkgs, ... }:
let
cfg = config.shb.hardcodedsecret;
opt = options.shb.hardcodedsecret;
inherit (lib) mapAttrs' mkOption nameValuePair;
inherit (lib.types) attrsOf listOf path nullOr str submodule;
inherit (pkgs) writeText;
in
{
options.shb.hardcodedsecret = mkOption {
default = {};
description = ''
Hardcoded secrets. These should only be used in tests.
'';
example = lib.literalExpression ''
{
mySecret = {
user = "me";
mode = "0400";
restartUnits = [ "myservice.service" ];
content = "My Secrets";
};
}
'';
type = attrsOf (submodule ({ name, ... }: {
options = {
mode = mkOption {
description = ''
Mode of the secret file.
'';
type = str;
default = "0400";
};
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;
};
};
}));
};
config = {
system.activationScripts = mapAttrs' (n: cfg':
let
source = if cfg'.source != null
then cfg'.source
else writeText "hardcodedsecret_${n}_content" cfg'.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}"
''
) cfg;
};
}

View file

@ -112,12 +112,9 @@ in
''; '';
readOnly = true; readOnly = true;
default = { default = {
# TODO: is there a workaround that avoid needing to use root? user = "lldap";
# root because otherwise we cannot access the private StateDiretory
user = "root";
# /private because the systemd service uses DynamicUser=true
sourceDirectories = [ sourceDirectories = [
"/var/lib/private/lldap" "/var/lib/lldap"
]; ];
}; };
}; };

View file

@ -1,10 +1,7 @@
{ pkgs, lib }: { lib }:
{ {
backup = import ./backup.nix { inherit lib; }; backup = import ./backup.nix { inherit lib; };
mount = import ./mount.nix { inherit lib; }; mount = import ./mount.nix { inherit lib; };
secret = import ./secret.nix { inherit lib; }; secret = import ./secret.nix { inherit lib; };
ssl = import ./ssl.nix { inherit lib; }; ssl = import ./ssl.nix { inherit lib; };
test = {
secret = import ./secret/test.nix { inherit pkgs lib; };
};
} }

View file

@ -1,64 +0,0 @@
{ pkgs, lib, ... }:
let
pkgs' = pkgs;
testLib = pkgs.callPackage ../../../test/common.nix {};
inherit (lib) getAttrFromPath setAttrByPath;
inherit (lib) mkIf;
in
{ name,
configRoot,
createContent, # config to create a secret with value "secretA".
modules ? [],
owner ? "root",
group ? "root",
mode ? "0400",
restartUnits ? [ "myunit.service" ],
}: pkgs.testers.runNixOSTest {
name = "secret_${name}_${owner}_${group}_${mode}";
nodes.machine = { config, ... }: {
imports = ( testLib.baseImports pkgs' ) ++ modules;
config = lib.mkMerge [
(setAttrByPath configRoot {
A = {
inherit owner group mode restartUnits;
} // createContent;
})
(mkIf (owner != "root") {
users.users.${owner}.isNormalUser = true;
})
(mkIf (group != "root") {
users.groups.${group} = {};
})
];
};
testScript = { nodes, ... }:
let
cfg = (getAttrFromPath configRoot nodes.machine)."A";
in
''
owner = machine.succeed("stat -c '%U' ${cfg.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()
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()))
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()
print(f"Got content {content}")
if content != "secretA":
raise Exception(f"Content should be 'secretA' but got '{content}'")
'';
}

View file

@ -82,12 +82,14 @@ in
default = "admin"; default = "admin";
}; };
adminPassword = contracts.secret.mkOption { adminPasswordFile = lib.mkOption {
description = "LDAP admin password."; type = lib.types.path;
mode = "0440"; description = ''
owner = "forgejo"; File containing the admin password of the LDAP server.
group = "forgejo";
restartUnits = [ "forgejo.service" ]; Must be readable by the forgejo system user.
'';
default = "";
}; };
userGroup = lib.mkOption { userGroup = lib.mkOption {
@ -138,37 +140,37 @@ in
default = "one_factor"; default = "one_factor";
}; };
sharedSecret = contracts.secret.mkOption { secretFile = lib.mkOption {
description = "OIDC shared secret for Forgejo."; type = lib.types.path;
mode = "0440"; description = ''
owner = "forgejo"; File containing the secret for the OIDC endpoint.
group = "forgejo";
restartUnits = [ "forgejo.service" ]; Must be readable by the forgejo system user.
'';
}; };
sharedSecretForAuthelia = contracts.secret.mkOption { secretFileForAuthelia = lib.mkOption {
description = "OIDC shared secret for Authelia."; type = lib.types.path;
mode = "0400"; description = ''
owner = "authelia"; File containing the secret for the OIDC endpoint, must be readable by the Authelia user.
Must be readable by the authelia system user.
'';
}; };
}; };
}; };
}; };
adminPassword = contracts.secret.mkOption { adminPasswordFile = lib.mkOption {
type = lib.types.path;
description = "File containing the Forgejo admin user password."; description = "File containing the Forgejo admin user password.";
mode = "0440"; example = "/run/secrets/forgejo/adminPassword";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
}; };
databasePassword = contracts.secret.mkOption { databasePasswordFile = lib.mkOption {
type = lib.types.path;
description = "File containing the Forgejo database password."; description = "File containing the Forgejo database password.";
mode = "0440"; example = "/run/secrets/forgejo/databasePassword";
owner = "forgejo";
group = "forgejo";
restartUnits = [ "forgejo.service" ];
}; };
repositoryRoot = lib.mkOption { repositoryRoot = lib.mkOption {
@ -342,7 +344,7 @@ in
services.forgejo.database = { services.forgejo.database = {
type = "postgres"; type = "postgres";
passwordFile = cfg.databasePassword.result.path; passwordFile = cfg.databasePasswordFile;
}; };
}) })
@ -378,7 +380,7 @@ in
--host ${cfg.ldap.host} \ --host ${cfg.ldap.host} \
--port ${toString cfg.ldap.port} \ --port ${toString cfg.ldap.port} \
--bind-dn uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain} \ --bind-dn uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain} \
--bind-password $(tr -d '\n' < ${cfg.ldap.adminPassword.result.path}) \ --bind-password $(tr -d '\n' < ${cfg.ldap.adminPasswordFile}) \
--security-protocol Unencrypted \ --security-protocol Unencrypted \
--user-search-base ou=people,${cfg.ldap.dcdomain} \ --user-search-base ou=people,${cfg.ldap.dcdomain} \
--user-filter '(&(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})(|(uid=%[1]s)(mail=%[1]s)))' \ --user-filter '(&(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})(|(uid=%[1]s)(mail=%[1]s)))' \
@ -397,7 +399,7 @@ in
--host ${cfg.ldap.host} \ --host ${cfg.ldap.host} \
--port ${toString cfg.ldap.port} \ --port ${toString cfg.ldap.port} \
--bind-dn uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain} \ --bind-dn uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain} \
--bind-password $(tr -d '\n' < ${cfg.ldap.adminPassword.result.path}) \ --bind-password $(tr -d '\n' < ${cfg.ldap.adminPasswordFile}) \
--security-protocol Unencrypted \ --security-protocol Unencrypted \
--user-search-base ou=people,${cfg.ldap.dcdomain} \ --user-search-base ou=people,${cfg.ldap.dcdomain} \
--user-filter '(&(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})(|(uid=%[1]s)(mail=%[1]s)))' \ --user-filter '(&(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})(|(uid=%[1]s)(mail=%[1]s)))' \
@ -454,7 +456,7 @@ in
--name ${provider} \ --name ${provider} \
--provider openidConnect \ --provider openidConnect \
--key forgejo \ --key forgejo \
--secret $(tr -d '\n' < ${cfg.sso.sharedSecret.result.path}) \ --secret $(tr -d '\n' < ${cfg.sso.secretFile}) \
--auto-discover-url ${cfg.sso.endpoint}/.well-known/openid-configuration --auto-discover-url ${cfg.sso.endpoint}/.well-known/openid-configuration
else else
echo Did not find any sso configuration, creating one with name ${provider}. echo Did not find any sso configuration, creating one with name ${provider}.
@ -462,7 +464,7 @@ in
--name ${provider} \ --name ${provider} \
--provider openidConnect \ --provider openidConnect \
--key forgejo \ --key forgejo \
--secret $(tr -d '\n' < ${cfg.sso.sharedSecret.result.path}) \ --secret $(tr -d '\n' < ${cfg.sso.secretFile}) \
--auto-discover-url ${cfg.sso.endpoint}/.well-known/openid-configuration --auto-discover-url ${cfg.sso.endpoint}/.well-known/openid-configuration
fi fi
''; '';
@ -473,7 +475,7 @@ in
in { in {
client_id = cfg.sso.clientID; client_id = cfg.sso.clientID;
client_name = "Forgejo"; client_name = "Forgejo";
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path; client_secret.source = cfg.sso.secretFileForAuthelia;
public = false; public = false;
authorization_policy = cfg.sso.authorization_policy; authorization_policy = cfg.sso.authorization_policy;
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/user/oauth2/${provider}/callback" ]; redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/user/oauth2/${provider}/callback" ];
@ -484,8 +486,8 @@ in
(lib.mkIf cfg.enable { (lib.mkIf cfg.enable {
systemd.services.forgejo.preStart = '' systemd.services.forgejo.preStart = ''
admin="${lib.getExe config.services.forgejo.package} admin user" admin="${lib.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 create --admin --email "root@localhost" --username meadmin --password "$(tr -d '\n' < ${cfg.adminPasswordFile})" || true
$admin change-password --username meadmin --password "$(tr -d '\n' < ${cfg.adminPassword.result.path})" || true $admin change-password --username meadmin --password "$(tr -d '\n' < ${cfg.adminPasswordFile})" || true
''; '';
}) })

View file

@ -67,12 +67,9 @@ in
default = "jellyfin_admin"; default = "jellyfin_admin";
}; };
adminPassword = contracts.secret.mkOption { passwordFile = lib.mkOption {
description = "LDAP admin password."; type = lib.types.path;
mode = "0440"; description = "File containing the LDAP admin password.";
owner = "jellyfin";
group = "jellyfin";
restartUnits = [ "jellyfin.service" ];
}; };
}; };
}; };
@ -121,18 +118,9 @@ in
default = "one_factor"; default = "one_factor";
}; };
sharedSecret = contracts.secret.mkOption { secretFile = lib.mkOption {
description = "OIDC shared secret for Jellyfin."; type = lib.types.path;
mode = "0440"; description = "File containing the OIDC shared secret.";
owner = "jellyfin";
group = "jellyfin";
restartUnits = [ "jellyfin.service" ];
};
sharedSecretForAuthelia = contracts.secret.mkOption {
description = "OIDC shared secret for Authelia.";
mode = "0400";
owner = config.shb.authelia.autheliaUser;
}; };
}; };
}; };
@ -412,35 +400,30 @@ in
lib.strings.optionalString cfg.ldap.enable (shblib.replaceSecretsScript { lib.strings.optionalString cfg.ldap.enable (shblib.replaceSecretsScript {
file = ldapConfig; file = ldapConfig;
resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml"; resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml";
replacements = [ replacements = {
{ "%LDAP_PASSWORD%" = "$(cat ${cfg.ldap.passwordFile})";
name = [ "%LDAP_PASSWORD%" ]; };
source = cfg.ldap.adminPassword.result.path;
}
];
}) })
+ lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript { + lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript {
file = ssoConfig; file = ssoConfig;
resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml"; resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml";
replacements = [ replacements = {
{ "%SSO_SECRET%" = "$(cat ${cfg.sso.secretFile})";
name = [ "%SSO_SECRET%" ]; };
source = cfg.sso.sharedSecret.result.path;
}
];
}) })
+ lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript { + lib.strings.optionalString cfg.sso.enable (shblib.replaceSecretsScript {
file = brandingConfig; file = brandingConfig;
resultPath = "/var/lib/jellyfin/config/branding.xml"; resultPath = "/var/lib/jellyfin/config/branding.xml";
replacements = [ replacements = {
]; "%a%" = "%a%";
};
}); });
shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [ shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [
{ {
client_id = cfg.sso.clientID; client_id = cfg.sso.clientID;
client_name = "Jellyfin"; client_name = "Jellyfin";
client_secret.source = cfg.sso.sharedSecretForAuthelia.result.path; client_secret.source = cfg.sso.secretFile;
public = false; public = false;
authorization_policy = cfg.sso.authorization_policy; authorization_policy = cfg.sso.authorization_policy;
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ]; redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ];

View file

@ -13,7 +13,6 @@ in
(pkgs'.path + "/nixos/modules/profiles/headless.nix") (pkgs'.path + "/nixos/modules/profiles/headless.nix")
(pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix") (pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
../../modules/blocks/authelia.nix ../../modules/blocks/authelia.nix
../../modules/blocks/hardcodedsecret.nix
../../modules/blocks/ldap.nix ../../modules/blocks/ldap.nix
../../modules/blocks/postgresql.nix ../../modules/blocks/postgresql.nix
]; ];
@ -45,12 +44,14 @@ in
ldapPort = config.shb.ldap.ldapPort; ldapPort = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain; dcdomain = config.shb.ldap.dcdomain;
secrets = { secrets = {
jwtSecret.result.path = config.shb.hardcodedsecret.autheliaJwtSecret.path; jwtSecretFile = pkgs.writeText "jwtSecretFile" "jwtSecretFile";
ldapAdminPassword.result.path = config.shb.hardcodedsecret.ldapAdminPassword.path; ldapAdminPasswordFile = pkgs.writeText "ldapAdminPasswordFile" ldapAdminPassword;
sessionSecret.result.path = config.shb.hardcodedsecret.sessionSecret.path; sessionSecretFile = pkgs.writeText "sessionSecretFile" "sessionSecretFile";
storageEncryptionKey.result.path = config.shb.hardcodedsecret.storageEncryptionKey.path; storageEncryptionKeyFile = pkgs.writeText "storageEncryptionKeyFile" "storageEncryptionKeyFile";
identityProvidersOIDCHMACSecret.result.path = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.path; identityProvidersOIDCHMACSecretFile = pkgs.writeText "identityProvidersOIDCHMACSecretFile" "identityProvidersOIDCHMACSecretFile";
identityProvidersOIDCIssuerPrivateKey.result.path = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.path; # This needs to be of the correct shape and at least 2048 bits. Generated with:
# nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048
identityProvidersOIDCIssuerPrivateKeyFile = pkgs.writeText "identityProvidersOIDCIssuerPrivateKeyFile" (builtins.readFile ./keypair.pem);
}; };
oidcClients = [ oidcClients = [
@ -72,28 +73,6 @@ in
} }
]; ];
}; };
shb.hardcodedsecret.autheliaJwtSecret = config.shb.authelia.secrets.jwtSecret.request // {
content = "jwtSecret";
};
shb.hardcodedsecret.ldapAdminPassword = config.shb.authelia.secrets.ldapAdminPassword.request // {
content = ldapAdminPassword;
};
shb.hardcodedsecret.sessionSecret = config.shb.authelia.secrets.sessionSecret.request // {
content = "sessionSecret";
};
shb.hardcodedsecret.storageEncryptionKey = config.shb.authelia.secrets.storageEncryptionKey.request // {
content = "storageEncryptionKey";
};
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request // {
content = "identityProvidersOIDCHMACSecret";
};
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request // {
source = (pkgs.runCommand "gen-private-key" {} ''
mkdir $out
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
'') + "/private.pem";
};
}; };
testScript = { nodes, ... }: '' testScript = { nodes, ... }: ''

View file

@ -12,25 +12,11 @@ let
commonTest = user: pkgs.testers.runNixOSTest { commonTest = user: pkgs.testers.runNixOSTest {
name = "restic_backupAndRestore_${user}"; name = "restic_backupAndRestore_${user}";
nodes.machine = { config, ... }: { nodes.machine = {
imports = ( testLib.baseImports pkgs' ) ++ [ imports = ( testLib.baseImports pkgs' ) ++ [
../../modules/blocks/hardcodedsecret.nix
../../modules/blocks/restic.nix ../../modules/blocks/restic.nix
]; ];
shb.hardcodedsecret.A = {
owner = "root";
group = "keys";
mode = "0440";
content = "secretA";
};
shb.hardcodedsecret.B = {
owner = "root";
group = "keys";
mode = "0440";
content = "secretB";
};
shb.restic.instances."testinstance" = { shb.restic.instances."testinstance" = {
enable = true; enable = true;
@ -53,8 +39,8 @@ let
# Those are not needed by the repository but are still included # Those are not needed by the repository but are still included
# so we can test them in the hooks section. # so we can test them in the hooks section.
secrets = { secrets = {
A.source = config.shb.hardcodedsecret.A.path; A.source = "/run/secrets/A";
B.source = config.shb.hardcodedsecret.B.path; B.source = "/run/secrets/B";
}; };
} }
{ {
@ -111,6 +97,19 @@ let
if len(result) > 0: if len(result) > 0:
raise Exception("Unexpected files:", result) raise Exception("Unexpected files:", result)
with subtest("Create secrets"):
print(machine.succeed("""
mkdir -p /run/secrets/
echo secretA > /run/secrets/A
echo secretB > /run/secrets/B
chown root:keys -R /run/secrets
find /run/secrets -type d -exec chmod u=rwx,g=rx,o=x '{}' ';'
find /run/secrets -type f -exec chmod u=r,g=r,o= '{}' ';'
ls -l /run/secrets
"""))
with subtest("Create initial content"): with subtest("Create initial content"):
machine.succeed(""" machine.succeed("""
mkdir -p /opt/files/A mkdir -p /opt/files/A

View file

@ -1,4 +1,6 @@
{ lib }: {
lib,
}:
let let
baseImports = pkgs: [ baseImports = pkgs: [
(pkgs.path + "/nixos/modules/profiles/headless.nix") (pkgs.path + "/nixos/modules/profiles/headless.nix")
@ -107,7 +109,6 @@ in
../modules/blocks/postgresql.nix ../modules/blocks/postgresql.nix
../modules/blocks/authelia.nix ../modules/blocks/authelia.nix
../modules/blocks/nginx.nix ../modules/blocks/nginx.nix
../modules/blocks/hardcodedsecret.nix
] ]
++ additionalModules; ++ additionalModules;
@ -137,7 +138,7 @@ in
systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ]; systemd.services.nginx.requires = [ config.shb.certs.certs.selfsigned.n.systemdService ];
}; };
ldap = domain: pkgs: { config, ... }: { ldap = domain: pkgs: {
imports = [ imports = [
../modules/blocks/ldap.nix ../modules/blocks/ldap.nix
]; ];
@ -146,13 +147,6 @@ in
"127.0.0.1" = [ "ldap.${domain}" ]; "127.0.0.1" = [ "ldap.${domain}" ];
}; };
shb.hardcodedsecret.ldapUserPassword = config.shb.ldap.ldapUserPassword.request // {
content = "ldapUserPassword";
};
shb.hardcodedsecret.jwtSecret = config.shb.ldap.ldapUserPassword.request // {
content = "jwtSecrets";
};
shb.ldap = { shb.ldap = {
enable = true; enable = true;
inherit domain; inherit domain;
@ -160,8 +154,8 @@ in
ldapPort = 3890; ldapPort = 3890;
webUIListenPort = 17170; webUIListenPort = 17170;
dcdomain = "dc=example,dc=com"; dcdomain = "dc=example,dc=com";
ldapUserPassword.result.path = config.shb.hardcodedsecret.ldapUserPassword.path; ldapUserPassword.result.path = pkgs.writeText "ldapUserPassword" "ldapUserPassword";
jwtSecret.result.path = config.shb.hardcodedsecret.jwtSecret.path; jwtSecret.result.path = pkgs.writeText "jwtSecret" "jwtSecret";
}; };
}; };
@ -185,36 +179,17 @@ in
dcdomain = config.shb.ldap.dcdomain; dcdomain = config.shb.ldap.dcdomain;
secrets = { secrets = {
jwtSecret.result.path = config.shb.hardcodedsecret.autheliaJwtSecret.path; jwtSecretFile = pkgs.writeText "jwtSecret" "jwtSecret";
ldapAdminPassword.result.path = config.shb.hardcodedsecret.ldapAdminPassword.path; ldapAdminPasswordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword";
sessionSecret.result.path = config.shb.hardcodedsecret.sessionSecret.path; sessionSecretFile = pkgs.writeText "sessionSecret" "sessionSecret";
storageEncryptionKey.result.path = config.shb.hardcodedsecret.storageEncryptionKey.path; storageEncryptionKeyFile = pkgs.writeText "storageEncryptionKey" "storageEncryptionKey";
identityProvidersOIDCHMACSecret.result.path = config.shb.hardcodedsecret.identityProvidersOIDCHMACSecret.path; identityProvidersOIDCHMACSecretFile = pkgs.writeText "identityProvidersOIDCHMACSecret" "identityProvidersOIDCHMACSecret";
identityProvidersOIDCIssuerPrivateKey.result.path = config.shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey.path; identityProvidersOIDCIssuerPrivateKeyFile = (pkgs.runCommand "gen-private-key" {} ''
};
};
shb.hardcodedsecret.autheliaJwtSecret = config.shb.authelia.secrets.jwtSecret.request // {
content = "jwtSecret";
};
shb.hardcodedsecret.ldapAdminPassword = config.shb.authelia.secrets.ldapAdminPassword.request // {
content = "ldapUserPassword";
};
shb.hardcodedsecret.sessionSecret = config.shb.authelia.secrets.sessionSecret.request // {
content = "sessionSecret";
};
shb.hardcodedsecret.storageEncryptionKey = config.shb.authelia.secrets.storageEncryptionKey.request // {
content = "storageEncryptionKey";
};
shb.hardcodedsecret.identityProvidersOIDCHMACSecret = config.shb.authelia.secrets.identityProvidersOIDCHMACSecret.request // {
content = "identityProvidersOIDCHMACSecret";
};
shb.hardcodedsecret.identityProvidersOIDCIssuerPrivateKey = config.shb.authelia.secrets.identityProvidersOIDCIssuerPrivateKey.request // {
source = (pkgs.runCommand "gen-private-key" {} ''
mkdir $out mkdir $out
${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096 ${pkgs.openssl}/bin/openssl genrsa -out $out/private.pem 4096
'') + "/private.pem"; '') + "/private.pem";
}; };
}; };
};
} }

View file

@ -1,35 +0,0 @@
{ pkgs, ... }:
let
contracts = pkgs.callPackage ../../modules/contracts {};
in
{
hardcoded_root_root = contracts.test.secret {
name = "hardcoded";
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
configRoot = [ "shb" "hardcodedsecret" ];
createContent = {
content = "secretA";
};
};
hardcoded_user_group = contracts.test.secret {
name = "hardcoded";
modules = [ ../../modules/blocks/hardcodedsecret.nix ];
configRoot = [ "shb" "hardcodedsecret" ];
createContent = {
content = "secretA";
};
owner = "user";
group = "group";
mode = "640";
};
# TODO: how to do this?
# sops = contracts.test.secret {
# name = "sops";
# configRoot = cfg: name: cfg.sops.secrets.${name};
# createContent = content: {
# sopsFile = ./secret/sops.yaml;
# };
# };
}

View file

@ -1,8 +1,6 @@
{ pkgs, lib, ... }: { pkgs, lib, ... }:
let let
shblib = pkgs.callPackage ../../lib {}; shblib = pkgs.callPackage ../../lib {};
inherit (lib) nameValuePair;
in in
{ {
# Tests that withReplacements can: # Tests that withReplacements can:
@ -81,15 +79,15 @@ in
testLibGetReplacements = { testLibGetReplacements = {
expected = expected =
let let
secrets = root: [ secrets = root: {
(nameValuePair "%SECRET_${root}B%" "$(cat /path/B)") "%SECRET_${root}B%" = "$(cat /path/B)";
(nameValuePair "%SECRET_${root}C%" "prefix-$(cat /path/C)-suffix") "%SECRET_${root}C%" = "prefix-$(cat /path/C)-suffix";
]; };
in in
(secrets "") ++ (secrets "") //
(secrets "DOUBLENESTEDLIST_0_N_") ++ (secrets "NESTEDATTR_") //
(secrets "NESTEDATTR_") ++ (secrets "NESTEDLIST_0_") //
(secrets "NESTEDLIST_0_"); (secrets "DOUBLENESTEDLIST_0_N_");
expr = expr =
let let
item = { item = {
@ -101,13 +99,13 @@ in
c.other = "other"; c.other = "other";
}; };
in in
map shblib.genReplacement (shblib.getReplacements ( shblib.getReplacements (
item // { item // {
nestedAttr = item; nestedAttr = item;
nestedList = [ item ]; nestedList = [ item ];
doubleNestedList = [ { n = item; } ]; doubleNestedList = [ { n = item; } ];
} }
)); );
}; };
testParseXML = { testParseXML = {

View file

@ -29,27 +29,19 @@ let
../../modules/services/forgejo.nix ../../modules/services/forgejo.nix
]; ];
basic = { config, ... }: { basic = {
shb.forgejo = { shb.forgejo = {
enable = true; enable = true;
inherit domain subdomain; inherit domain subdomain;
adminPassword.result.path = config.shb.hardcodedsecret.forgejoAdminPassword.path; adminPasswordFile = pkgs.writeText "adminPasswordFile" adminPassword;
databasePassword.result.path = config.shb.hardcodedsecret.forgejoDatabasePassword.path; databasePasswordFile = pkgs.writeText "databasePassword" "databasePassword";
}; };
# Needed for gitea-runner-local to be able to ping forgejo. # Needed for gitea-runner-local to be able to ping forgejo.
networking.hosts = { networking.hosts = {
"127.0.0.1" = [ "${subdomain}.${domain}" ]; "127.0.0.1" = [ "${subdomain}.${domain}" ];
}; };
shb.hardcodedsecret.forgejoAdminPassword = config.shb.forgejo.adminPassword.request // {
content = adminPassword;
};
shb.hardcodedsecret.forgejoDatabasePassword = config.shb.forgejo.databasePassword.request // {
content = "databasePassword";
};
}; };
https = { config, ... }: { https = { config, ... }: {
@ -65,13 +57,9 @@ let
host = "127.0.0.1"; host = "127.0.0.1";
port = config.shb.ldap.ldapPort; port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain; dcdomain = config.shb.ldap.dcdomain;
adminPassword.result.path = config.shb.hardcodedsecret.forgejoLdapUserPassword.path; adminPasswordFile = config.shb.ldap.ldapUserPassword.result.path;
}; };
}; };
shb.hardcodedsecret.forgejoLdapUserPassword = config.shb.forgejo.ldap.adminPassword.request // {
content = "ldapUserPassword";
};
}; };
sso = { config, ... }: { sso = { config, ... }: {
@ -79,18 +67,10 @@ let
sso = { sso = {
enable = true; enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}"; endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
sharedSecret.result.path = config.shb.hardcodedsecret.forgejoSSOPassword.path; secretFile = pkgs.writeText "ssoSecretFile" "ssoSecretFile";
sharedSecretForAuthelia.result.path = config.shb.hardcodedsecret.forgejoSSOPasswordAuthelia.path; secretFileForAuthelia = pkgs.writeText "ssoSecretFile" "ssoSecretFile";
}; };
}; };
shb.hardcodedsecret.forgejoSSOPassword = config.shb.forgejo.sso.sharedSecret.request // {
content = "ssoPassword";
};
shb.hardcodedsecret.forgejoSSOPasswordAuthelia = config.shb.forgejo.sso.sharedSecretForAuthelia.request // {
content = "ssoPassword";
};
}; };
in in
{ {

View file

@ -43,13 +43,9 @@ let
host = "127.0.0.1"; host = "127.0.0.1";
port = config.shb.ldap.ldapPort; port = config.shb.ldap.ldapPort;
dcdomain = config.shb.ldap.dcdomain; dcdomain = config.shb.ldap.dcdomain;
adminPassword.result.path = config.shb.hardcodedsecret.jellyfinLdapUserPassword.path; passwordFile = config.shb.ldap.ldapUserPassword.result.path;
}; };
}; };
shb.hardcodedsecret.jellyfinLdapUserPassword = config.shb.jellyfin.ldap.adminPassword.request // {
content = "ldapUserPassword";
};
}; };
sso = { config, ... }: { sso = { config, ... }: {
@ -57,18 +53,9 @@ let
sso = { sso = {
enable = true; enable = true;
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}"; endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
sharedSecret.result.path = config.shb.hardcodedsecret.jellyfinSSOPassword.path; secretFile = pkgs.writeText "ssoSecretFile" "ssoSecretFile";
sharedSecretForAuthelia.result.path = config.shb.hardcodedsecret.jellyfinSSOPasswordAuthelia.path;
}; };
}; };
shb.hardcodedsecret.jellyfinSSOPassword = config.shb.jellyfin.sso.sharedSecret.request // {
content = "ssoPassword";
};
shb.hardcodedsecret.jellyfinSSOPasswordAuthelia = config.shb.jellyfin.sso.sharedSecretForAuthelia.request // {
content = "ssoPassword";
};
}; };
in in
{ {