Compare commits
2 Commits
ce2f6a4546
...
74a2b1970e
Author | SHA1 | Date |
---|---|---|
h7x4 | 74a2b1970e | |
h7x4 | 91876214f0 |
|
@ -52,15 +52,31 @@ 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"."/var/lib/gitea-web/authorized_keys.d".d = {
|
systemd.tmpfiles.settings."10-gitea-web-secret-provider" = {
|
||||||
|
"/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
|
||||||
|
@ -79,7 +95,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/%i/keys";
|
key-dir = "%S/gitea-web/keys/%i";
|
||||||
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";
|
||||||
|
@ -95,18 +111,11 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
"gitea-web-chown@" = {
|
"gitea-web-chown@" = {
|
||||||
description = "Ensure all gitea-web content is owned by the gitea user";
|
description = "Ensure all gitea-web content has correct ownership";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Slice = "system-giteaweb.slice";
|
Slice = "system-giteaweb.slice";
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
ExecStart = "${pkgs.coreutils}/bin/chown -R gitea:gitea '%S/gitea-web'";
|
ExecStart = "${pkgs.coreutils}/bin/chown -R gitea:nginx '%S/gitea-web/web/%i'";
|
||||||
|
|
||||||
StateDirectory = "%i";
|
|
||||||
|
|
||||||
LoadCredential = [
|
|
||||||
"token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
|
|
||||||
];
|
|
||||||
|
|
||||||
PrivateNetwork = true;
|
PrivateNetwork = true;
|
||||||
} // commonHardening;
|
} // commonHardening;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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, help="The organization to generate keys for")
|
parser.add_argument("--org", required=True, type=str, help="The organization to generate keys for")
|
||||||
parser.add_argument("--token-path", metavar='PATH', required=True, help="Path to a file containing the Gitea API token")
|
parser.add_argument("--token-path", metavar='PATH', required=True, type=Path, help="Path to a file containing the Gitea API token")
|
||||||
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("--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', 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', 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', help="The path to the rrsync binary", default="/run/current-system/sw/bin/rrsync")
|
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', 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()
|
||||||
|
|
||||||
|
|
||||||
def add_secret(args, token, repo, name, secret):
|
def add_secret(args: argparse.Namespace, token: str, repo: str, name: str, secret: str):
|
||||||
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, token, repo, name, secret):
|
||||||
raise Exception(f"Failed to add secret: {result.json()}")
|
raise Exception(f"Failed to add secret: {result.json()}")
|
||||||
|
|
||||||
|
|
||||||
def get_org_repo_list(args, token):
|
def get_org_repo_list(args: argparse.Namespace, token: str):
|
||||||
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, token):
|
||||||
return [repo["name"] for repo in result.json()]
|
return [repo["name"] for repo in result.json()]
|
||||||
|
|
||||||
|
|
||||||
def generate_ssh_key(args, repository: str):
|
def generate_ssh_key(args: argparse.Namespace, 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 os.path.exists(os.path.join(args.key_dir, keyname)) or args.force:
|
if not key_path.is_file() or args.force:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[
|
[
|
||||||
"ssh-keygen",
|
"ssh-keygen",
|
||||||
*("-t", "ed25519"),
|
*("-t", "ed25519"),
|
||||||
*("-b", "4096"),
|
*("-b", "4096"),
|
||||||
*("-f", os.path.join(args.key_dir, keyname)),
|
*("-f", key_path),
|
||||||
*("-N", ""),
|
*("-N", ""),
|
||||||
*("-C", f"{args.org}/{repository}"),
|
*("-C", f"{args.org}/{repository}"),
|
||||||
],
|
],
|
||||||
|
@ -60,24 +60,33 @@ def generate_ssh_key(args, repository: str):
|
||||||
)
|
)
|
||||||
print(f"Generated SSH key for `{args.org}/{repository}`")
|
print(f"Generated SSH key for `{args.org}/{repository}`")
|
||||||
|
|
||||||
with open(os.path.join(args.key_dir, keyname), "r") as f:
|
with open(key_path, "r") as f:
|
||||||
private_key = f.read()
|
private_key = f.read()
|
||||||
|
|
||||||
with open(os.path.join(args.key_dir, keyname + ".pub"), "r") as f:
|
pub_key_path = args.key_dir / (keyname + '.pub')
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def generate_authorized_keys(args, repo_public_keys: list[tuple[str, str]]):
|
SSH_OPTS = ",".join([
|
||||||
result = ""
|
"restrict",
|
||||||
|
"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:
|
||||||
result += f"""
|
command = f"{args.rrsync_path} -wo {args.web_dir}/{args.org}/{repo}"
|
||||||
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}
|
lines.append(f'command="{command}",{SSH_OPTS} {public_key}')
|
||||||
""".strip() + "\n"
|
|
||||||
|
|
||||||
with open(args.authorized_keys_path, "w") as f:
|
with open(args.authorized_keys_path, "w") as f:
|
||||||
f.write(result)
|
f.writelines(lines)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -87,7 +96,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(Path(args.authorized_keys_path).parent, 0o700, exist_ok=True)
|
os.makedirs(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}`')
|
||||||
|
|
Loading…
Reference in New Issue