470 lines
15 KiB
Nix
470 lines
15 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}:
|
|
let
|
|
domain = "login.kaareskovgaard.net";
|
|
bootstrapping = config.khscodes."security.kaareskovgaard.net".bootstrap.enable;
|
|
openbaoAppBasicSecretFile = "/run/kanidm/openbao_basic_secret";
|
|
openbaoCliAppBasicSecretFile = "/run/kanidm/openbao_cli_basic_secret";
|
|
monitoringAppBasicSecretFile = "/run/kanidm/monitoring_basic_secret";
|
|
forgejoAppBasicSecretFile = "/run/kanidm/forgejo_basic_secret";
|
|
secretFiles = [
|
|
openbaoAppBasicSecretFile
|
|
openbaoCliAppBasicSecretFile
|
|
monitoringAppBasicSecretFile
|
|
forgejoAppBasicSecretFile
|
|
];
|
|
openbaoDomain = config.khscodes.infrastructure.openbao.domain;
|
|
openbaoAllowedRedirectUrls = [
|
|
"https://${openbaoDomain}/ui/vault/auth/kanidm/oidc/callback"
|
|
"https://${openbaoDomain}/kanidm/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" ];
|
|
};
|
|
groups.forgejo_user = {
|
|
present = true;
|
|
members = [ "khs" ];
|
|
};
|
|
groups.forgejo_comitter = {
|
|
present = true;
|
|
members = [ "khs" ];
|
|
};
|
|
groups.forgejo_admin = {
|
|
present = true;
|
|
members = [ "khs" ];
|
|
};
|
|
# We cannot add oauth2 apps before the secrets for them are generated.
|
|
systems.oauth2 = lib.mkIf (!bootstrapping) {
|
|
openbao = {
|
|
present = true;
|
|
public = false;
|
|
preferShortUsername = true;
|
|
basicSecretFile = lib.mkIf (!bootstrapping) openbaoAppBasicSecretFile;
|
|
originUrl = openbaoAllowedRedirectUrls;
|
|
originLanding = "https://${openbaoDomain}";
|
|
displayName = "OpenBAO";
|
|
scopeMaps = {
|
|
"openbao_admin" = [
|
|
"profile"
|
|
"email"
|
|
"openid"
|
|
];
|
|
};
|
|
claimMaps.groups = {
|
|
joinType = "array";
|
|
valuesByGroup = {
|
|
"openbao_admin" = [
|
|
"openbao_writer"
|
|
"openbao_cli_writer"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
openbao-cli = {
|
|
present = true;
|
|
public = false;
|
|
preferShortUsername = true;
|
|
basicSecretFile = lib.mkIf (!bootstrapping) openbaoCliAppBasicSecretFile;
|
|
originUrl = [ "http://localhost:8250/oidc/callback" ];
|
|
originLanding = "http://localhost:8250";
|
|
displayName = "OpenBAO CLI";
|
|
scopeMaps = {
|
|
"openbao_admin" = [
|
|
"profile"
|
|
"email"
|
|
"openid"
|
|
];
|
|
};
|
|
claimMaps.groups = {
|
|
joinType = "array";
|
|
valuesByGroup = {
|
|
"openbao_admin" = [
|
|
"openbao_writer"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
monitoring = {
|
|
present = true;
|
|
public = false;
|
|
preferShortUsername = true;
|
|
basicSecretFile = lib.mkIf (!bootstrapping) monitoringAppBasicSecretFile;
|
|
originUrl = [ "https://monitoring.kaareskovgaard.net/login/generic_oauth" ];
|
|
originLanding = "http://monitoring.kaareskovgaard.net";
|
|
displayName = "Monitoring";
|
|
scopeMaps = {
|
|
"openbao_admin" = [
|
|
"profile"
|
|
"email"
|
|
"openid"
|
|
];
|
|
};
|
|
};
|
|
forgejo = {
|
|
present = true;
|
|
public = false;
|
|
preferShortUsername = true;
|
|
basicSecretFile = lib.mkIf (!bootstrapping) forgejoAppBasicSecretFile;
|
|
originUrl = [ "https://kas.codes/user/oauth2/Kanidm/callback" ];
|
|
originLanding = "http://kas.codes";
|
|
displayName = "KAS: Codes";
|
|
scopeMaps = {
|
|
"forgejo_user" = [
|
|
"profile"
|
|
"email"
|
|
"openid"
|
|
];
|
|
};
|
|
claimMaps.groups = {
|
|
joinType = "array";
|
|
valuesByGroup = {
|
|
"forgejo_comitter" = [
|
|
"comitter"
|
|
];
|
|
"forgejo_admin" = [
|
|
"admin"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
# Don't add dependencies from bootstrapping when not bootstrapping.
|
|
systemd.services.kanidm = lib.mkIf (!bootstrapping) {
|
|
unitConfig = {
|
|
ConditionPathExists = secretFiles;
|
|
};
|
|
};
|
|
|
|
# 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 }" }
|
|
'';
|
|
};
|
|
resource.random_password.openbao_cli_secret = {
|
|
length = 48;
|
|
numeric = true;
|
|
lower = true;
|
|
upper = true;
|
|
special = false;
|
|
};
|
|
resource.vault_kv_secret_v2.openbao_cli_secret = {
|
|
mount = config.khscodes.vault.output.mount.kanidm.path;
|
|
name = "apps/openbao_cli";
|
|
data_json = ''
|
|
{ "basic_secret": "''${ resource.random_password.openbao_cli_secret.result }" }
|
|
'';
|
|
};
|
|
resource.random_password.monitoring = {
|
|
length = 48;
|
|
numeric = true;
|
|
lower = true;
|
|
upper = true;
|
|
special = false;
|
|
};
|
|
resource.vault_kv_secret_v2.monitoring_secret = {
|
|
mount = config.khscodes.vault.output.mount.kanidm.path;
|
|
name = "apps/monitoring";
|
|
data_json = ''
|
|
{ "basic_secret": "''${ resource.random_password.monitoring.result }" }
|
|
'';
|
|
};
|
|
resource.random_password.forgejo = {
|
|
length = 48;
|
|
numeric = true;
|
|
lower = true;
|
|
upper = true;
|
|
special = false;
|
|
};
|
|
resource.vault_kv_secret_v2.forgejo_secret = {
|
|
mount = config.khscodes.vault.output.mount.kanidm.path;
|
|
name = "apps/forgejo";
|
|
data_json = ''
|
|
{ "basic_secret": "''${ resource.random_password.forgejo.result }" }
|
|
'';
|
|
};
|
|
}
|
|
)
|
|
# Sets up OIDC authentication within OpenBAO.
|
|
# OpenBAO queries the openid url for its configuration when adding it, so it is not possible,
|
|
# to add this before
|
|
(
|
|
if bootstrapping then
|
|
{ }
|
|
else
|
|
{
|
|
resource.vault_jwt_auth_backend.kanidm_cli = {
|
|
description = "Kanidm cli auth backend";
|
|
path = "oidc";
|
|
type = "oidc";
|
|
oidc_discovery_url = "https://${domain}/oauth2/openid/openbao-cli";
|
|
oidc_client_id = "openbao-cli";
|
|
oidc_client_secret = "\${ resource.random_password.openbao_cli_secret.result }";
|
|
default_role = "kanidm_cli_writer";
|
|
jwt_supported_algs = [
|
|
"ES256"
|
|
];
|
|
tune = [
|
|
{
|
|
listing_visibility = "hidden";
|
|
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.kanidm = {
|
|
description = "Kanidm auth backend";
|
|
path = "kanidm";
|
|
type = "oidc";
|
|
oidc_discovery_url = "https://${domain}/oauth2/openid/openbao";
|
|
oidc_client_id = "openbao";
|
|
oidc_client_secret = "\${ resource.random_password.openbao_secret.result }";
|
|
default_role = "kanidm_writer";
|
|
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.kanidm_cli_writer = {
|
|
backend = "\${ resource.vault_jwt_auth_backend.kanidm_cli.path }";
|
|
role_name = "kanidm_cli_writer";
|
|
bound_audiences = [ "openbao-cli" ];
|
|
allowed_redirect_uris = [ "http://localhost:8250/oidc/callback" ];
|
|
user_claim = "sub";
|
|
token_policies = [ "writer" ];
|
|
groups_claim = "groups";
|
|
oidc_scopes = [
|
|
"openid"
|
|
"profile"
|
|
"email"
|
|
];
|
|
};
|
|
|
|
resource.vault_jwt_auth_backend_role.kanidm_writer = {
|
|
backend = "\${ resource.vault_jwt_auth_backend.kanidm.path }";
|
|
role_name = "kanidm_writer";
|
|
bound_audiences = [ "openbao" ];
|
|
allowed_redirect_uris = openbaoAllowedRedirectUrls;
|
|
user_claim = "sub";
|
|
token_policies = [ "writer" ];
|
|
groups_claim = "groups";
|
|
oidc_scopes = [
|
|
"openid"
|
|
"profile"
|
|
"email"
|
|
];
|
|
};
|
|
|
|
resource.vault_identity_group.kanidm_writer = {
|
|
name = "kanidm_writer";
|
|
policies = [ "writer" ];
|
|
type = "external";
|
|
};
|
|
|
|
resource.vault_identity_group.kanidm_cli_writer = {
|
|
name = "kanidm_cli_writer";
|
|
policies = [ "writer" ];
|
|
type = "external";
|
|
};
|
|
|
|
resource.vault_identity_group_alias.oidc_writer = {
|
|
name = "openbao_cli_writer";
|
|
mount_accessor = "\${ vault_jwt_auth_backend.kanidm_cli.accessor }";
|
|
canonical_id = "\${ vault_identity_group.kanidm_cli_writer.id }";
|
|
};
|
|
|
|
resource.vault_identity_group_alias.kanidm_writer = {
|
|
name = "openbao_writer";
|
|
mount_accessor = "\${ vault_jwt_auth_backend.kanidm.accessor }";
|
|
canonical_id = "\${ vault_identity_group.kanidm_writer.id }";
|
|
};
|
|
|
|
resource.vault_policy.writer = {
|
|
name = "writer";
|
|
|
|
policy = ''
|
|
path "*" {
|
|
capabilities = ["create", "update", "patch", "read", "delete", "list"]
|
|
}
|
|
'';
|
|
};
|
|
}
|
|
)
|
|
];
|
|
|
|
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" ];
|
|
}
|
|
{
|
|
contents = ''
|
|
{{- with secret "kanidm/data/apps/openbao_cli" -}}
|
|
{{ .Data.data.basic_secret }}
|
|
{{- end -}}
|
|
'';
|
|
destination = openbaoCliAppBasicSecretFile;
|
|
perms = "0600";
|
|
owner = "kanidm";
|
|
group = "kanidm";
|
|
reloadOrRestartUnits = [ "kanidm.service" ];
|
|
}
|
|
{
|
|
contents = ''
|
|
{{- with secret "kanidm/data/apps/monitoring" -}}
|
|
{{ .Data.data.basic_secret }}
|
|
{{- end -}}
|
|
'';
|
|
destination = monitoringAppBasicSecretFile;
|
|
perms = "0600";
|
|
owner = "kanidm";
|
|
group = "kanidm";
|
|
reloadOrRestartUnits = [ "kanidm.service" ];
|
|
}
|
|
{
|
|
contents = ''
|
|
{{- with secret "kanidm/data/apps/forgejo" -}}
|
|
{{ .Data.data.basic_secret }}
|
|
{{- end -}}
|
|
'';
|
|
destination = forgejoAppBasicSecretFile;
|
|
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
|
|
];
|
|
}
|