Compare commits

..

2 Commits

Author SHA1 Message Date
h7x4 ce2f6a4546
bekkalokk/gitea-web: host pages 2024-08-14 21:28:48 +02:00
h7x4 ed13e49ba7
bekkalokk/gitea: set up gitea-web sync units 2024-08-14 21:28:48 +02:00
2 changed files with 38 additions and 56 deletions

View File

@ -52,31 +52,15 @@ in
] ++ (map (org: "gitea-web-secret-provider@${org}") organizations); ] ++ (map (org: "gitea-web-secret-provider@${org}") organizations);
}; };
systemd.tmpfiles.settings."10-gitea-web-secret-provider" = { systemd.tmpfiles.settings."10-gitea-web-secret-provider"."/var/lib/gitea-web/authorized_keys.d".d = {
"/var/lib/gitea-web/authorized_keys.d".d = { user = "gitea";
user = "gitea"; group = "gitea";
group = "gitea"; mode = "700";
mode = "700"; };
};
"/var/lib/gitea-web/web".d = {
user = "gitea";
group = "nginx";
mode = "750";
};
} //
(builtins.listToAttrs (map (org: {
name = "/var/lib/gitea-web/web/${org}";
value = {
d = {
user = "gitea";
group = "nginx";
mode = "750";
};
};
}) organizations));
systemd.slices.system-giteaweb = { systemd.slices.system-giteaweb = {
description = "Gitea web directories"; description = "Gitea web directories";
wantedBy = [ "multi-user.target" ];
}; };
# https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers # https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers
@ -95,7 +79,7 @@ in
org = "%i"; org = "%i";
token-path = "%d/token"; token-path = "%d/token";
api-url = "${cfg.settings.server.ROOT_URL}api/v1"; api-url = "${cfg.settings.server.ROOT_URL}api/v1";
key-dir = "%S/gitea-web/keys/%i"; key-dir = "%S/%i/keys";
authorized-keys-path = "%S/gitea-web/authorized_keys.d/%i"; authorized-keys-path = "%S/gitea-web/authorized_keys.d/%i";
rrsync-path = "${pkgs.rrsync}/bin/rrsync"; rrsync-path = "${pkgs.rrsync}/bin/rrsync";
web-dir = "%S/gitea-web/web"; web-dir = "%S/gitea-web/web";
@ -111,11 +95,18 @@ in
}; };
"gitea-web-chown@" = { "gitea-web-chown@" = {
description = "Ensure all gitea-web content has correct ownership"; description = "Ensure all gitea-web content is owned by the gitea user";
serviceConfig = { serviceConfig = {
Slice = "system-giteaweb.slice"; Slice = "system-giteaweb.slice";
Type = "oneshot"; Type = "oneshot";
ExecStart = "${pkgs.coreutils}/bin/chown -R gitea:nginx '%S/gitea-web/web/%i'"; ExecStart = "${pkgs.coreutils}/bin/chown -R gitea:gitea '%S/gitea-web'";
StateDirectory = "%i";
LoadCredential = [
"token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
];
PrivateNetwork = true; PrivateNetwork = true;
} // commonHardening; } // commonHardening;
}; };

View File

@ -11,18 +11,18 @@ from pathlib import Path
def parse_args(): def parse_args():
parser = argparse.ArgumentParser(description="Generate SSH keys for Gitea repositories and add them as secrets") parser = argparse.ArgumentParser(description="Generate SSH keys for Gitea repositories and add them as secrets")
parser.add_argument("--org", required=True, type=str, help="The organization to generate keys for") parser.add_argument("--org", required=True, help="The organization to generate keys for")
parser.add_argument("--token-path", metavar='PATH', required=True, type=Path, help="Path to a file containing the Gitea API token") parser.add_argument("--token-path", metavar='PATH', required=True, help="Path to a file containing the Gitea API token")
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', 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', 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', 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-path", metavar='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', 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()
def add_secret(args: argparse.Namespace, token: str, repo: str, name: str, secret: str): def add_secret(args, token, repo, name, secret):
result = requests.put( result = requests.put(
f"{args.api_url}/repos/{args.org}/{repo}/actions/secrets/{name}", f"{args.api_url}/repos/{args.org}/{repo}/actions/secrets/{name}",
json = { 'data': secret }, json = { 'data': secret },
@ -32,7 +32,7 @@ def add_secret(args: argparse.Namespace, token: str, repo: str, name: str, secre
raise Exception(f"Failed to add secret: {result.json()}") raise Exception(f"Failed to add secret: {result.json()}")
def get_org_repo_list(args: argparse.Namespace, token: str): def get_org_repo_list(args, token):
result = requests.get( result = requests.get(
f"{args.api_url}/orgs/{args.org}/repos", f"{args.api_url}/orgs/{args.org}/repos",
headers = { 'Authorization': 'token ' + token }, headers = { 'Authorization': 'token ' + token },
@ -40,16 +40,16 @@ def get_org_repo_list(args: argparse.Namespace, token: str):
return [repo["name"] for repo in result.json()] return [repo["name"] for repo in result.json()]
def generate_ssh_key(args: argparse.Namespace, repository: str): def generate_ssh_key(args, repository: str):
keyname = hashlib.sha256(args.org.encode() + repository.encode()).hexdigest() keyname = hashlib.sha256(args.org.encode() + repository.encode()).hexdigest()
key_path = args.key_dir / keyname
if not key_path.is_file() or args.force: if not os.path.exists(os.path.join(args.key_dir, keyname)) or args.force:
subprocess.run( subprocess.run(
[ [
"ssh-keygen", "ssh-keygen",
*("-t", "ed25519"), *("-t", "ed25519"),
*("-b", "4096"), *("-b", "4096"),
*("-f", key_path), *("-f", os.path.join(args.key_dir, keyname)),
*("-N", ""), *("-N", ""),
*("-C", f"{args.org}/{repository}"), *("-C", f"{args.org}/{repository}"),
], ],
@ -60,33 +60,24 @@ def generate_ssh_key(args: argparse.Namespace, repository: str):
) )
print(f"Generated SSH key for `{args.org}/{repository}`") print(f"Generated SSH key for `{args.org}/{repository}`")
with open(key_path, "r") as f: with open(os.path.join(args.key_dir, keyname), "r") as f:
private_key = f.read() private_key = f.read()
pub_key_path = args.key_dir / (keyname + '.pub') with open(os.path.join(args.key_dir, keyname + ".pub"), "r") as f:
with open(pub_key_path, "r") as f:
public_key = f.read() public_key = f.read()
return private_key, public_key return private_key, public_key
SSH_OPTS = ",".join([ def generate_authorized_keys(args, repo_public_keys: list[tuple[str, str]]):
"restrict", result = ""
"no-agent-forwarding",
"no-port-forwarding",
"no-pty",
"no-X11-forwarding",
])
def generate_authorized_keys(args: argparse.Namespace, repo_public_keys: list[tuple[str, str]]):
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}" result += f"""
lines.append(f'command="{command}",{SSH_OPTS} {public_key}') command="{args.rrsync_path} -wo {args.web_dir}/{args.org}/{repo}",restrict,no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding {public_key}
""".strip() + "\n"
with open(args.authorized_keys_path, "w") as f: with open(args.authorized_keys_path, "w") as f:
f.writelines(lines) f.write(result)
def main(): def main():
@ -96,7 +87,7 @@ def main():
token = f.read().strip() token = f.read().strip()
os.makedirs(args.key_dir, 0o700, exist_ok=True) os.makedirs(args.key_dir, 0o700, exist_ok=True)
os.makedirs(args.authorized_keys_path.parent, 0o700, exist_ok=True) os.makedirs(Path(args.authorized_keys_path).parent, 0o700, exist_ok=True)
repos = get_org_repo_list(args, token) repos = get_org_repo_list(args, token)
print(f'Found {len(repos)} repositories in `{args.org}`') print(f'Found {len(repos)} repositories in `{args.org}`')