Begin setting up authentication and secrets engine
This commit is contained in:
parent
81551dd791
commit
f3175b9b03
20 changed files with 430 additions and 258 deletions
172
flake.lock
generated
172
flake.lock
generated
|
@ -16,58 +16,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"authentik-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"authentik-src": "authentik-src",
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"flake-parts": [
|
|
||||||
"flake-parts"
|
|
||||||
],
|
|
||||||
"flake-utils": [
|
|
||||||
"flake-utils"
|
|
||||||
],
|
|
||||||
"napalm": "napalm",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"pyproject-build-systems": "pyproject-build-systems",
|
|
||||||
"pyproject-nix": "pyproject-nix",
|
|
||||||
"systems": [
|
|
||||||
"systems"
|
|
||||||
],
|
|
||||||
"uv2nix": "uv2nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1751033152,
|
|
||||||
"narHash": "sha256-0ANu9OLQJszcEyvnfDB7G957uqskZwCrTzRXz/yfAmE=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "authentik-nix",
|
|
||||||
"rev": "1a4d6a5dd6fef39b99eb7ea4db79c5d5c7d7f1bf",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "authentik-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"authentik-src": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1751031262,
|
|
||||||
"narHash": "sha256-SNgRMQUjL3DTlWkMyRMan+pY1FfIV+DMeq5BiTM0N0k=",
|
|
||||||
"owner": "goauthentik",
|
|
||||||
"repo": "authentik",
|
|
||||||
"rev": "b34665fabd8d938d81ce871a4e86ca528c5f253b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "goauthentik",
|
|
||||||
"ref": "version/2025.4.3",
|
|
||||||
"repo": "authentik",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"base16": {
|
"base16": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fromYaml": "fromYaml"
|
"fromYaml": "fromYaml"
|
||||||
|
@ -237,22 +185,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1747046372,
|
|
||||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat_2": {
|
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1650374568,
|
"lastModified": 1650374568,
|
||||||
|
@ -397,32 +329,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"napalm": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": [
|
|
||||||
"authentik-nix",
|
|
||||||
"flake-utils"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"authentik-nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1725806412,
|
|
||||||
"narHash": "sha256-lGZjkjds0p924QEhm/r0BhAxbHBJE1xMOldB/HmQH04=",
|
|
||||||
"owner": "willibutz",
|
|
||||||
"repo": "napalm",
|
|
||||||
"rev": "b492440d9e64ae20736d3bec5c7715ffcbde83f5",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "willibutz",
|
|
||||||
"ref": "avoid-foldl-stack-overflow",
|
|
||||||
"repo": "napalm",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-vm-test": {
|
"nix-vm-test": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
@ -575,60 +481,9 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pyproject-build-systems": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"authentik-nix",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"pyproject-nix": [
|
|
||||||
"authentik-nix",
|
|
||||||
"pyproject-nix"
|
|
||||||
],
|
|
||||||
"uv2nix": [
|
|
||||||
"authentik-nix",
|
|
||||||
"uv2nix"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1749519371,
|
|
||||||
"narHash": "sha256-UJONN7mA2stweZCoRcry2aa1XTTBL0AfUOY84Lmqhos=",
|
|
||||||
"owner": "pyproject-nix",
|
|
||||||
"repo": "build-system-pkgs",
|
|
||||||
"rev": "7c06967eca687f3482624250428cc12f43c92523",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "pyproject-nix",
|
|
||||||
"repo": "build-system-pkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pyproject-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"authentik-nix",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1750499893,
|
|
||||||
"narHash": "sha256-ThKBd8XSvITAh2JqU7enOp8AfKeQgf9u7zYC41cnBE4=",
|
|
||||||
"owner": "pyproject-nix",
|
|
||||||
"repo": "pyproject.nix",
|
|
||||||
"rev": "e824458bd917b44bf4c38795dea2650336b2f55d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "pyproject-nix",
|
|
||||||
"repo": "pyproject.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"advisory-db": "advisory-db",
|
"advisory-db": "advisory-db",
|
||||||
"authentik-nix": "authentik-nix",
|
|
||||||
"cosmic-manager": "cosmic-manager",
|
"cosmic-manager": "cosmic-manager",
|
||||||
"crane": "crane",
|
"crane": "crane",
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
|
@ -668,7 +523,7 @@
|
||||||
},
|
},
|
||||||
"snowfall-lib": {
|
"snowfall-lib": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_2",
|
"flake-compat": "flake-compat",
|
||||||
"flake-utils-plus": "flake-utils-plus",
|
"flake-utils-plus": "flake-utils-plus",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"flake-base",
|
"flake-base",
|
||||||
|
@ -945,31 +800,6 @@
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"uv2nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"authentik-nix",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"pyproject-nix": [
|
|
||||||
"authentik-nix",
|
|
||||||
"pyproject-nix"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1750987094,
|
|
||||||
"narHash": "sha256-GujDElxLgYatnNvuL1U6qd18lcuG6anJMjpfYRScV08=",
|
|
||||||
"owner": "pyproject-nix",
|
|
||||||
"repo": "uv2nix",
|
|
||||||
"rev": "4b703d851b61e664a70238711a8ff0efa1aa2f52",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "pyproject-nix",
|
|
||||||
"repo": "uv2nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
10
flake.nix
10
flake.nix
|
@ -2,15 +2,6 @@
|
||||||
description = "A very basic flake";
|
description = "A very basic flake";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
authentik-nix = {
|
|
||||||
url = "github:nix-community/authentik-nix";
|
|
||||||
inputs = {
|
|
||||||
flake-utils.follows = "flake-utils";
|
|
||||||
nixpkgs.follows = "nixpkgs";
|
|
||||||
flake-parts.follows = "flake-parts";
|
|
||||||
systems.follows = "systems";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
|
||||||
flake-base = {
|
flake-base = {
|
||||||
url = "git+https://khs.codes/nix/flake-base";
|
url = "git+https://khs.codes/nix/flake-base";
|
||||||
|
@ -108,7 +99,6 @@
|
||||||
inputNixosModules = [
|
inputNixosModules = [
|
||||||
inputs.disko.nixosModules.disko
|
inputs.disko.nixosModules.disko
|
||||||
inputs.stylix.nixosModules.stylix
|
inputs.stylix.nixosModules.stylix
|
||||||
inputs.authentik-nix.nixosModules.default
|
|
||||||
];
|
];
|
||||||
inputHomeModules = [
|
inputHomeModules = [
|
||||||
inputs.cosmic-manager.homeManagerModules.cosmic-manager
|
inputs.cosmic-manager.homeManagerModules.cosmic-manager
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
options.khscodes.infrastructure.openbao = {
|
options.khscodes.infrastructure.openbao = {
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "vault.kaareskovgaard.net";
|
default = "secrets.kaareskovgaard.net";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,7 @@ in
|
||||||
role_name = config.khscodes.vault.output.approle_auth_backend_role.${cfg.role_name}.role_name;
|
role_name = config.khscodes.vault.output.approle_auth_backend_role.${cfg.role_name}.role_name;
|
||||||
# Should only be 5-10 mins once done testing
|
# Should only be 5-10 mins once done testing
|
||||||
wrapping_ttl = 5 * 60;
|
wrapping_ttl = 5 * 60;
|
||||||
|
num_uses = 0;
|
||||||
|
|
||||||
# This should simply mean that we never attempt to recreate the secret id, as we don't want a rerun of the
|
# This should simply mean that we never attempt to recreate the secret id, as we don't want a rerun of the
|
||||||
# provisioning to invalidate the existing secret id, nor recreate the entire server.
|
# provisioning to invalidate the existing secret id, nor recreate the entire server.
|
||||||
|
|
31
nix/packages/bao-import-secret/default.nix
Normal file
31
nix/packages/bao-import-secret/default.nix
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "bao-import-secret";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.openbao
|
||||||
|
pkgs.uutils-coreutils-noprefix
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
role_id="''${1:-}"
|
||||||
|
wrapped_secret_id="''${2:-}"
|
||||||
|
|
||||||
|
if [[ "$role_id" == "" || "$wrapped_secret_id" == "" ]]; then
|
||||||
|
>&2 echo "Usage: bao-import-secret <role-id> <wrapped-secret-id>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(id -u)" != "0" ]]; then
|
||||||
|
>&2 echo "Must run as root"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
secret_id="$(bao unwrap -field=secret_id "$wrapped_secret_id")"
|
||||||
|
mkdir -p /var/lib/vault-agent
|
||||||
|
touch /var/lib/vault-agent/role-id
|
||||||
|
touch /var/lib/vault-agent/secret-id
|
||||||
|
chmod 0600 /var/lib/vault-agent/role-id
|
||||||
|
chmod 0600 /var/lib/vault-agent/secret-id
|
||||||
|
echo -n "$role_id" > /var/lib/vault-agent/role-id
|
||||||
|
echo -n "$secret_id" > /var/lib/vault-agent/secret-id
|
||||||
|
systemctl restart vault-agent-openbao.service
|
||||||
|
'';
|
||||||
|
}
|
|
@ -25,7 +25,7 @@
|
||||||
"AUTHENTIK_TOKEN" = "Admin API Token";
|
"AUTHENTIK_TOKEN" = "Admin API Token";
|
||||||
"TF_VAR_authentik_username" = "login.username";
|
"TF_VAR_authentik_username" = "login.username";
|
||||||
};
|
};
|
||||||
"vault.kaareskovgaard.net" = {
|
"secrets.kaareskovgaard.net" = {
|
||||||
"VAULT_TOKEN" = "Initial root token";
|
"VAULT_TOKEN" = "Initial root token";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
10
nix/packages/configure-instance/default.nix
Normal file
10
nix/packages/configure-instance/default.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "configure-instance";
|
||||||
|
runtimeInputs = [ pkgs.khscodes.post-provisioning ];
|
||||||
|
text = ''
|
||||||
|
instance="''${1:-}"
|
||||||
|
cmd="''${2:-apply}"
|
||||||
|
post-provisioning "$instance" "$cmd"
|
||||||
|
'';
|
||||||
|
}
|
|
@ -5,4 +5,5 @@ pkgs.opentofu.withPlugins (p: [
|
||||||
pkgs.khscodes.terraform-provider-hcloud
|
pkgs.khscodes.terraform-provider-hcloud
|
||||||
pkgs.khscodes.terraform-provider-openstack
|
pkgs.khscodes.terraform-provider-openstack
|
||||||
pkgs.khscodes.terraform-provider-vault
|
pkgs.khscodes.terraform-provider-vault
|
||||||
|
pkgs.khscodes.terraform-provider-random
|
||||||
])
|
])
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
}:
|
}:
|
||||||
pkgs.writeShellApplication {
|
pkgs.writeShellApplication {
|
||||||
name = "pre-provisioning";
|
name = "post-provisioning";
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
pkgs.nix
|
pkgs.nix
|
||||||
pkgs.khscodes.bw-opentofu
|
pkgs.khscodes.bw-opentofu
|
||||||
|
@ -19,7 +19,7 @@ pkgs.writeShellApplication {
|
||||||
secretsSource="$(nix eval --raw "''${baseAttr}.post.secretsSource")"
|
secretsSource="$(nix eval --raw "''${baseAttr}.post.secretsSource")"
|
||||||
endpoints="$(nix eval --show-trace --json "''${baseAttr}.postEndpoints")"
|
endpoints="$(nix eval --show-trace --json "''${baseAttr}.postEndpoints")"
|
||||||
if [[ "$config" == "null" ]]; then
|
if [[ "$config" == "null" ]]; then
|
||||||
echo "No preprovisioning needed"
|
echo "No postprovisioning needed"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
if [[ "$secretsSource" == "vault" ]]; then
|
if [[ "$secretsSource" == "vault" ]]; then
|
||||||
|
|
10
nix/packages/terraform-provider-random/default.nix
Normal file
10
nix/packages/terraform-provider-random/default.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{ pkgs }:
|
||||||
|
pkgs.terraform-providers.mkProvider {
|
||||||
|
hash = "sha256-tdTVqSJmQ6Ht3kdoYMxhoRN+XJqvu8BPVB0VQghcDVs=";
|
||||||
|
homepage = "https://registry.terraform.io/providers/hashicorp/random";
|
||||||
|
owner = "hashicorp";
|
||||||
|
repo = "terraform-provider-random";
|
||||||
|
rev = "v3.7.2";
|
||||||
|
spdx = "MPL-2.0";
|
||||||
|
vendorHash = "sha256-jyfzk3vbgZwHlyiFFw1mhD+us/7WNatUQTGN4WsrfgE=";
|
||||||
|
}
|
|
@ -1,7 +1,12 @@
|
||||||
{ pkgs, config, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
imports = [ ./nix-base.nix ];
|
imports = [ ./nix-base.nix ];
|
||||||
snowfallorg.users.khs.admin = true;
|
snowfallorg.users.khs.admin = lib.mkDefault true;
|
||||||
users.users.khs = {
|
users.users.khs = {
|
||||||
# TODO: Figure out how to provision password changes to servers from VAULT
|
# TODO: Figure out how to provision password changes to servers from VAULT
|
||||||
initialPassword = "changeme";
|
initialPassword = "changeme";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [ ./nix-base.nix ];
|
imports = [ ./khs-base.nix ];
|
||||||
config = {
|
config = {
|
||||||
khscodes = {
|
khscodes = {
|
||||||
services.openssh.enable = true;
|
services.openssh.enable = true;
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
mapRdns = true;
|
mapRdns = true;
|
||||||
server_type = "cax11";
|
server_type = "cax11";
|
||||||
};
|
};
|
||||||
khscodes.networking.fqdn = "khs.codes";
|
khscodes.networking.fqdn = "kas.codes";
|
||||||
system.stateVersion = "25.05";
|
system.stateVersion = "25.05";
|
||||||
}
|
}
|
|
@ -1,7 +1,25 @@
|
||||||
# After creating the instance
|
# After creating the instance
|
||||||
|
|
||||||
Open https://vault.kaareskovgaard.net and initialize OpenBAO. Remember to get some sort of auto unsealing set up afterwards, currently this is implemented with a cronjob on TrueNAS. Doing it this way allows various certificates to continue getting issued, even as OpenBAO gets sealed (due to auto updates).
|
Open https://secrets.kaareskovgaard.net and initialize OpenBAO. Remember to get some sort of auto unsealing set up afterwards, currently this is implemented with a cronjob on TrueNAS. Doing it this way allows various certificates to continue getting issued, even as OpenBAO gets sealed (due to auto updates).
|
||||||
|
|
||||||
After this, run the post provisioning script to initialize the various OpenBAO parts needed.
|
After this, configure the OpenBAO instance with:
|
||||||
In order for `security.kaareskovgaard.net` to authenticate itself with OpenBAO
|
|
||||||
Then `nix run '.#bitwarden-to-vault` can transfer the needed Bitwarden secrets to vault.
|
```bash
|
||||||
|
nix run '.#configure-instance` -- security.kaareskovgaard.net
|
||||||
|
```
|
||||||
|
|
||||||
|
In order for `security.kaareskovgaard.net` to authenticate itself with OpenBAO, the printed credentials needs to be passed to the server with (on the server):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bao-import-secret <role-id> <wrapped-secret-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
While still SSH'ed into the server, reset the user (khs) password for kanidm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kanidm-reset-password <user>
|
||||||
|
```
|
||||||
|
|
||||||
|
Open https://login.kaareskovgaard.net - and log into the account, setting up the Yubikey (Passkey) auth, as well as Bitwarden based TOTP/password auth.
|
||||||
|
|
||||||
|
Then `nix run '.#bitwarden-to-vault` can transfer the needed Bitwarden secrets to vault, enabling other instances to not rely on Bitwarden.
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
{ config, ... }:
|
|
||||||
let
|
|
||||||
secretsFile = "/var/lib/authentik/authentik-env";
|
|
||||||
domain = "auth-test.kaareskovgaard.net";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
khscodes.nix.nix-community.enable = true;
|
|
||||||
services.authentik = {
|
|
||||||
enable = true;
|
|
||||||
environmentFile = secretsFile;
|
|
||||||
settings = {
|
|
||||||
email = {
|
|
||||||
host = "smtp.soverin.net";
|
|
||||||
port = 587;
|
|
||||||
username = "kaare@kaareskovgaard.net";
|
|
||||||
use_tls = true;
|
|
||||||
use_ssl = false;
|
|
||||||
from = "kaare@kaareskovgaard.net";
|
|
||||||
};
|
|
||||||
disable_startup_analytics = true;
|
|
||||||
avatars = "initials";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
khscodes.services.nginx.virtualHosts.${domain} = {
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "https://localhost:9443";
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
services.postgresqlBackup = {
|
|
||||||
enable = true;
|
|
||||||
databases = [ "authentik" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services = {
|
|
||||||
authentik-migrate = {
|
|
||||||
unitConfig = {
|
|
||||||
ConditionPathExists = secretsFile;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
authentik-worker = {
|
|
||||||
unitConfig = {
|
|
||||||
ConditionPathExists = secretsFile;
|
|
||||||
};
|
|
||||||
serviceConfig = {
|
|
||||||
LoadCredential = [
|
|
||||||
"${domain}.pem:${config.security.acme.certs.${domain}.directory}/fullchain.pem"
|
|
||||||
"${domain}.key:${config.security.acme.certs.${domain}.directory}/key.pem"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
authentik = {
|
|
||||||
unitConfig = {
|
|
||||||
ConditionPathExists = secretsFile;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,14 +1,16 @@
|
||||||
{
|
{
|
||||||
inputs,
|
inputs,
|
||||||
|
pkgs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
|
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
|
||||||
./authentik.nix
|
./kanidm.nix
|
||||||
./openbao.nix
|
./openbao.nix
|
||||||
./post/openbao
|
./post
|
||||||
];
|
];
|
||||||
|
environment.systemPackages = [ pkgs.khscodes.bao-import-secret ];
|
||||||
khscodes.services.nginx.enable = true;
|
khscodes.services.nginx.enable = true;
|
||||||
khscodes.infrastructure.hetzner-instance = {
|
khscodes.infrastructure.hetzner-instance = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -25,6 +27,6 @@
|
||||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqY0FHnWFKfLG2yfgr4qka5sR9CK+EMAhzlHUkaQyWHTKD+G0/vC/fNPyL1VV3Dxc/ajxGuPzVE+mBMoyxazL3EtuCDOVvHJ5CR+MUSEckg/DDwcGHqy6rC8BvVVpTAVL04ByQdwFnpE1qNSBaQLkxaFVdtriGKkgMkc7+UNeYX/bv7yn+APqfP1a3xr6wdkSSdO8x4N2jsSygOIMx10hLyCV4Ueu7Kp8Ww4rGY8j5o7lKJhbgfItBfSOuQHdppHVF/GKYRhdnK6Y2fZVYbhq4KipUtclbZ6O/VYd8/sOO98+LMm7cOX+K35PQjUpYgcoNy5+Sw3CNS/NHn4JvOtTaUEYP7fK6c9LhMULOO3T7Cm6TMdiFjUKHkyG+s2Mu/LXJJoilw571zwuh6chkeitW8+Ht7k0aPV96kNEvTdoXwLhBifVEaChlAsLAzSUjUq+YYCiXVk0VIXCZQWKj8LoVNTmaqDksWwbcT64fw/FpVC0N18WHbKcFUEIW/O4spJMa30CQwf9FeqpoWoaF1oRClCSDPvX0AauCu0JcmRinz1/JmlXljnXWbSfm20/V+WyvktlI0wTD0cdpNuSasT9vS77YfJ8nutcWWZKSkCj4R4uHeCNpDTX5YXzapy7FxpM9ANCXLIvoGX7Yafba2Po+er7SSsUIY1AsnBBr8ZoDVw=="
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqY0FHnWFKfLG2yfgr4qka5sR9CK+EMAhzlHUkaQyWHTKD+G0/vC/fNPyL1VV3Dxc/ajxGuPzVE+mBMoyxazL3EtuCDOVvHJ5CR+MUSEckg/DDwcGHqy6rC8BvVVpTAVL04ByQdwFnpE1qNSBaQLkxaFVdtriGKkgMkc7+UNeYX/bv7yn+APqfP1a3xr6wdkSSdO8x4N2jsSygOIMx10hLyCV4Ueu7Kp8Ww4rGY8j5o7lKJhbgfItBfSOuQHdppHVF/GKYRhdnK6Y2fZVYbhq4KipUtclbZ6O/VYd8/sOO98+LMm7cOX+K35PQjUpYgcoNy5+Sw3CNS/NHn4JvOtTaUEYP7fK6c9LhMULOO3T7Cm6TMdiFjUKHkyG+s2Mu/LXJJoilw571zwuh6chkeitW8+Ht7k0aPV96kNEvTdoXwLhBifVEaChlAsLAzSUjUq+YYCiXVk0VIXCZQWKj8LoVNTmaqDksWwbcT64fw/FpVC0N18WHbKcFUEIW/O4spJMa30CQwf9FeqpoWoaF1oRClCSDPvX0AauCu0JcmRinz1/JmlXljnXWbSfm20/V+WyvktlI0wTD0cdpNuSasT9vS77YfJ8nutcWWZKSkCj4R4uHeCNpDTX5YXzapy7FxpM9ANCXLIvoGX7Yafba2Po+er7SSsUIY1AsnBBr8ZoDVw=="
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
khscodes.infrastructure.openbao.domain = "vault-test.kaareskovgaard.net";
|
khscodes.infrastructure.openbao.domain = "secrets.kaareskovgaard.net";
|
||||||
system.stateVersion = "25.05";
|
system.stateVersion = "25.05";
|
||||||
}
|
}
|
||||||
|
|
314
nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix
Normal file
314
nix/systems/aarch64-linux/security.kaareskovgaard.net/kanidm.nix
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
let
|
||||||
|
domain = "login.kaareskovgaard.net";
|
||||||
|
openbaoAppBasicSecretFile = "/var/lib/vault-agent/kanidm/openbao_basic_secret";
|
||||||
|
openbaoDomain = config.khscodes.infrastructure.openbao.domain;
|
||||||
|
openbaoAllowedRedirectUrls = [
|
||||||
|
"https://${openbaoDomain}/ui/vault/auth/oidc/oidc/callback"
|
||||||
|
"https://${openbaoDomain}/oidc/callback"
|
||||||
|
"http://localhost:8250/oidc/callback"
|
||||||
|
];
|
||||||
|
kanidm-reset-password = pkgs.writeShellApplication {
|
||||||
|
name = "kanidm-reset-password";
|
||||||
|
runtimeInputs = [
|
||||||
|
config.services.kanidm.package
|
||||||
|
pkgs.jq
|
||||||
|
pkgs.gnugrep
|
||||||
|
pkgs.expect
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
username="''${1:-}"
|
||||||
|
if [[ "$username" == "" ]]; then
|
||||||
|
>&2 echo "Usage kanidm-reset-password <username>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Resetting password for idm_admin, please provide sudo password"
|
||||||
|
password="$(sudo -u kanidm kanidmd recover-account -c /etc/kanidm/server.toml -o json idm_admin 2> /dev/null | grep '^{' | jq --raw-output '.password')"
|
||||||
|
expect <<EOD
|
||||||
|
spawn kanidm login --url https://login.kaareskovgaard.net --name idm_admin
|
||||||
|
expect "Enter password"
|
||||||
|
send "$password\n"
|
||||||
|
expect eof
|
||||||
|
EOD
|
||||||
|
kanidm person credential create-reset-token --url https://login.kaareskovgaard.net "$username"
|
||||||
|
trap "kanidm logout --url https://login.kaareskovgaard.net" EXIT
|
||||||
|
'';
|
||||||
|
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.kanidm = {
|
||||||
|
enableServer = true;
|
||||||
|
# Verify upgrade will be successful by running `kanidmd domain upgrade-check` before bumping package version.
|
||||||
|
package = pkgs.kanidmWithSecretProvisioning_1_6;
|
||||||
|
serverSettings = {
|
||||||
|
inherit domain;
|
||||||
|
tls_chain = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
|
||||||
|
tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem";
|
||||||
|
origin = "https://${domain}";
|
||||||
|
trust_x_forward_for = true;
|
||||||
|
};
|
||||||
|
provision = {
|
||||||
|
enable = true;
|
||||||
|
idmAdminPasswordFile = null;
|
||||||
|
adminPasswordFile = null;
|
||||||
|
acceptInvalidCerts = true;
|
||||||
|
persons.khs = {
|
||||||
|
present = true;
|
||||||
|
mailAddresses = [ "kaare@kaareskovgaard.net" ];
|
||||||
|
legalName = "Kaare Skovgaard";
|
||||||
|
displayName = "khs";
|
||||||
|
};
|
||||||
|
groups.openbao_admin = {
|
||||||
|
present = true;
|
||||||
|
members = [ "khs" ];
|
||||||
|
};
|
||||||
|
systems.oauth2.openbao = {
|
||||||
|
present = true;
|
||||||
|
public = false;
|
||||||
|
preferShortUsername = true;
|
||||||
|
basicSecretFile = openbaoAppBasicSecretFile;
|
||||||
|
originUrl = openbaoAllowedRedirectUrls;
|
||||||
|
originLanding = "https://${openbaoDomain}";
|
||||||
|
displayName = "OpenBAO";
|
||||||
|
scopeMaps = {
|
||||||
|
"openbao_admin" = [
|
||||||
|
"profile"
|
||||||
|
"email"
|
||||||
|
"openid"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
claimMaps.groups = {
|
||||||
|
joinType = "array";
|
||||||
|
valuesByGroup = {
|
||||||
|
"openbao_admin" = [
|
||||||
|
"openbao_reader"
|
||||||
|
"openbao_writer"
|
||||||
|
"openbao_sudo"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
systemd.services.kanidm = {
|
||||||
|
unitConfig = {
|
||||||
|
ConditionPathExists = [
|
||||||
|
openbaoAppBasicSecretFile
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Allow the server to read the secrets for its own apps
|
||||||
|
khscodes.infrastructure.vault-server-approle.policy."kanidm/data/apps/*" = {
|
||||||
|
capabilities = [ "read" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
khscodes.infrastructure.provisioning.post.modules = [
|
||||||
|
# Creates mount for kanidm apps and generates random passwords for the apps
|
||||||
|
(
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
terraform.required_providers.random = {
|
||||||
|
source = "hashicorp/random";
|
||||||
|
version = "3.7.2";
|
||||||
|
};
|
||||||
|
provider.random = { };
|
||||||
|
# NOTE: This gets stored in the state file
|
||||||
|
resource.random_password.openbao_secret = {
|
||||||
|
length = 48;
|
||||||
|
numeric = true;
|
||||||
|
lower = true;
|
||||||
|
upper = true;
|
||||||
|
special = false;
|
||||||
|
};
|
||||||
|
khscodes.vault = {
|
||||||
|
enable = true;
|
||||||
|
mount.kanidm = {
|
||||||
|
path = "kanidm";
|
||||||
|
type = "kv";
|
||||||
|
options = {
|
||||||
|
version = "2";
|
||||||
|
};
|
||||||
|
description = "Secrets used for kanidm";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
resource.vault_kv_secret_v2.openbao_secret = {
|
||||||
|
mount = config.khscodes.vault.output.mount.kanidm.path;
|
||||||
|
name = "apps/openbao";
|
||||||
|
data_json = ''
|
||||||
|
{ "basic_secret": "''${ resource.random_password.openbao_secret.result }" }
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Sets up OIDC authentication within OpenBAO
|
||||||
|
{
|
||||||
|
resource.vault_jwt_auth_backend.oidc = {
|
||||||
|
description = "Kanidm auth backend";
|
||||||
|
path = "oidc";
|
||||||
|
type = "oidc";
|
||||||
|
oidc_discovery_url = "https://${domain}/oauth2/openid/openbao";
|
||||||
|
oidc_client_id = "openbao";
|
||||||
|
oidc_client_secret = "\${ resource.random_password.openbao_secret.result }";
|
||||||
|
jwt_supported_algs = [
|
||||||
|
"ES256"
|
||||||
|
];
|
||||||
|
tune = [
|
||||||
|
{
|
||||||
|
listing_visibility = "unauth";
|
||||||
|
default_lease_ttl = "2h";
|
||||||
|
max_lease_ttl = "2h";
|
||||||
|
token_type = "default-service";
|
||||||
|
passthrough_request_headers = [ ];
|
||||||
|
allowed_response_headers = [ ];
|
||||||
|
audit_non_hmac_request_keys = [ ];
|
||||||
|
audit_non_hmac_response_keys = [ ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_jwt_auth_backend_role.oidc_reader = {
|
||||||
|
backend = "\${ resource.vault_jwt_auth_backend.oidc.path }";
|
||||||
|
role_name = "reader";
|
||||||
|
bound_audiences = [ "openbao" ];
|
||||||
|
allowed_redirect_uris = openbaoAllowedRedirectUrls;
|
||||||
|
user_claim = "sub";
|
||||||
|
token_policies = [ "reader" ];
|
||||||
|
groups_claim = "groups";
|
||||||
|
oidc_scopes = [
|
||||||
|
"openid"
|
||||||
|
"profile"
|
||||||
|
"email"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_jwt_auth_backend_role.oidc_writer = {
|
||||||
|
backend = "\${ resource.vault_jwt_auth_backend.oidc.path }";
|
||||||
|
role_name = "writer";
|
||||||
|
bound_audiences = [ "openbao" ];
|
||||||
|
allowed_redirect_uris = openbaoAllowedRedirectUrls;
|
||||||
|
user_claim = "sub";
|
||||||
|
token_policies = [ "writer" ];
|
||||||
|
groups_claim = "groups";
|
||||||
|
oidc_scopes = [
|
||||||
|
"openid"
|
||||||
|
"profile"
|
||||||
|
"email"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_jwt_auth_backend_role.oidc_sudo = {
|
||||||
|
backend = "\${ resource.vault_jwt_auth_backend.oidc.path }";
|
||||||
|
role_name = "sudo";
|
||||||
|
bound_audiences = [ "openbao" ];
|
||||||
|
allowed_redirect_uris = openbaoAllowedRedirectUrls;
|
||||||
|
user_claim = "sub";
|
||||||
|
token_policies = [ "sudo" ];
|
||||||
|
groups_claim = "groups";
|
||||||
|
oidc_scopes = [
|
||||||
|
"openid"
|
||||||
|
"profile"
|
||||||
|
"email"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_identity_group.reader = {
|
||||||
|
name = "reader";
|
||||||
|
policies = [ "reader" ];
|
||||||
|
type = "external";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_identity_group.writer = {
|
||||||
|
name = "writer";
|
||||||
|
policies = [ "writer" ];
|
||||||
|
type = "external";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_identity_group.sudo = {
|
||||||
|
name = "sudo";
|
||||||
|
policies = [ "sudo" ];
|
||||||
|
type = "external";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_identity_group_alias.reader = {
|
||||||
|
name = "openbao_reader";
|
||||||
|
mount_accessor = "\${ vault_jwt_auth_backend.oidc.accessor }";
|
||||||
|
canonical_id = "\${ vault_identity_group.reader.id }";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_identity_group_alias.writer = {
|
||||||
|
name = "openbao_writer";
|
||||||
|
mount_accessor = "\${ vault_jwt_auth_backend.oidc.accessor }";
|
||||||
|
canonical_id = "\${ vault_identity_group.writer.id }";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_identity_group_alias.sudo = {
|
||||||
|
name = "openbao_sudo";
|
||||||
|
mount_accessor = "\${ vault_jwt_auth_backend.oidc.accessor }";
|
||||||
|
canonical_id = "\${ vault_identity_group.writer.id }";
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_policy.reader = {
|
||||||
|
name = "reader";
|
||||||
|
|
||||||
|
policy = ''
|
||||||
|
path "*" {
|
||||||
|
capabilities = ["read", "list"]
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_policy.writer = {
|
||||||
|
name = "writer";
|
||||||
|
|
||||||
|
policy = ''
|
||||||
|
path "*" {
|
||||||
|
capabilities = ["create", "update", "patch", "read", "delete", "list"]
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
resource.vault_policy.sudo = {
|
||||||
|
name = "sudo";
|
||||||
|
|
||||||
|
policy = ''
|
||||||
|
path "auth/token/create" {
|
||||||
|
capabilities = ["create", "update", "sudo"]
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
khscodes.services.vault-agent.templates = [
|
||||||
|
{
|
||||||
|
contents = ''
|
||||||
|
{{- with secret "kanidm/data/apps/openbao" -}}
|
||||||
|
{{ .Data.data.basic_secret }}
|
||||||
|
{{- end -}}
|
||||||
|
'';
|
||||||
|
destination = openbaoAppBasicSecretFile;
|
||||||
|
perms = "0600";
|
||||||
|
owner = "kanidm";
|
||||||
|
group = "kanidm";
|
||||||
|
reloadOrRestartUnits = [ "kanidm.service" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
security.acme.certs.${domain}.reloadServices = [ "kanidm.service" ];
|
||||||
|
# Allow kanidm to read the certificate file
|
||||||
|
users.groups.nginx.members = [ "kanidm" ];
|
||||||
|
|
||||||
|
khscodes.services.nginx.virtualHosts.${domain} = {
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "https://${config.services.kanidm.serverSettings.bindaddress}/";
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
kanidm-reset-password
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
imports = [ ./openbao ];
|
||||||
|
khscodes.infrastructure.provisioning.post.modules = [
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.self.terranixModules.s3
|
||||||
|
];
|
||||||
|
khscodes.s3 = {
|
||||||
|
enable = true;
|
||||||
|
bucket = {
|
||||||
|
key = "security.kaareskovgaard.net-configuration.tfstate";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
|
@ -51,7 +51,10 @@ fn configure() -> anyhow::Result<()> {
|
||||||
if ipv4.is_some_and(|v| v) {
|
if ipv4.is_some_and(|v| v) {
|
||||||
cmd.arg("-4");
|
cmd.arg("-4");
|
||||||
}
|
}
|
||||||
cmd.args(["addr", "add", &address, "dev", &m.name]);
|
// Apparently the configuration used renames the eth0 device to enp1s0, I should really figure out why this happens, but for now
|
||||||
|
// this will do.
|
||||||
|
let dev_name = if m.name == "eth0" { "enp1s0" } else { &m.name };
|
||||||
|
cmd.args(["addr", "add", &address, "dev", dev_name]);
|
||||||
cmd.try_spawn_to_string()?;
|
cmd.try_spawn_to_string()?;
|
||||||
let mut cmd = common::proc::Command::new("ip");
|
let mut cmd = common::proc::Command::new("ip");
|
||||||
if ipv6.is_some_and(|v| v) {
|
if ipv6.is_some_and(|v| v) {
|
||||||
|
@ -60,7 +63,7 @@ fn configure() -> anyhow::Result<()> {
|
||||||
if ipv4.is_some_and(|v| v) {
|
if ipv4.is_some_and(|v| v) {
|
||||||
cmd.arg("-4");
|
cmd.arg("-4");
|
||||||
}
|
}
|
||||||
cmd.args(["route", "add", "default", "via", &gateway, "dev", &m.name]);
|
cmd.args(["route", "add", "default", "via", &gateway, "dev", dev_name]);
|
||||||
cmd.try_spawn_to_string()?;
|
cmd.try_spawn_to_string()?;
|
||||||
}
|
}
|
||||||
metadata::InstanceNetworkConfigConfigSubnet::Dhcp {} => continue,
|
metadata::InstanceNetworkConfigConfigSubnet::Dhcp {} => continue,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue