{ lib, config, pkgs, agenix, mysecrets, myvars, ... }: with lib; let cfg = config.modules.secrets; enabledServerSecrets = cfg.server.application.enable || cfg.server.network.enable || cfg.server.operation.enable || cfg.server.kubernetes.enable || cfg.server.webserver.enable || cfg.server.storage.enable; noaccess = { mode = "0000"; owner = "root"; }; high_security = { mode = "0500"; owner = "root"; }; user_readable = { mode = "0500"; owner = myvars.username; }; in { imports = [ agenix.nixosModules.default ]; options.modules.secrets = { desktop.enable = mkEnableOption "NixOS Secrets for Desktops"; server.network.enable = mkEnableOption "NixOS Secrets for Network Servers"; server.application.enable = mkEnableOption "NixOS Secrets for Application Servers"; server.operation.enable = mkEnableOption "NixOS Secrets for Operation Servers(Backup, Monitoring, etc)"; server.kubernetes.enable = mkEnableOption "NixOS Secrets for Kubernetes"; server.webserver.enable = mkEnableOption "NixOS Secrets for Web Servers(contains tls cert keys)"; server.storage.enable = mkEnableOption "NixOS Secrets for HDD Data's LUKS Encryption"; preservation.enable = mkEnableOption "whether use preservation and ephemeral root file system"; }; config = mkIf (cfg.desktop.enable || enabledServerSecrets) (mkMerge [ { environment.systemPackages = [ agenix.packages."${pkgs.stdenv.hostPlatform.system}".default ]; # if you changed this key, you need to regenerate all encrypt files from the decrypt contents! age.identityPaths = if cfg.preservation.enable then [ # To decrypt secrets on boot, this key should exists when the system is booting, # so we should use the real key file path(prefixed by `/persistent/`) here, instead of the path mounted by preservation. "/persistent/etc/ssh/ssh_host_ed25519_key" # Linux ] else [ "/etc/ssh/ssh_host_ed25519_key" ]; # secrets that are used by all nixos hosts age.secrets = { "nix-access-tokens" = { file = "${mysecrets}/server/nix-access-tokens.age"; } # access-token needs to be readable by the user running the `nix` command // user_readable; }; assertions = [ { # This expression should be true to pass the assertion assertion = !(cfg.desktop.enable && enabledServerSecrets); message = "Enable either desktop or server's secrets, not both!"; } ]; } (mkIf cfg.desktop.enable { age.secrets = { # --------------------------------------------- # No discretionary access; privileged root can still bypass file modes. # --------------------------------------------- # .age means the decrypted file is still encrypted by age(via a passphrase) "ryan4yin-gpg-subkeys.priv.age" = { file = "${mysecrets}/desktop/ryan4yin-gpg-subkeys-2024-01-27.priv.age.age"; } // noaccess; # Used only by NixOS Modules "rclone.conf" = { file = "${mysecrets}/desktop/rclone.conf.age"; } // high_security; # --------------------------------------------- # user can read this file. # --------------------------------------------- "ssh-key-romantic" = { file = "${mysecrets}/desktop/ssh-key-romantic.age"; } // user_readable; # work git identity, symlinked to ~/work/.gitconfig by home-manager "work-gitconfig" = { file = "${mysecrets}/desktop/work-gitconfig.age"; } // user_readable; }; # place secrets in /etc/ environment.etc = { "agenix/rclone.conf" = { source = config.age.secrets."rclone.conf".path; }; "agenix/ssh-key-romantic" = { source = config.age.secrets."ssh-key-romantic".path; mode = "0600"; user = myvars.username; }; "agenix/ryan4yin-gpg-subkeys.priv.age" = { source = config.age.secrets."ryan4yin-gpg-subkeys.priv.age".path; mode = "0000"; }; # Used by a home-manager module, so it must be readable by the user. # Setting `mode` makes environment.etc copy the file instead of # symlinking the runtime secret, so the owner must be set as well — # otherwise it becomes a world-readable, root-owned copy of the secret. "agenix/work-gitconfig" = { source = config.age.secrets."work-gitconfig".path; mode = "0400"; user = myvars.username; }; }; }) (mkIf cfg.server.application.enable { age.secrets = { "transmission-credentials.json" = { file = "${mysecrets}/server/transmission-credentials.json.age"; } // high_security; "sftpgo.env" = { file = "${mysecrets}/server/sftpgo.env.age"; mode = "0400"; owner = "sftpgo"; }; # RustFS root credentials as a systemd EnvironmentFile consumed by # services.rustfs: RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY. "rustfs.env" = { file = "${mysecrets}/server/rustfs.env.age"; mode = "0400"; owner = "root"; }; }; }) (mkIf cfg.server.operation.enable { age.secrets = { "grafana-admin-password" = { file = "${mysecrets}/server/grafana-admin-password.age"; mode = "0400"; owner = "grafana"; }; "grafana-secret-key" = { file = "${mysecrets}/server/grafana-secret-key.age"; mode = "0400"; owner = "grafana"; }; "alertmanager.env" = { file = "${mysecrets}/server/alertmanager.env.age"; } // high_security; }; }) (mkIf cfg.server.kubernetes.enable { age.secrets = { "k3s-test-1-token" = { file = "${mysecrets}/server/k3s-test-1-token.age"; } // high_security; }; }) (mkIf cfg.server.webserver.enable { age.secrets = { "caddy-ecc-server.key" = { file = "${mysecrets}/certs/ecc-server.key.age"; mode = "0400"; owner = "caddy"; }; "postgres-ecc-server.key" = { file = "${mysecrets}/certs/ecc-server.key.age"; mode = "0400"; owner = "postgres"; }; # Cloudflare API token (Zone -> DNS -> Edit on `writefor.fun`) consumed # by security.acme (lego) as its environment file for the ACME DNS-01 # challenge on publicly-trusted sites. lego runs as root here. "cloudflare-dns-api-token" = { file = "${mysecrets}/server/cloudflare-dns-api-token.env.age"; mode = "0400"; owner = "root"; }; }; }) (mkIf cfg.desktop.enable { # Desktops use their own restic repository password, NOT the homelab's: # the backup server holds the homelab one, so a shared password would let # that server decrypt desktop data. age.secrets."restic-password" = { file = "${mysecrets}/desktop/restic-password-desktop.age"; mode = "0400"; owner = "root"; }; # credentials for the restic REST server that receives this backup age.secrets."restic-rest-credentials" = { file = "${mysecrets}/desktop/restic-rest-credentials.age"; mode = "0400"; owner = "root"; }; }) (mkIf cfg.server.storage.enable { # the homelab's restic repository password, shared by the homelab hosts # (this host holds it, so it can copy/verify homelab repositories) age.secrets."restic-password" = { file = "${mysecrets}/server/restic-password-homelab.age"; mode = "0400"; owner = "root"; }; # htpasswd for the restic REST server this host runs; the service runs as # the unprivileged `restic` user, which must be able to read it age.secrets."restic-rest-htpasswd" = { file = "${mysecrets}/server/restic-rest-htpasswd.age"; mode = "0400"; owner = "restic"; }; }) (mkIf cfg.server.storage.enable { age.secrets = { "hdd-luks-crypt-key" = { file = "${mysecrets}/server/hdd-luks-crypt-key.age"; mode = "0400"; owner = "root"; }; # restic repository password (used by modules.restic-backup). Gated # separately below so desktops get it without enabling a server group. }; # place secrets in /etc/ environment.etc = { "agenix/hdd-luks-crypt-key" = { source = config.age.secrets."hdd-luks-crypt-key".path; mode = "0400"; user = "root"; }; }; }) ]); }