Compare commits

..

2 Commits

Author SHA1 Message Date
h7x4 50fd7ccee2
bekkalokk/gitea-web: host pages 2024-08-20 20:54:56 +02:00
h7x4 f68c7a1dae
bekkalokk/gitea: set up gitea-web sync units 2024-08-20 20:54:56 +02:00
2 changed files with 54 additions and 94 deletions

View File

@ -6,9 +6,9 @@ let
"Kurs" "Kurs"
]; ];
cfg = config.services.gitea; giteaCfg = config.services.gitea;
program = pkgs.writers.writePython3 "gitea-web-secret-provider" { giteaWebSecretProviderScript = pkgs.writers.writePython3 "gitea-web-secret-provider" {
libraries = with pkgs.python3Packages; [ requests ]; libraries = with pkgs.python3Packages; [ requests ];
flakeIgnore = [ flakeIgnore = [
"E501" # Line over 80 chars lol "E501" # Line over 80 chars lol
@ -20,28 +20,7 @@ let
makeWrapperArgs = [ makeWrapperArgs = [
"--prefix PATH : ${(lib.makeBinPath [ pkgs.openssh ])}" "--prefix PATH : ${(lib.makeBinPath [ pkgs.openssh ])}"
]; ];
} (lib.pipe ./gitea-web-secret-provider.py [ } (builtins.fileContents ./gitea-web-secret-provider.py);
builtins.readFile
(lib.splitString "\n")
(lib.drop 2)
lib.concatLines
]);
commonHardening = {
NoNewPrivileges = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectSystem = true;
ProtectHome = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictRealtime = true;
RestrictSUIDSGID = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
};
in in
{ {
sops.secrets."gitea/web-secret-provider/token" = { sops.secrets."gitea/web-secret-provider/token" = {
@ -82,74 +61,59 @@ in
# https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers # https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers
# %i - instance name (after the @) # %i - instance name (after the @)
# %d - secrets directory # %d - secrets directory
# %S - /var/lib systemd.services."gitea-web-secret-provider@" = {
systemd.services = { description = "Ensure all repos in %i has an SSH key to push web content";
"gitea-web-secret-provider@" = { requires = [ "gitea.service" "network.target" ];
description = "Ensure all repos in %i has an SSH key to push web content"; serviceConfig = {
requires = [ "gitea.service" "network.target" ]; Slice = "system-giteaweb.slice";
serviceConfig = { Type = "oneshot";
Slice = "system-giteaweb.slice"; ExecStart = let
Type = "oneshot"; args = lib.cli.toGNUCommandLineShell { } {
ExecStart = let org = "%i";
args = lib.cli.toGNUCommandLineShell { } { token-path = "%d/token";
org = "%i"; api-url = "${giteaCfg.settings.server.ROOT_URL}api/v1";
token-path = "%d/token"; key-dir = "/var/lib/gitea-web/keys/%i";
api-url = "${cfg.settings.server.ROOT_URL}api/v1"; authorized-keys-path = "/var/lib/gitea-web/authorized_keys.d/%i";
key-dir = "%S/gitea-web/keys/%i"; rrsync-script = pkgs.writeShellScript "rrsync-chown" ''
authorized-keys-path = "%S/gitea-web/authorized_keys.d/%i"; ${lib.getExe pkgs.rrsync} -wo "$1"
rrsync-path = "${pkgs.rrsync}/bin/rrsync"; ${pkgs.coreutils}/bin/chown -R gitea:nginx "$1"
web-dir = "%S/gitea-web/web"; '';
}; web-dir = "/var/lib/gitea-web/web";
in "${program} ${args}"; };
User = "gitea"; in "${giteaWebSecretProviderScript} ${args}";
Group = "gitea"; User = "gitea";
StateDirectory = "%i"; Group = "gitea";
LoadCredential = [ StateDirectory = "%i";
"token:${config.sops.secrets."gitea/web-secret-provider/token".path}" LoadCredential = [
]; "token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
} // commonHardening; ];
}; NoNewPrivileges = true;
PrivateTmp = true;
"gitea-web-chown@" = { PrivateDevices = true;
description = "Ensure all gitea-web content has correct ownership"; ProtectSystem = true;
serviceConfig = { ProtectHome = true;
Slice = "system-giteaweb.slice"; ProtectControlGroups = true;
Type = "oneshot"; ProtectKernelModules = true;
ExecStart = "${pkgs.coreutils}/bin/chown -R gitea:nginx '%S/gitea-web/web/%i'"; ProtectKernelTunables = true;
PrivateNetwork = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
} // commonHardening; RestrictRealtime = true;
RestrictSUIDSGID = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
}; };
}; };
systemd.timers = { systemd.timers."gitea-web-secret-provider@" = {
"gitea-web-secret-provider@" = { description = "Ensure all repos in %i has an SSH key to push web content";
description = "Ensure all repos in %i has an SSH key to push web content"; timerConfig = {
timerConfig = { RandomizedDelaySec = "1h";
RandomizedDelaySec = "1h"; Persistent = true;
Persistent = true; Unit = "gitea-web-secret-provider@%i.service";
Unit = "gitea-web-secret-provider@%i.service"; OnCalendar = "daily";
OnCalendar = "daily";
};
};
"gitea-web-chown@" = {
description = "Ensure all gitea-web content is owned by the gitea user";
timerConfig = {
RandomizedDelaySec = "10m";
Persistent = true;
Unit = "gitea-web-chown@%i.service";
OnCalendar = "hourly";
};
}; };
}; };
systemd.targets.timers.wants = lib.mapCartesianProduct ({ timer, org }: "${timer}@${org}.timer") { systemd.targets.timers.wants = map (org: "gitea-web-secret-provider@${org}.timer") organizations;
timer = [
"gitea-web-secret-provider"
"gitea-web-chown"
];
org = organizations;
};
services.openssh.authorizedKeysFiles = map (org: "/var/lib/gitea-web/authorized_keys.d/${org}") organizations; services.openssh.authorizedKeysFiles = map (org: "/var/lib/gitea-web/authorized_keys.d/${org}") organizations;

View File

@ -1,6 +1,3 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p "python3.withPackages(ps: with ps; [ requests ])" openssh
import argparse import argparse
import hashlib import hashlib
import os import os
@ -16,7 +13,7 @@ def parse_args():
parser.add_argument("--api-url", metavar='URL', type=str, help="The URL of the Gitea API", default="https://git.pvv.ntnu.no/api/v1") parser.add_argument("--api-url", metavar='URL', type=str, help="The URL of the Gitea API", default="https://git.pvv.ntnu.no/api/v1")
parser.add_argument("--key-dir", metavar='PATH', type=Path, help="The directory to store the generated keys in", default="/run/gitea-web-secret-provider") parser.add_argument("--key-dir", metavar='PATH', type=Path, help="The directory to store the generated keys in", default="/run/gitea-web-secret-provider")
parser.add_argument("--authorized-keys-path", metavar='PATH', type=Path, help="The path to the resulting authorized_keys file", default="/etc/ssh/authorized_keys.d/gitea-web-secret-provider") parser.add_argument("--authorized-keys-path", metavar='PATH', type=Path, help="The path to the resulting authorized_keys file", default="/etc/ssh/authorized_keys.d/gitea-web-secret-provider")
parser.add_argument("--rrsync-path", metavar='PATH', type=Path, help="The path to the rrsync binary", default="/run/current-system/sw/bin/rrsync") parser.add_argument("--rrsync-script", metavar='PATH', type=Path, help="The path to a rrsync script, taking the destination path as its single argument")
parser.add_argument("--web-dir", metavar='PATH', type=Path, help="The directory to sync the repositories to", default="/var/www") parser.add_argument("--web-dir", metavar='PATH', type=Path, help="The directory to sync the repositories to", default="/var/www")
parser.add_argument("--force", action="store_true", help="Overwrite existing keys") parser.add_argument("--force", action="store_true", help="Overwrite existing keys")
return parser.parse_args() return parser.parse_args()
@ -48,15 +45,14 @@ def generate_ssh_key(args: argparse.Namespace, repository: str):
[ [
"ssh-keygen", "ssh-keygen",
*("-t", "ed25519"), *("-t", "ed25519"),
*("-b", "4096"),
*("-f", key_path), *("-f", key_path),
*("-N", ""), *("-N", ""),
*("-C", f"{args.org}/{repository}"), *("-C", f"{args.org}/{repository}"),
], ],
check=True, check=True,
stdin=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL stderr=subprocess.PIPE,
) )
print(f"Generated SSH key for `{args.org}/{repository}`") print(f"Generated SSH key for `{args.org}/{repository}`")
@ -82,7 +78,7 @@ SSH_OPTS = ",".join([
def generate_authorized_keys(args: argparse.Namespace, repo_public_keys: list[tuple[str, str]]): def generate_authorized_keys(args: argparse.Namespace, repo_public_keys: list[tuple[str, str]]):
lines = [] lines = []
for repo, public_key in repo_public_keys: for repo, public_key in repo_public_keys:
command = f"{args.rrsync_path} -wo {args.web_dir}/{args.org}/{repo}" command = f"{args.rrsync_script} {args.web_dir}/{args.org}/{repo}"
lines.append(f'command="{command}",{SSH_OPTS} {public_key}') lines.append(f'command="{command}",{SSH_OPTS} {public_key}')
with open(args.authorized_keys_path, "w") as f: with open(args.authorized_keys_path, "w") as f: