Compare commits
2 Commits
1ac43cdb72
...
74a2b1970e
Author | SHA1 | Date |
---|---|---|
Oystein Kristoffer Tveit | 74a2b1970e | |
Oystein Kristoffer Tveit | 91876214f0 |
|
@ -6,9 +6,9 @@ let
|
||||||
"Kurs"
|
"Kurs"
|
||||||
];
|
];
|
||||||
|
|
||||||
giteaCfg = config.services.gitea;
|
cfg = config.services.gitea;
|
||||||
|
|
||||||
giteaWebSecretProviderScript = pkgs.writers.writePython3 "gitea-web-secret-provider" {
|
program = 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,7 +20,28 @@ let
|
||||||
makeWrapperArgs = [
|
makeWrapperArgs = [
|
||||||
"--prefix PATH : ${(lib.makeBinPath [ pkgs.openssh ])}"
|
"--prefix PATH : ${(lib.makeBinPath [ pkgs.openssh ])}"
|
||||||
];
|
];
|
||||||
} (lib.fileContents ./gitea-web-secret-provider.py);
|
} (lib.pipe ./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" = {
|
||||||
|
@ -61,59 +82,74 @@ 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
|
||||||
systemd.services."gitea-web-secret-provider@" = {
|
# %S - /var/lib
|
||||||
description = "Ensure all repos in %i has an SSH key to push web content";
|
systemd.services = {
|
||||||
requires = [ "gitea.service" "network.target" ];
|
"gitea-web-secret-provider@" = {
|
||||||
serviceConfig = {
|
description = "Ensure all repos in %i has an SSH key to push web content";
|
||||||
Slice = "system-giteaweb.slice";
|
requires = [ "gitea.service" "network.target" ];
|
||||||
Type = "oneshot";
|
serviceConfig = {
|
||||||
ExecStart = let
|
Slice = "system-giteaweb.slice";
|
||||||
args = lib.cli.toGNUCommandLineShell { } {
|
Type = "oneshot";
|
||||||
org = "%i";
|
ExecStart = let
|
||||||
token-path = "%d/token";
|
args = lib.cli.toGNUCommandLineShell { } {
|
||||||
api-url = "${giteaCfg.settings.server.ROOT_URL}api/v1";
|
org = "%i";
|
||||||
key-dir = "/var/lib/gitea-web/keys/%i";
|
token-path = "%d/token";
|
||||||
authorized-keys-path = "/var/lib/gitea-web/authorized_keys.d/%i";
|
api-url = "${cfg.settings.server.ROOT_URL}api/v1";
|
||||||
rrsync-script = pkgs.writeShellScript "rrsync-chown" ''
|
key-dir = "%S/gitea-web/keys/%i";
|
||||||
${lib.getExe pkgs.rrsync} -wo "$1"
|
authorized-keys-path = "%S/gitea-web/authorized_keys.d/%i";
|
||||||
${pkgs.coreutils}/bin/chown -R gitea:nginx "$1"
|
rrsync-path = "${pkgs.rrsync}/bin/rrsync";
|
||||||
'';
|
web-dir = "%S/gitea-web/web";
|
||||||
web-dir = "/var/lib/gitea-web/web";
|
};
|
||||||
};
|
in "${program} ${args}";
|
||||||
in "${giteaWebSecretProviderScript} ${args}";
|
User = "gitea";
|
||||||
User = "gitea";
|
Group = "gitea";
|
||||||
Group = "gitea";
|
StateDirectory = "%i";
|
||||||
StateDirectory = "%i";
|
LoadCredential = [
|
||||||
LoadCredential = [
|
"token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
|
||||||
"token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
|
];
|
||||||
];
|
} // commonHardening;
|
||||||
NoNewPrivileges = true;
|
};
|
||||||
PrivateTmp = true;
|
|
||||||
PrivateDevices = true;
|
"gitea-web-chown@" = {
|
||||||
ProtectSystem = true;
|
description = "Ensure all gitea-web content has correct ownership";
|
||||||
ProtectHome = true;
|
serviceConfig = {
|
||||||
ProtectControlGroups = true;
|
Slice = "system-giteaweb.slice";
|
||||||
ProtectKernelModules = true;
|
Type = "oneshot";
|
||||||
ProtectKernelTunables = true;
|
ExecStart = "${pkgs.coreutils}/bin/chown -R gitea:nginx '%S/gitea-web/web/%i'";
|
||||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
PrivateNetwork = true;
|
||||||
RestrictRealtime = true;
|
} // commonHardening;
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
MemoryDenyWriteExecute = true;
|
|
||||||
LockPersonality = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.timers."gitea-web-secret-provider@" = {
|
systemd.timers = {
|
||||||
description = "Ensure all repos in %i has an SSH key to push web content";
|
"gitea-web-secret-provider@" = {
|
||||||
timerConfig = {
|
description = "Ensure all repos in %i has an SSH key to push web content";
|
||||||
RandomizedDelaySec = "1h";
|
timerConfig = {
|
||||||
Persistent = true;
|
RandomizedDelaySec = "1h";
|
||||||
Unit = "gitea-web-secret-provider@%i.service";
|
Persistent = true;
|
||||||
OnCalendar = "daily";
|
Unit = "gitea-web-secret-provider@%i.service";
|
||||||
|
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 = map (org: "gitea-web-secret-provider@${org}.timer") organizations;
|
systemd.targets.timers.wants = lib.mapCartesianProduct ({ timer, org }: "${timer}@${org}.timer") {
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#!/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
|
||||||
|
@ -13,7 +16,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-script", metavar='PATH', type=Path, help="The path to a rrsync script, taking the destination path as its single argument")
|
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("--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()
|
||||||
|
@ -45,14 +48,15 @@ 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.PIPE,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.DEVNULL
|
||||||
)
|
)
|
||||||
print(f"Generated SSH key for `{args.org}/{repository}`")
|
print(f"Generated SSH key for `{args.org}/{repository}`")
|
||||||
|
|
||||||
|
@ -78,7 +82,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_script} {args.web_dir}/{args.org}/{repo}"
|
command = f"{args.rrsync_path} -wo {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:
|
||||||
|
|
Loading…
Reference in New Issue