Compare commits
2 commits
1730970935
...
2b06848632
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b06848632 | |||
| 3fd32ffa45 |
7 changed files with 324 additions and 0 deletions
30
bin/secret-translator.jl
Normal file
30
bin/secret-translator.jl
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env julia
|
||||||
|
using TOML: TOML
|
||||||
|
|
||||||
|
function remove_podman_secrets()
|
||||||
|
run(`podman secret rm --all`)
|
||||||
|
return nothing
|
||||||
|
end #function
|
||||||
|
|
||||||
|
function create_podman_secret(name, secret)
|
||||||
|
run(
|
||||||
|
Cmd(
|
||||||
|
`podman secret create --env=true --replace $name SECRET`;
|
||||||
|
env = Dict("SECRET" => secret),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return nothing
|
||||||
|
end #function
|
||||||
|
|
||||||
|
function parse_toml_secrets(file)
|
||||||
|
foreach(f -> create_podman_secret(f[1], f[2]), TOML.parsefile(file))
|
||||||
|
return nothing
|
||||||
|
end #function
|
||||||
|
|
||||||
|
function main()
|
||||||
|
remove_podman_secrets()
|
||||||
|
foreach(parse_toml_secrets, ARGS)
|
||||||
|
return 0
|
||||||
|
end #function
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
@ -138,9 +138,13 @@
|
||||||
|
|
||||||
"mcentire" = nixpkgs.lib.nixosSystem {
|
"mcentire" = nixpkgs.lib.nixosSystem {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
|
specialArgs = {
|
||||||
|
home-manager-quadlet-nix = quadlet-nix.homeManagerModules.quadlet;
|
||||||
|
};
|
||||||
modules = [
|
modules = [
|
||||||
./systems/linux/mcentire.nix
|
./systems/linux/mcentire.nix
|
||||||
agenix.nixosModules.default
|
agenix.nixosModules.default
|
||||||
|
home-manager.nixosModules.home-manager
|
||||||
quadlet-nix.nixosModules.quadlet
|
quadlet-nix.nixosModules.quadlet
|
||||||
crowdsec.nixosModules.crowdsec
|
crowdsec.nixosModules.crowdsec
|
||||||
crowdsec.nixosModules.crowdsec-firewall-bouncer
|
crowdsec.nixosModules.crowdsec-firewall-bouncer
|
||||||
|
|
|
||||||
154
modules/podman-secrets.nix
Normal file
154
modules/podman-secrets.nix
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
# Warning to my future self: This module was "vibe coded" by Claude Sonnet 4.5.
|
||||||
|
# I originally had a hand-written module in here that did the secrets
|
||||||
|
# translation as the root user. I knew that I would want to support multiple
|
||||||
|
# secrets files and multiple users, and thought I should be able to create an
|
||||||
|
# arbitrary number of them, similar to how you can have an arbitrary number
|
||||||
|
# of `programs.firefox.profiles`. Unfortunately, even after looking at lots of
|
||||||
|
# example modules, I could not figure out the syntax (Nix has a serious lack
|
||||||
|
# of minimum working examples), so I broke down and asked Claude to rewrite it
|
||||||
|
# for me. Based on everything that I read, this seems to be exactly what I asked
|
||||||
|
# for.
|
||||||
|
#
|
||||||
|
# Here is the prompt I used to get here:
|
||||||
|
# [@Per service isolation on NixOS with Traefik](zed:///agent/thread/94cb8a22-ff0c-4772-a1a1-018b0107f334?name=Per+service+isolation+on+NixOS+with+Traefik)
|
||||||
|
#
|
||||||
|
# [@podman-secrets.nix](file:///home/millironx/.config/home-manager/modules/podman-secrets.nix)
|
||||||
|
#
|
||||||
|
# I originally wrote this module assuming that I would use Podman as root for
|
||||||
|
# all containers. I would like to fix the module to have as many secrets files
|
||||||
|
# processed as needed with services setup for each secret that is run as the
|
||||||
|
# appropriate user. Ideally, the syntax for working with secrets to be
|
||||||
|
# translated would be
|
||||||
|
#
|
||||||
|
# ```nix
|
||||||
|
# { config, ... }: {
|
||||||
|
# age.secrets = {
|
||||||
|
# "caddy.toml" = {
|
||||||
|
# file = ./../secrets/caddy.toml.age;
|
||||||
|
# owner = "caddy";
|
||||||
|
# group = "caddy";
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# "authentik.toml" = {
|
||||||
|
# file = ./../secrets/authentik.toml.age;
|
||||||
|
# owner = "authentik";
|
||||||
|
# group = "authentik";
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# "freshrss.toml" = {
|
||||||
|
# file = ./../secrets/freshrss.toml.age;
|
||||||
|
# owner = "freshrss";
|
||||||
|
# group = "freshrss";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# millironx.podman-secrets = with config; {
|
||||||
|
# caddy = {
|
||||||
|
# user = "caddy";
|
||||||
|
# secrets-files = [
|
||||||
|
# ./../not-really-secret.toml
|
||||||
|
# age.secrets."caddy.toml".path
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
# authentik = {
|
||||||
|
# user = "authentik";
|
||||||
|
# secrets-files = [
|
||||||
|
# age.secrets."authentik.toml".path
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
# freshrss = {
|
||||||
|
# user = "freshrss";
|
||||||
|
# secrets-files = [
|
||||||
|
# age.secrets."freshrss.toml".path
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# }
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Can you help me rewrite the module to accomplish this?
|
||||||
|
#
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.millironx.podman-secrets;
|
||||||
|
|
||||||
|
secret-translator = pkgs.writeScriptBin "secret-translator"
|
||||||
|
(builtins.readFile ./../bin/secret-translator.jl);
|
||||||
|
|
||||||
|
# Submodule type for each service's secrets configuration
|
||||||
|
serviceSecretsType = types.submodule {
|
||||||
|
options = {
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "User account to run the secrets translation service as";
|
||||||
|
example = "caddy";
|
||||||
|
};
|
||||||
|
|
||||||
|
secrets-files = mkOption {
|
||||||
|
type = types.listOf (types.either types.path types.string);
|
||||||
|
description =
|
||||||
|
"List of TOML files containing secrets to translate to Podman secrets";
|
||||||
|
example = literalExpression ''
|
||||||
|
[
|
||||||
|
"/run/agenix/caddy.toml"
|
||||||
|
./../not-really-secret.toml
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Generate a systemd service for each configured service
|
||||||
|
mkSecretsService = name: serviceCfg:
|
||||||
|
nameValuePair "podman-secrets-${name}" {
|
||||||
|
description = "Podman secrets converter service for ${name}";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = serviceCfg.user;
|
||||||
|
ExecStart = "${secret-translator}/bin/secret-translator ${
|
||||||
|
lib.concatStringsSep " " serviceCfg.secrets-files
|
||||||
|
}";
|
||||||
|
Path = [ "${pkgs.podman}/bin" "${pkgs.julia-lts-bin}/bin" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Filter out services with no secrets-files
|
||||||
|
enabledServices =
|
||||||
|
lib.filterAttrs (name: serviceCfg: serviceCfg.secrets-files != [ ]) cfg;
|
||||||
|
|
||||||
|
in {
|
||||||
|
options.millironx.podman-secrets = mkOption {
|
||||||
|
type = types.attrsOf serviceSecretsType;
|
||||||
|
description = ''
|
||||||
|
Per-service Podman secrets configuration.
|
||||||
|
Each attribute creates a separate systemd service that translates TOML secrets
|
||||||
|
files into Podman secrets for the specified user.
|
||||||
|
'';
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
caddy = {
|
||||||
|
user = "caddy";
|
||||||
|
secrets-files = [
|
||||||
|
./../not-really-secret.toml
|
||||||
|
config.age.secrets."caddy.toml".path
|
||||||
|
];
|
||||||
|
};
|
||||||
|
authentik = {
|
||||||
|
user = "authentik";
|
||||||
|
secrets-files = [
|
||||||
|
config.age.secrets."authentik.toml".path
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf (enabledServices != { }) {
|
||||||
|
systemd.services = lib.mapAttrs' mkSecretsService enabledServices;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ let
|
||||||
|
|
||||||
in {
|
in {
|
||||||
"secrets/ansible-vault-password.age".publicKeys = system-administrators;
|
"secrets/ansible-vault-password.age".publicKeys = system-administrators;
|
||||||
|
"secrets/authentik.toml.age".publicKeys = system-administrators
|
||||||
|
++ [ mcentire-host ];
|
||||||
"secrets/borgmatic-passphrase.age".publicKeys = system-administrators
|
"secrets/borgmatic-passphrase.age".publicKeys = system-administrators
|
||||||
++ [ mcentire-host ];
|
++ [ mcentire-host ];
|
||||||
"secrets/borgmatic-ssh-config.age".publicKeys = system-administrators
|
"secrets/borgmatic-ssh-config.age".publicKeys = system-administrators
|
||||||
|
|
|
||||||
BIN
secrets/authentik.toml.age
Normal file
BIN
secrets/authentik.toml.age
Normal file
Binary file not shown.
128
services/authentik.nix
Normal file
128
services/authentik.nix
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
{ config, home-manager-quadlet-nix, ... }:
|
||||||
|
let
|
||||||
|
user = "authentik";
|
||||||
|
state-directory = "/var/lib/authentik";
|
||||||
|
port = "9000";
|
||||||
|
|
||||||
|
in {
|
||||||
|
# Secrets are translated in the system scope, but performed by the user,
|
||||||
|
# so are available in the user's Podman secrets
|
||||||
|
age.secrets = {
|
||||||
|
"authentik.toml" = {
|
||||||
|
file = ./../secrets/authentik.toml.age;
|
||||||
|
owner = "${user}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
millironx.podman-secrets.authentik = {
|
||||||
|
user = "${user}";
|
||||||
|
secrets-files = [ config.age.secrets."authentik.toml".path ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [ "d ${state-directory} 1775 ${user} ${user} -" ];
|
||||||
|
|
||||||
|
services.caddy.virtualHosts."auth.millironx.com".extraConfig = ''
|
||||||
|
reverse_proxy http://127.0.0.1:${port}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Create a dedicated user for this service
|
||||||
|
users.users."${user}" = {
|
||||||
|
group = "${user}";
|
||||||
|
isSystemUser = true;
|
||||||
|
linger = true;
|
||||||
|
autoSubUidGidRange = true;
|
||||||
|
};
|
||||||
|
users.groups."${user}" = {};
|
||||||
|
|
||||||
|
home-manager.users."${user}" = { config, ... }: {
|
||||||
|
imports = [ home-manager-quadlet-nix ];
|
||||||
|
|
||||||
|
home.stateVersion = "25.05";
|
||||||
|
|
||||||
|
virtualisation.quadlet.containers = {
|
||||||
|
authentik-db = {
|
||||||
|
autoStart = true;
|
||||||
|
containerConfig = {
|
||||||
|
image = "docker.io/library/postgres:16";
|
||||||
|
environments = {
|
||||||
|
POSTGRES_DB = "${user}";
|
||||||
|
POSTGRES_USER = "${user}";
|
||||||
|
};
|
||||||
|
secrets = [
|
||||||
|
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env,target=POSTGRES_PASSWORD"
|
||||||
|
];
|
||||||
|
healthCmd = "pg_isready -d \${POSTGRES_DB} -U \${POSTGRES_USER}";
|
||||||
|
healthInterval = "30s";
|
||||||
|
healthRetries = 5;
|
||||||
|
healthStartPeriod = "20s";
|
||||||
|
volumes = [ "${state-directory}/database:/var/lib/postgresql/data" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
authentik-worker = {
|
||||||
|
autoStart = true;
|
||||||
|
containerConfig = {
|
||||||
|
image = "ghcr.io/goauthentik/server:2025.10.2";
|
||||||
|
environments = {
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST = "authentik-db";
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME = "${user}";
|
||||||
|
AUTHENTIK_POSTGRESQL__USER = "${user}";
|
||||||
|
};
|
||||||
|
exec = "worker";
|
||||||
|
secrets = [
|
||||||
|
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env"
|
||||||
|
"AUTHENTIK_SECRET_KEY,type=env"
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"${state-directory}/media:/media"
|
||||||
|
"${state-directory}/custom-templates:/templates"
|
||||||
|
"${state-directory}/certs:/certs"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
unitConfig.Requires = [
|
||||||
|
config.virtualisation.quadlet.containers.authentik-db.ref
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
unitConfig.After = [
|
||||||
|
config.virtualisation.quadlet.containers.authentik-db.ref
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
authentik = {
|
||||||
|
autoStart = true;
|
||||||
|
containerConfig = {
|
||||||
|
image = "ghcr.io/goauthentik/server:2025.10.2";
|
||||||
|
environments = {
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST = "authentik-db";
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME = "${user}";
|
||||||
|
AUTHENTIK_POSTGRESQL__USER = "${user}";
|
||||||
|
};
|
||||||
|
secrets = [
|
||||||
|
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env"
|
||||||
|
"AUTHENTIK_SECRET_KEY,type=env"
|
||||||
|
"AUTHENTIK_EMAIL__HOST,type=env"
|
||||||
|
"AUTHENTIK_EMAIL__PORT,type=env"
|
||||||
|
"AUTHENTIK_EMAIL__USERNAME,type=env"
|
||||||
|
"AUTHENTIK_EMAIL__PASSWORD,type=env"
|
||||||
|
"AUTHENTIK_EMAIL__USE_SSL,type=env"
|
||||||
|
"AUTHENTIK_EMAIL__FROM,type=env"
|
||||||
|
];
|
||||||
|
publishPorts = [ "127.0.0.1:${port}:${port}" ];
|
||||||
|
volumes = [
|
||||||
|
"${state-directory}/media:/media"
|
||||||
|
"${state-directory}/custom-templates:/templates"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
unitConfig.Requires = [
|
||||||
|
config.virtualisation.quadlet.containers.authentik-db.ref
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
unitConfig.After = [
|
||||||
|
config.virtualisation.quadlet.containers.authentik-db.ref
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
{
|
{
|
||||||
imports = [ # Include the results of the hardware scan.
|
imports = [ # Include the results of the hardware scan.
|
||||||
./hardware-configuration/mcentire.nix
|
./hardware-configuration/mcentire.nix
|
||||||
|
./../../modules/podman-secrets.nix
|
||||||
./../../services/nixos-update.nix
|
./../../services/nixos-update.nix
|
||||||
./../../services/borgmatic.nix
|
./../../services/borgmatic.nix
|
||||||
./../../services/crowdsec.nix
|
./../../services/crowdsec.nix
|
||||||
|
./../../services/authentik.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
# Use the GRUB 2 boot loader.
|
# Use the GRUB 2 boot loader.
|
||||||
|
|
@ -16,6 +18,7 @@
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
interfaces.eth0.useDHCP = true;
|
interfaces.eth0.useDHCP = true;
|
||||||
hostName = "mcentire"; # Define your hostname.
|
hostName = "mcentire"; # Define your hostname.
|
||||||
|
firewall.allowedTCPPorts = [ 80 443 ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Set your time zone.
|
# Set your time zone.
|
||||||
|
|
@ -55,8 +58,11 @@
|
||||||
services = {
|
services = {
|
||||||
openssh.enable = true;
|
openssh.enable = true;
|
||||||
tailscale.enable = true;
|
tailscale.enable = true;
|
||||||
|
caddy.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
virtualisation.quadlet.enable = true;
|
||||||
|
|
||||||
system.stateVersion = "25.05"; # Did you read the comment?
|
system.stateVersion = "25.05"; # Did you read the comment?
|
||||||
nix = { extraOptions = "experimental-features = nix-command flakes"; };
|
nix = { extraOptions = "experimental-features = nix-command flakes"; };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue