Begin preparing kas.codes domain
Some checks are pending
/ check (push) Waiting to run
/ dev-shell (push) Waiting to run
/ rust-packages (push) Waiting to run
/ terraform-providers (push) Waiting to run
/ systems (push) Waiting to run

This commit is contained in:
Kaare Hoff Skovgaard 2025-07-18 22:58:35 +02:00
parent eff84d05da
commit dec0048a7b
Signed by: khs
GPG key ID: C7D890804F01E9F0
20 changed files with 721 additions and 52 deletions

122
flake.lock generated
View file

@ -83,6 +83,22 @@
"type": "github"
}
},
"blobs": {
"flake": false,
"locked": {
"lastModified": 1604995301,
"narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"type": "gitlab"
}
},
"cosmic-manager": {
"inputs": {
"flake-parts": [
@ -200,6 +216,22 @@
"type": "github"
}
},
"flake-compat_2": {
"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-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
@ -291,6 +323,54 @@
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"simple-nixos-mailserver",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"simple-nixos-mailserver",
"nixpkgs"
]
},
"locked": {
"lastModified": 1742649964,
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"simple-nixos-mailserver",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gnome-shell": {
"flake": false,
"locked": {
@ -440,6 +520,22 @@
"type": "github"
}
},
"nixpkgs-25_05": {
"locked": {
"lastModified": 1747610100,
"narHash": "sha256-rpR5ZPMkWzcnCcYYo3lScqfuzEw5Uyfh+R0EKZfroAc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ca49c4304acf0973078db0a9d200fd2bae75676d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1751159883,
@ -494,6 +590,7 @@
"nixos-anywhere": "nixos-anywhere",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"simple-nixos-mailserver": "simple-nixos-mailserver",
"stylix": "stylix",
"systems": "systems_2",
"terranix": "terranix",
@ -521,6 +618,31 @@
"type": "github"
}
},
"simple-nixos-mailserver": {
"inputs": {
"blobs": "blobs",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-25_05": "nixpkgs-25_05"
},
"locked": {
"lastModified": 1747965231,
"narHash": "sha256-BW3ktviEhfCN/z3+kEyzpDKAI8qFTwO7+S0NVA0C90o=",
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"rev": "53007af63fade28853408370c4c600a63dd97f41",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"ref": "nixos-25.05",
"repo": "nixos-mailserver",
"type": "gitlab"
}
},
"snowfall-lib": {
"inputs": {
"flake-compat": "flake-compat",

View file

@ -91,10 +91,16 @@
flake-parts.follows = "flake-parts";
};
};
simple-nixos-mailserver = {
url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-25.05";
inputs = {
nixpkgs.follows = "nixpkgs";
};
};
};
outputs =
inputs@{ self, ... }:
inputs:
let
inputNixosModules = [
inputs.disko.nixosModules.disko

View file

@ -77,6 +77,7 @@ in
description = "The server type to create";
default = null;
};
extraFirewallRules = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Extra firewall rules added to the instance";
@ -153,7 +154,7 @@ in
initial_image = "debian-12";
rdns = lib.mkIf cfg.mapRdns fqdn;
ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ];
user_data = provisioningUserData;
user_data = builtins.toJSON provisioningUserData;
};
khscodes.cloudflare = {
enable = true;
@ -199,8 +200,16 @@ in
message = "Must set config.khscodes.networking.fqdn when using opentofu";
}
];
khscodes.services.read-vault-auth-from-userdata.url = "http://169.254.169.254/latest/user-data";
khscodes.services.openssh = {
enable = true;
hostCertificate = {
enable = true;
};
};
khscodes.services.read-vault-auth-from-userdata = {
url = "http://169.254.169.254/latest/user-data";
doubleDecodeJsonData = true;
};
khscodes.infrastructure.provisioning.pre = {
modules = modules;
};

View file

@ -135,7 +135,7 @@ in
flavor = cfg.flavor;
ssh_public_key = cfg.ssh_key;
firewall_rules = firewallRules;
user_data = provisioningUserData;
user_data = builtins.toJSON provisioningUserData;
};
khscodes.unifi.enable = true;
khscodes.unifi.static_route.compute = {

View file

@ -30,6 +30,10 @@ let
search = "cloudflare/cloudflare";
endpoint = "cloudflare";
}
{
search = "hetznercloud/hcloud";
endpoint = "hcloud";
}
{
search = "terraform-provider-openstack/openstack";
endpoint = "openstack";
@ -79,7 +83,7 @@ in
pre = provisioning;
post = provisioning;
instanceUserData = lib.mkOption {
type = lib.types.str;
type = (pkgs.formats.json { }).type;
description = "User data that should be added to the instance during provisioning";
default = "";
};

View file

@ -66,7 +66,9 @@ in
owner = "alloy";
group = "alloy";
perms = "0600";
reloadOrRestartUnits = [ "alloy.service" ];
# Alloy doesn't seem to reload the certificates when using just reload
# so restart the unit.
restartUnits = [ "alloy.service" ];
}
];
khscodes.services.alloy = {

View file

@ -66,7 +66,7 @@ in
owner = "alloy";
group = "alloy";
perms = "0600";
reloadOrRestartUnits = [ "alloy.service" ];
restartUnits = [ "alloy.service" ];
}
];
khscodes.services.alloy = {

View file

@ -131,11 +131,9 @@ in
};
# I can only provide the user data if the stage is pre (along with the instance creation)
# Also I should probably find a way of injecting this in a nicer way than this mess.
khscodes.infrastructure.provisioning.instanceUserData = lib.mkIf (cfg.stage == "pre") ''
{
"VAULT_ROLE_ID": "''${ vault_approle_auth_backend_role.${lib.khscodes.sanitize-terraform-name cfg.role_name}.role_id }",
"VAULT_SECRET_ID_WRAPPED": "''${ vault_approle_auth_backend_role_secret_id.${lib.khscodes.sanitize-terraform-name cfg.role_name}.wrapping_token }"
}
'';
khscodes.infrastructure.provisioning.instanceUserData = lib.mkIf (cfg.stage == "pre") {
VAULT_ROLE_ID = "\${ vault_approle_auth_backend_role.${lib.khscodes.sanitize-terraform-name cfg.role_name}.role_id }";
VAULT_SECRET_ID_WRAPPED = "\${ vault_approle_auth_backend_role_secret_id.${lib.khscodes.sanitize-terraform-name cfg.role_name}.wrapping_token }";
};
};
}

View file

@ -14,7 +14,7 @@ let
pkgs.uutils-coreutils-noprefix
pkgs.nix
];
name = "nixos-prepare-upgrade";
name = "nixos-upgrade-prepare-flake";
text = ''
@ -28,7 +28,7 @@ let
echo -n ${inputs.self.outPath} > ${upgradeVersion}
fi
cd ${upgradePath}
NIX_CONFIG="extra-experimental-features=flake nix-command" nix flake update
nix --extra-experimental-features "nix-command flakes" flake update
'';
};
in

View file

@ -29,7 +29,7 @@ in
credentialsFile = vaultAgentCredentialsFile;
};
};
khscodes.infrastructure.vault-server-approle = {
khscodes.infrastructure.vault-server-approle = lib.mkIf cfg.dns01Enabled {
enable = true;
policy = {
"${cloudflareSecret}" = {

View file

@ -10,6 +10,7 @@ in
{
options.khscodes.services.read-vault-auth-from-userdata = {
enable = lib.mkEnableOption "Enables reading vault auth information from instance userdata";
doubleDecodeJsonData = lib.mkEnableOption "Enables double decoding the JSON data. Used by the hcloud provider as hetzner encodes the entire user data as a string";
url = lib.mkOption {
type = lib.types.str;
description = "URL to retrieve instance metadata from";
@ -29,6 +30,9 @@ in
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
environment = {
DOUBLE_DECODE = if cfg.doubleDecodeJsonData then "yes" else "no";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
@ -43,7 +47,11 @@ in
pkgs.systemd
];
text = ''
DOUBLE_DECODE="''${DOUBLE_DECODE:-no}"
userdata="$(curl ${lib.escapeShellArg cfg.url})"
if [[ "$DOUBLE_DECODE" == "yes" ]]; then
userdata="$(echo "$userdata" | jq --raw-output '.')"
fi
role_id="$(echo "$userdata" | jq --raw-output '.VAULT_ROLE_ID')"
secret_id_wrapped="$(echo "$userdata" | jq --raw-output '.VAULT_SECRET_ID_WRAPPED')"
if [[ -f ${cacheFilePath} ]]; then

View file

@ -120,6 +120,7 @@ in
ignore_changes = [
"ssh_keys"
"public_net"
"user_data"
"image"
];
};

View file

@ -3,7 +3,11 @@
...
}:
{
imports = [ "${inputs.self}/nix/profiles/nixos/hetzner-server.nix" ];
imports = [
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
./mailserver
./forgejo
];
khscodes.infrastructure.hetzner-instance = {
enable = true;
mapRdns = true;

View file

@ -1,34 +0,0 @@
{
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "forgejo/data/mailserver/dkim" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';
destination = "/var/lib/vault-agent/mailserver/dkim/private.key";
perms = "0600";
owner = "rspamd";
group = "rspamd";
restartUnits = [
"rspamd.service"
"postfix.service"
];
}
{
contents = ''
{{- with secret "forgejo/data/mailserver/forgejo-user" -}}
{{ .Data.data.hashed_password }}
{{- end -}}
'';
destination = "/var/lib/vault-agent/mailserver/users/forgejo.passwd.hash";
perms = "0600";
owner = "rspamd";
group = "rspamd";
restartUnits = [
"rspamd.service"
"postfix.service"
];
}
];
}

View file

@ -0,0 +1,202 @@
{ config, pkgs, ... }:
let
home_forgejo = pkgs.writeText "home_forgejo.tmpl" ''
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-person"}}
</h1>
<p class="large">
This is just a personal self hosted software forge for my projects. I might publish a few things here for public consumption.
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-code"}}
</h1>
<p class="large">
This server is running <a target="_blank" rel="noopener noreferrer" href="https://forgejo.org">Forgejo</a>. Click the link to learn more.
</p>
</div>
</div>
'';
# This simply has the <h2> tag removed.
home = pkgs.writeText "home.tmpl" ''
{{template "base/head" .}}
<div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}" class="page-content home">
<div class="tw-mb-8 tw-px-8">
<div class="center">
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
<div class="hero">
<h1 class="ui icon header title">
{{AppDisplayName}}
</h1>
</div>
</div>
</div>
{{template "home_forgejo" .}}
</div>
{{template "base/footer" .}}
'';
in
{
imports = [ ./oauth.nix ];
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "forgejo/data/mailserver/users/forgejo" -}}
{{ .Data.data.password }}
{{- end -}}
'';
destination = "/var/lib/vault-agent/forgejo/mailserver/forgejo.passwd";
perms = "0600";
owner = "git";
group = "git";
restartUnits = [
"forgejo.service"
];
}
];
systemd.services.forgejo = {
unitConfig = {
ConditionPathExists = [ "/var/lib/vault-agent/forgejo/mailserver/forgejo.passwd" ];
};
};
services.forgejo = {
enable = true;
user = "git";
group = "git";
settings = {
DEFAULT = {
APP_NAME = "KAS: Codes";
};
server = rec {
DOMAIN = "kas.codes";
ROOT_URL = "https://${DOMAIN}";
};
session = {
COOKIE_SECURE = true;
};
service = {
DISABLE_REGISTRATION = true;
ENABLE_INTERNAL_SIGNIN = false;
};
repository = {
DEFAULT_REPO_UNITS = "repo.code,repo.releases,repo.issues,repo.packages,repo.actions";
};
mailer = {
ENABLED = true;
SMTP_ADDR = "kas.codes";
FROM = "forgejo@khs.codes";
USER = "forgejo@khs.codes";
};
"ui.meta" = {
AUTHOR = "Kaare Hoff Skovgaard <kaare@kaareskovgaard.net>";
DESCRIPTION = "A self-hosted software forge for KAS/KHS";
KEYWORDS = "khs,kas,kastermester,code";
};
actions = {
DEFAULT_ACTIONS_URL = "https://kas.codes";
};
oauth2_client = {
ENABLE_AUTO_REGISTRATION = true;
USERNAME = "nickname";
ACCOUNT_LINKING = "disabled";
REGISTER_EMAIL_CONFIRM = false;
};
};
secrets.mailer.PASSWD = "/var/lib/vault-agent/forgejo/mailserver/forgejo.passwd";
lfs = {
enable = true;
};
database = {
type = "postgres";
user = "git";
name = "git";
};
dump = {
enable = true;
file = "forgejo-dump";
};
};
systemd.services.write-forgejo-templates = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
before = [ "forgejo.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.lib.getExe (
pkgs.writeShellApplication {
name = "write-forgejo-templates";
runtimeInputs = [ pkgs.uutils-coreutils-noprefix ];
text = ''
if [ ! -d /var/lib/forgejo/custom/templates ]; then
mkdir /var/lib/forgejo/custom/templates
fi
ln -sf ${home_forgejo} /var/lib/forgejo/custom/templates/home_forgejo.tmpl
ln -sf ${home} /var/lib/forgejo/custom/templates/home.tmpl
'';
}
);
};
};
users.users.forgejo-backup = {
isNormalUser = true;
home = "/home/forgejo-backup";
group = "forgejo-backup";
createHome = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ/hn4Q1+5KpViol+Kk7bUvWrka2hhKEXqUJVY0quQLu forgejo-backup@truenas.kaareskovgaard.net"
];
};
users.groups.forgejo-backup = { };
systemd.timers.forgejo-dump = {
timerConfig = {
Unit = "forgejo-copy-dump.service";
};
};
systemd.services.forgejo-copy-dump = {
requires = [ "forgejo-dump.service" ];
after = [ "forgejo-dump.service" ];
wantedBy = [ "timers.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.lib.getExe (
pkgs.writeShellApplication {
name = "forgejo-copy-dump";
runtimeInputs = [ pkgs.uutils-coreutils-noprefix ];
text = ''
mv /var/lib/forgejo/dump/forgejo-dump.zip /home/forgejo-backup/dump.zip
chown forgejo-backup:forgejo-backup /home/forgejo-backup/dump.zip
chmod 0640 /home/forgejo-backup/dump.zip
'';
}
);
};
};
khscodes.services.nginx = {
enable = true;
virtualHosts = {
"kas.codes" = {
extraConfig = ''
client_max_body_size 32M;
'';
locations."/" = {
proxyPass = "http://localhost:3000";
};
};
};
};
users.users.git = {
isSystemUser = true;
group = "git";
home = config.services.forgejo.stateDir;
useDefaultShell = true;
};
users.groups.git = { };
}

View file

@ -0,0 +1,79 @@
{
pkgs,
lib,
config,
...
}:
let
oauthSecretIdFile = "/run/forgejo/oauth_secret_id";
setApp = pkgs.writeShellApplication {
name = "forgejo-set-oauth-app";
runtimeInputs = [
pkgs.forgejo
pkgs.gnugrep
];
text = ''
config="${config.services.forgejo.stateDir}/custom/conf/app.ini"
secret="$(cat ${oauthSecretIdFile})"
if gitea "--config=$config" admin auth list | grep -q "Kanidm" 2> /dev/null; then
echo "Oauth2 app already exists, updating not yet implemented"
exit 0
else
gitea "--config=$config" admin auth add-oauth \
--name "Kanidm" \
--key "forgejo" \
--secret "$secret" \
--auto-discover-url https://login.kaareskovgaard.net/oauth2/openid/forgejo/.well-known/openid-configuration \
--scopes "email profile" \
--skip-local-2fa \
--provider openidConnect \
--group-claim-name groups \
--admin-group admin \
--group-team-map-removal \
--group-team-map '{"nix": ["nix"], "actions": ["actons"]}'
fi
'';
};
in
{
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "kanidm/data/apps/forgejo" -}}
{{ .Data.data.basic_secret }}
{{- end -}}
'';
destination = oauthSecretIdFile;
perms = "0600";
owner = "git";
group = "git";
restartUnits = [
"forgejo-setup-oauth.service"
];
}
];
khscodes.infrastructure.vault-server-approle.policy = {
"kanidm/data/apps/forgejo" = {
capabilities = [ "read" ];
};
};
systemd.services.forgejo-setup-oauth = {
enable = true;
wants = [ "vault-agent-openbao.service" ];
requires = [ "forgejo.service" ];
after = [
"forgejo.service"
"vault-agent-openbao.service"
];
unitConfig = {
ConditionPathExists = [ oauthSecretIdFile ];
};
serviceConfig = {
Type = "oneshot";
User = "git";
Group = "git";
ExecStart = lib.getExe setApp;
};
};
}

View file

@ -0,0 +1,44 @@
{ inputs, ... }:
{
imports = [
./dkim.nix
./forgejo-user.nix
inputs.simple-nixos-mailserver.nixosModules.mailserver
];
khscodes.infrastructure.vault-server-approle.policy = {
"forgejo/data/mailserver/*" = {
capabilities = [ "read" ];
};
};
khscodes.infrastructure.provisioning.pre.modules = [
{
khscodes.vault = {
enable = true;
mount.forgejo = {
path = "forgejo";
type = "kv";
options = {
version = "2";
};
description = "Secrets used for forgejo";
};
};
}
];
mailserver = {
enable = true;
fqdn = "kas.codes";
domains = [ "kas.codes" ];
loginAccounts = {
"forgejo@kas.codes" = {
hashedPasswordFile = "/var/lib/vault-agent/mailserver/users/forgejo.passwd.hash";
sendOnly = true;
};
};
certificateScheme = "acme";
dkimKeyDirectory = "/var/lib/vault-agent/mailserver/dkim/";
dkimSelector = "dkim_rsa";
# Not sure we need to set this at all.
dkimKeyBits = 2048;
};
}

View file

@ -0,0 +1,112 @@
let
publicKeyBegin = ''"-----BEGIN PUBLIC KEY-----\n"'';
publicKeyEnd = ''"-----END PUBLIC KEY-----\n"'';
dkimPublicKey =
tls_key:
''''${ replace(trimprefix(trimsuffix(${tls_key}.public_key_pem, ${publicKeyEnd}), ${publicKeyBegin}), "\n", "") }'';
in
{
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "forgejo/data/mailserver/dkim/rsa" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';
destination = "/var/lib/vault-agent/mailserver/dkim/rsa_private.key";
perms = "0600";
owner = "rspamd";
group = "rspamd";
restartUnits = [
"rspamd.service"
"postfix.service"
];
}
{
contents = ''
{{- with secret "forgejo/data/mailserver/dkim/ed25519" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';
destination = "/var/lib/vault-agent/mailserver/dkim/ed25519_private.key";
perms = "0600";
owner = "rspamd";
group = "rspamd";
restartUnits = [
"rspamd.service"
"postfix.service"
];
}
];
khscodes.infrastructure.provisioning.pre.modules = [
(
{ config, ... }:
{
terraform.required_providers.tls = {
source = "hashicorp/tls";
version = "4.1.0";
};
provider.tls = { };
resource.tls_private_key.dkim_rsa = {
algorithm = "RSA";
rsa_bits = 2048;
};
resource.tls_private_key.dkim_ed25519 = {
algorithm = "ED25519";
};
resource.cloudflare_record.dkim_rsa = {
name = "snm_rsa._domainkey";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
type = "TXT";
content = ''"v=DKIM1;k=rsa;p=${dkimPublicKey "tls_private_key.dkim_rsa"}"'';
comment = "app=kas.codes";
ttl = 600;
};
resource.cloudflare_record.dkim_ed25519 = {
name = "snm_ed25519._domainkey";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
type = "TXT";
content = ''"v=DKIM1;k=ed25519;p=${dkimPublicKey "tls_private_key.dkim_ed25519"}"'';
comment = "app=kas.codes";
ttl = 600;
};
resource.cloudflare_record.spf = {
name = "@";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
type = "TXT";
content = ''"v=spf1 ip4:${config.khscodes.hcloud.output.server.compute.ipv4_address} ip6:${config.khscodes.hcloud.output.server.compute.ipv6_address} -all"'';
comment = "app=kas.codes";
ttl = 600;
};
resource.cloudflare_record.dmarc = {
name = "_dmarc";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
type = "TXT";
content = ''"v=DMARC1; p=reject; adkim=s; aspf=s;"'';
comment = "app=kas.codes";
ttl = 600;
};
resource.vault_kv_secret_v2.dkim_rsa_secret = {
mount = config.khscodes.vault.output.mount.forgejo.path;
name = "mailserver/dkim/rsa";
data_json = ''
{ "dkim_private_key": ''${ jsonencode(resource.tls_private_key.dkim_rsa.private_key_pem) } }
'';
};
resource.vault_kv_secret_v2.dkim_ed25519_secret = {
mount = config.khscodes.vault.output.mount.forgejo.path;
name = "mailserver/dkim/ed25519";
data_json = ''
{ "dkim_private_key": ''${ jsonencode(resource.tls_private_key.dkim_ed25519.private_key_pem) } }
'';
};
}
)
];
}

View file

@ -0,0 +1,53 @@
let
bcrypt = expr: "\${ jsonencode(bcrypt(${expr})) }";
in
{
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "forgejo/data/mailserver/users/forgejo" -}}
{{ .Data.data.hashed_password }}
{{- end -}}
'';
destination = "/var/lib/vault-agent/mailserver/users/forgejo.passwd.hash";
perms = "0600";
owner = "rspamd";
group = "rspamd";
restartUnits = [
"rspamd.service"
"postfix.service"
];
}
];
khscodes.infrastructure.provisioning.pre.modules = [
(
{ config, ... }:
{
terraform.required_providers.random = {
source = "hashicorp/random";
version = "3.7.2";
};
provider.random = { };
resource.random_password.forgejo_mail_passwd = {
length = 48;
numeric = true;
lower = true;
upper = true;
special = false;
};
resource.vault_kv_secret_v2.forgejo_email_user_password = {
mount = config.khscodes.vault.output.mount.forgejo.path;
name = "mailserver/users/forgejo";
data_json = ''
{
"hashed_password": ${bcrypt "resource.random_password.forgejo_mail_passwd.result"},
"password": ''${ jsonencode(resource.random_password.forgejo_mail_passwd.result) }
}
'';
};
}
)
];
}

View file

@ -10,6 +10,7 @@ let
openbaoAppBasicSecretFile = "/var/lib/vault-agent/kanidm/openbao_basic_secret";
openbaoCliAppBasicSecretFile = "/var/lib/vault-agent/kanidm/openbao_cli_basic_secret";
monitoringAppBasicSecretFile = "/var/lib/vault-agent/kanidm/monitoring_basic_secret";
forgejoAppBasicSecretFile = "/var/lib/vault-agent/kanidm/monitoring_basic_secret";
openbaoDomain = config.khscodes.infrastructure.openbao.domain;
openbaoAllowedRedirectUrls = [
"https://${openbaoDomain}/ui/vault/auth/kanidm/oidc/callback"
@ -70,6 +71,14 @@ in
present = true;
members = [ "khs" ];
};
groups.forgejo_user = {
present = true;
members = [ "khs" ];
};
groups.forgejo_comitter = {
present = true;
members = [ "khs" ];
};
# We cannot add oauth2 apps before the secrets for them are generated.
systems.oauth2 = lib.mkIf (!bootstrapping) {
openbao = {
@ -137,6 +146,30 @@ in
];
};
};
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"
];
};
};
};
};
};
};
@ -218,6 +251,20 @@ in
{ "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.
@ -381,6 +428,18 @@ in
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" ];