integrate sops-nix for secret management

This commit is contained in:
Satria 2026-03-11 19:24:17 +07:00
commit 99ad3058a3
10 changed files with 141 additions and 16 deletions

25
.sops.yaml Normal file
View file

@ -0,0 +1,25 @@
# To set up sops-nix:
# 1. Generate an age key on each host:
# mkdir -p ~/.config/sops/age
# age-keygen -o ~/.config/sops/age/keys.txt
# Or derive from the host SSH key:
# nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
#
# 2. Replace the placeholder age keys below with the actual public keys.
#
# 3. Encrypt secret files:
# sops secrets/homelab.yaml
#
# 4. To re-key after changing keys:
# sops updatekeys secrets/homelab.yaml
keys:
- &homelab age1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # replace with: ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub
- &admin age1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # replace with: age-keygen output from your admin machine
creation_rules:
- path_regex: secrets/homelab\.yaml$
key_groups:
- age:
- *homelab
- *admin

View file

@ -1,7 +1,7 @@
{ homelab, ... }: {
{ config, homelab, ... }: {
services.pocket-id = {
enable = true;
credentials.ENCRYPTION_KEY = "/mnt/data/pocketid/encryption-key";
credentials.ENCRYPTION_KEY = config.sops.secrets.pocketid_encryption_key.path;
dataDir = "/mnt/data/pocketid/data";
settings = {
PORT = "1411";

View file

@ -1,4 +1,4 @@
{ timezone, homelab, ... }: let
{ config, timezone, homelab, ... }: let
rss = [
"https://www.raspberrypi.com/news/feed/"
"https://www.jeffgeerling.com/blog.xml"
@ -96,7 +96,7 @@ in {
};
services.glance = {
enable = true;
environmentFile = "/var/lib/glance/.env";
environmentFile = config.sops.secrets.glance_env.path;
settings = {
server = {
host = "127.0.0.1";

View file

@ -1,9 +1,9 @@
{ homelab, ... }: {
{ config, homelab, ... }: {
services.vaultwarden = {
enable = true;
domain = "pass.proxy.${homelab.domain}";
backupDir = "/mnt/data/vaultwarden/backups";
environmentFile = "/mnt/data/vaultwarden/.env";
environmentFile = config.sops.secrets.vaultwarden_env.path;
config = {
ROCKET_PORT = 8060;
ROCKET_ADDRESS = "127.0.0.1";

View file

@ -1,4 +1,4 @@
{ homelab, lib, ... }: let
{ config, homelab, lib, ... }: let
base = "proxy.${homelab.domain}";
hosts = {
"server" = { dest = "https://server.dns.${homelab.domain}:8006"; auth = false; };
@ -45,8 +45,7 @@ in {
domain = "*.${base}";
extraDomainNames = [ base ];
dnsProvider = "cloudflare";
environmentFile = "/var/lib/acme/cloudflare.env";
# ^^^contents: CLOUDFLARE_DNS_API_TOKEN=XXXXX
environmentFile = config.sops.templates."cloudflare.env".path;
};
};
@ -81,7 +80,7 @@ in {
locations."/" = {
proxyPass = cfg.dest;
proxyWebsockets = true;
basicAuthFile = if cfg.auth then "/var/lib/nginx/.htpasswd" else null;
basicAuthFile = if cfg.auth then config.sops.secrets.nginx_htpasswd.path else null;
extraConfig = exta-conf;
};
}) hosts;

View file

@ -0,0 +1,59 @@
{ config, ... }: {
sops = {
defaultSopsFile = ../../../secrets/homelab.yaml;
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
secrets = {
cloudflare_dns_api_token = {
owner = "acme";
group = "acme";
};
cloudflared_tunnel_credentials = {
owner = "cloudflared";
group = "cloudflared";
};
cloudflared_cert = {
owner = "cloudflared";
group = "cloudflared";
};
vaultwarden_env = {
owner = "vaultwarden";
group = "vaultwarden";
restartUnits = [ "vaultwarden.service" ];
};
glance_env = {
owner = "glance";
group = "glance";
restartUnits = [ "glance.service" ];
};
pocketid_encryption_key = {
owner = "root";
group = "root";
restartUnits = [ "pocket-id.service" ];
};
tailscale_authkey = {
owner = "root";
group = "root";
restartUnits = [ "tailscaled.service" ];
};
nginx_htpasswd = {
owner = "nginx";
group = "nginx";
restartUnits = [ "nginx.service" ];
};
};
templates."cloudflare.env" = {
owner = "acme";
group = "acme";
content = "CLOUDFLARE_DNS_API_TOKEN=${config.sops.placeholder.cloudflare_dns_api_token}";
};
};
}

View file

@ -1,4 +1,4 @@
{ pkgs, lib, homelab, ... }: let
{ config, pkgs, lib, homelab, ... }: let
routes = {
"git.${homelab.domain}" = "http://localhost:5080";
"auth.${homelab.domain}" = "http://localhost:1411";
@ -10,8 +10,8 @@ in {
services.cloudflared = {
enable = true;
tunnels.homelab = {
credentialsFile = "/mnt/data/cloudflared/homelab.json";
certificateFile = "/mnt/data/cloudflared/cert.pem";
credentialsFile = config.sops.secrets.cloudflared_tunnel_credentials.path;
certificateFile = config.sops.secrets.cloudflared_cert.path;
default = "http_status:404";
ingress = routes;
};
@ -31,7 +31,7 @@ in {
script = lib.concatMapStringsSep "\n" (domain: ''
echo "Ensuring DNS route for ${domain}..."
${pkgs.cloudflared}/bin/cloudflared tunnel --origincert /mnt/data/cloudflared/cert.pem route dns ${homelab.cf-tunnel-id} ${domain} || true
${pkgs.cloudflared}/bin/cloudflared tunnel --origincert ${config.sops.secrets.cloudflared_cert.path} route dns ${homelab.cf-tunnel-id} ${domain} || true
'') (builtins.attrNames routes);
};
}

View file

@ -1,4 +1,4 @@
{ lib, homelab, ... }: let
{ config, lib, homelab, ... }: let
ts-flags = [
"--advertise-exit-node"
"--advertise-routes=10.3.14.0/24,192.168.1.0/24"
@ -20,6 +20,7 @@ in {
./homelab/dns.nix
./homelab/git.nix
./homelab/ai.nix
./homelab/sops.nix
./core/swapfile.nix
./core/oom.nix
@ -29,7 +30,7 @@ in {
services.tailscale = {
enable = true;
authKeyFile = "/mnt/data/tailscale/authkey";
authKeyFile = config.sops.secrets.tailscale_authkey.path;
useRoutingFeatures = "server";
extraUpFlags = ts-flags;
extraSetFlags = ts-flags;

30
scripts/check-sops.sh Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Pre-commit hook: block commits containing unencrypted sops secret files.
# Install with: ln -sf ../../scripts/check-sops.sh .git/hooks/pre-commit
set -euo pipefail
staged_secrets=$(git diff --cached --name-only --diff-filter=ACM -- 'secrets/*.yaml' 'secrets/*.yml' 'secrets/*.json')
if [ -z "$staged_secrets" ]; then
exit 0
fi
failed=0
for file in $staged_secrets; do
# sops-encrypted YAML/JSON files always contain a top-level "sops" key with metadata
if ! git show ":$file" | grep -q '"sops"\|sops:'; then
echo "ERROR: $file is not encrypted with sops! Encrypt it first:"
echo " sops $file"
echo
echo "hint: bypass with: git commit --no-verify"
failed=1
fi
done
if [ "$failed" -ne 0 ]; then
echo ""
echo "Commit aborted. Encrypt secret files before committing."
exit 1
fi

11
secrets/homelab.yaml Normal file
View file

@ -0,0 +1,11 @@
# This file should be encrypted with sops before committing.
# Run: sops secrets/homelab.yaml
# All values below are placeholders. Replace them with actual values.
cloudflare_dns_api_token: REPLACE_ME
cloudflared_tunnel_credentials: REPLACE_ME
cloudflared_cert: REPLACE_ME
vaultwarden_env: REPLACE_ME
glance_env: REPLACE_ME
pocketid_encryption_key: REPLACE_ME
tailscale_authkey: REPLACE_ME
nginx_htpasswd: REPLACE_ME