mirror of
				https://git.pvv.ntnu.no/Drift/pvv-nixos-config.git
				synced 2025-11-04 02:58:02 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			74a2b1970e
			...
			ce2f6a4546
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ce2f6a4546 | ||
| 
						 | 
					ed13e49ba7 | 
@ -6,7 +6,8 @@ let
 | 
				
			|||||||
in {
 | 
					in {
 | 
				
			||||||
  imports = [
 | 
					  imports = [
 | 
				
			||||||
    ./ci.nix
 | 
					    ./ci.nix
 | 
				
			||||||
    ./import-users.nix
 | 
					    ./import-users
 | 
				
			||||||
 | 
					    ./web-secret-provider
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sops.secrets = {
 | 
					  sops.secrets = {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										153
									
								
								hosts/bekkalokk/services/gitea/web-secret-provider/default.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								hosts/bekkalokk/services/gitea/web-secret-provider/default.nix
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					{ config, pkgs, lib, ... }:
 | 
				
			||||||
 | 
					let
 | 
				
			||||||
 | 
					  organizations = [
 | 
				
			||||||
 | 
					    "Drift"
 | 
				
			||||||
 | 
					    "Projects"
 | 
				
			||||||
 | 
					    "Kurs"
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cfg = config.services.gitea;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  program = pkgs.writers.writePython3 "gitea-web-secret-provider" {
 | 
				
			||||||
 | 
					    libraries = with pkgs.python3Packages; [ requests ];
 | 
				
			||||||
 | 
					    flakeIgnore = [
 | 
				
			||||||
 | 
					      "E501" # Line over 80 chars lol
 | 
				
			||||||
 | 
					      "E201" # "whitespace after {"
 | 
				
			||||||
 | 
					      "E202" # "whitespace after }"
 | 
				
			||||||
 | 
					      "E251" # unexpected spaces around keyword / parameter equals
 | 
				
			||||||
 | 
					      "W391" # Newline at end of file
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    makeWrapperArgs = [
 | 
				
			||||||
 | 
					      "--prefix PATH : ${(lib.makeBinPath [ pkgs.openssh ])}"
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					  } (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
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  sops.secrets."gitea/web-secret-provider/token" = {
 | 
				
			||||||
 | 
					    owner = "gitea";
 | 
				
			||||||
 | 
					    group = "gitea";
 | 
				
			||||||
 | 
					    restartUnits = [
 | 
				
			||||||
 | 
					      "gitea-web-secret-provider@"
 | 
				
			||||||
 | 
					    ] ++ (map (org: "gitea-web-secret-provider@${org}") organizations);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  systemd.tmpfiles.settings."10-gitea-web-secret-provider"."/var/lib/gitea-web/authorized_keys.d".d = {
 | 
				
			||||||
 | 
					    user = "gitea";
 | 
				
			||||||
 | 
					    group = "gitea";
 | 
				
			||||||
 | 
					    mode = "700";
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  systemd.slices.system-giteaweb = {
 | 
				
			||||||
 | 
					    description = "Gitea web directories";
 | 
				
			||||||
 | 
					    wantedBy = [ "multi-user.target" ];
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers
 | 
				
			||||||
 | 
					  # %i - instance name (after the @)
 | 
				
			||||||
 | 
					  # %d - secrets directory
 | 
				
			||||||
 | 
					  # %S - /var/lib
 | 
				
			||||||
 | 
					  systemd.services = {
 | 
				
			||||||
 | 
					    "gitea-web-secret-provider@" = {
 | 
				
			||||||
 | 
					      description = "Ensure all repos in %i has an SSH key to push web content";
 | 
				
			||||||
 | 
					      requires = [ "gitea.service" "network.target" ];
 | 
				
			||||||
 | 
					      serviceConfig = {
 | 
				
			||||||
 | 
					        Slice = "system-giteaweb.slice";
 | 
				
			||||||
 | 
					        Type = "oneshot";
 | 
				
			||||||
 | 
					        ExecStart = let
 | 
				
			||||||
 | 
					          args = lib.cli.toGNUCommandLineShell { } {
 | 
				
			||||||
 | 
					            org = "%i";
 | 
				
			||||||
 | 
					            token-path = "%d/token";
 | 
				
			||||||
 | 
					            api-url = "${cfg.settings.server.ROOT_URL}api/v1";
 | 
				
			||||||
 | 
					            key-dir = "%S/%i/keys";
 | 
				
			||||||
 | 
					            authorized-keys-path = "%S/gitea-web/authorized_keys.d/%i";
 | 
				
			||||||
 | 
					            rrsync-path = "${pkgs.rrsync}/bin/rrsync";
 | 
				
			||||||
 | 
					            web-dir = "%S/gitea-web/web";
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        in "${program} ${args}";
 | 
				
			||||||
 | 
					        User = "gitea";
 | 
				
			||||||
 | 
					        Group = "gitea";
 | 
				
			||||||
 | 
					        StateDirectory = "%i";
 | 
				
			||||||
 | 
					        LoadCredential = [
 | 
				
			||||||
 | 
					          "token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					      } // commonHardening;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "gitea-web-chown@" = {
 | 
				
			||||||
 | 
					      description = "Ensure all gitea-web content is owned by the gitea user";
 | 
				
			||||||
 | 
					      serviceConfig = {
 | 
				
			||||||
 | 
					        Slice = "system-giteaweb.slice";
 | 
				
			||||||
 | 
					        Type = "oneshot";
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					      } // commonHardening;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  systemd.timers = {
 | 
				
			||||||
 | 
					    "gitea-web-secret-provider@" = {
 | 
				
			||||||
 | 
					      description = "Ensure all repos in %i has an SSH key to push web content";
 | 
				
			||||||
 | 
					      timerConfig = {
 | 
				
			||||||
 | 
					        RandomizedDelaySec = "1h";
 | 
				
			||||||
 | 
					        Persistent = true;
 | 
				
			||||||
 | 
					        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 = 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.nginx.virtualHosts."pages.pvv.ntnu.no" = {
 | 
				
			||||||
 | 
					    kTLS = true;
 | 
				
			||||||
 | 
					    forceSSL = true;
 | 
				
			||||||
 | 
					    enableACME = true;
 | 
				
			||||||
 | 
					    root = "/var/lib/gitea-web/web";
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env nix-shell
 | 
				
			||||||
 | 
					#!nix-shell -i python3 -p "python3.withPackages(ps: with ps; [ requests ])" openssh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse_args():
 | 
				
			||||||
 | 
					    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("--token-path", metavar='PATH', required=True, 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("--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', 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("--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")
 | 
				
			||||||
 | 
					    return parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_secret(args, token, repo, name, secret):
 | 
				
			||||||
 | 
					    result = requests.put(
 | 
				
			||||||
 | 
					        f"{args.api_url}/repos/{args.org}/{repo}/actions/secrets/{name}",
 | 
				
			||||||
 | 
					        json = { 'data': secret },
 | 
				
			||||||
 | 
					        headers = { 'Authorization': 'token ' + token },
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    if result.status_code not in (201, 204):
 | 
				
			||||||
 | 
					        raise Exception(f"Failed to add secret: {result.json()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_org_repo_list(args, token):
 | 
				
			||||||
 | 
					    result = requests.get(
 | 
				
			||||||
 | 
					        f"{args.api_url}/orgs/{args.org}/repos",
 | 
				
			||||||
 | 
					        headers = { 'Authorization': 'token ' + token },
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    return [repo["name"] for repo in result.json()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_ssh_key(args, repository: str):
 | 
				
			||||||
 | 
					    keyname = hashlib.sha256(args.org.encode() + repository.encode()).hexdigest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not os.path.exists(os.path.join(args.key_dir, keyname)) or args.force:
 | 
				
			||||||
 | 
					        subprocess.run(
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                "ssh-keygen",
 | 
				
			||||||
 | 
					                *("-t", "ed25519"),
 | 
				
			||||||
 | 
					                *("-b", "4096"),
 | 
				
			||||||
 | 
					                *("-f", os.path.join(args.key_dir, keyname)),
 | 
				
			||||||
 | 
					                *("-N", ""),
 | 
				
			||||||
 | 
					                *("-C", f"{args.org}/{repository}"),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            check=True,
 | 
				
			||||||
 | 
					            stdin=subprocess.DEVNULL,
 | 
				
			||||||
 | 
					            stdout=subprocess.DEVNULL,
 | 
				
			||||||
 | 
					            stderr=subprocess.DEVNULL
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        print(f"Generated SSH key for `{args.org}/{repository}`")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(os.path.join(args.key_dir, keyname), "r") as f:
 | 
				
			||||||
 | 
					        private_key = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(os.path.join(args.key_dir, keyname + ".pub"), "r") as f:
 | 
				
			||||||
 | 
					        public_key = f.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return private_key, public_key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_authorized_keys(args, repo_public_keys: list[tuple[str, str]]):
 | 
				
			||||||
 | 
					    result = ""
 | 
				
			||||||
 | 
					    for repo, public_key in repo_public_keys:
 | 
				
			||||||
 | 
					        result += f"""
 | 
				
			||||||
 | 
					            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:
 | 
				
			||||||
 | 
					        f.write(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    args = parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(args.token_path, "r") as f:
 | 
				
			||||||
 | 
					        token = f.read().strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    os.makedirs(args.key_dir, 0o700, exist_ok=True)
 | 
				
			||||||
 | 
					    os.makedirs(Path(args.authorized_keys_path).parent, 0o700, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    repos = get_org_repo_list(args, token)
 | 
				
			||||||
 | 
					    print(f'Found {len(repos)} repositories in `{args.org}`')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    repo_public_keys = []
 | 
				
			||||||
 | 
					    for repo in repos:
 | 
				
			||||||
 | 
					        print(f"Locating key for `{args.org}/{repo}`")
 | 
				
			||||||
 | 
					        private_key, public_key = generate_ssh_key(args, repo)
 | 
				
			||||||
 | 
					        add_secret(args, token, repo, "WEB_SYNC_SSH_KEY", private_key)
 | 
				
			||||||
 | 
					        repo_public_keys.append((repo, public_key))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    generate_authorized_keys(args, repo_public_keys)
 | 
				
			||||||
 | 
					    print(f"Wrote authorized_keys file to `{args.authorized_keys_path}`")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
@ -1,4 +1,6 @@
 | 
				
			|||||||
gitea:
 | 
					gitea:
 | 
				
			||||||
 | 
					    web-secret-provider:
 | 
				
			||||||
 | 
					        token: ENC[AES256_GCM,data:pHmBKxrNcLifl4sjR44AGEElfdachja35Tl/InsqvBWturaeTv4R0w==,iv:emBWfXQs2VNqtpDp5iA5swNC+24AWDYYXo6nvN+Fwx4=,tag:lkhSVSs6IqhHpfDPOX0wQA==,type:str]
 | 
				
			||||||
    password: ENC[AES256_GCM,data:hlNzdU1ope0t50/3aztyLeXjMHd2vFPpwURX+Iu8f49DOqgSnEMtV+KtLA==,iv:qljRnSnchL5cFmaUAfCH9GQYQxcy5cyWejgk1x6bFgI=,tag:tIhboFU5kZsj5oAQR3hLbw==,type:str]
 | 
					    password: ENC[AES256_GCM,data:hlNzdU1ope0t50/3aztyLeXjMHd2vFPpwURX+Iu8f49DOqgSnEMtV+KtLA==,iv:qljRnSnchL5cFmaUAfCH9GQYQxcy5cyWejgk1x6bFgI=,tag:tIhboFU5kZsj5oAQR3hLbw==,type:str]
 | 
				
			||||||
    database: ENC[AES256_GCM,data:UlS33IdCEyeSvT6ngpmnkBWHuSEqsB//DT+3b7C+UwbD8UXWJlsLf1X8/w==,iv:mPRW5ldyZaHP+y/0vC2JGSLZmlkhgmkvXPk4LazkSDs=,tag:gGk6Z/nbPvzE1zG+tJC8Sw==,type:str]
 | 
					    database: ENC[AES256_GCM,data:UlS33IdCEyeSvT6ngpmnkBWHuSEqsB//DT+3b7C+UwbD8UXWJlsLf1X8/w==,iv:mPRW5ldyZaHP+y/0vC2JGSLZmlkhgmkvXPk4LazkSDs=,tag:gGk6Z/nbPvzE1zG+tJC8Sw==,type:str]
 | 
				
			||||||
    email-password: ENC[AES256_GCM,data:KRwC+aL1aPvJuXt91Oq1ttATMnFTnuUy,iv:ats8TygB/2pORkaTZzPOLufZ9UmvVAKoRcWNvYF1z6w=,tag:Do0fA+4cZ3+l7JJyu8hjBg==,type:str]
 | 
					    email-password: ENC[AES256_GCM,data:KRwC+aL1aPvJuXt91Oq1ttATMnFTnuUy,iv:ats8TygB/2pORkaTZzPOLufZ9UmvVAKoRcWNvYF1z6w=,tag:Do0fA+4cZ3+l7JJyu8hjBg==,type:str]
 | 
				
			||||||
@ -90,8 +92,8 @@ sops:
 | 
				
			|||||||
            UHpLRkdQTnhkeGlWVG9VS1hkWktyckEKAdwnA9URLYZ50lMtXrU9Q09d0L3Zfsyr
 | 
					            UHpLRkdQTnhkeGlWVG9VS1hkWktyckEKAdwnA9URLYZ50lMtXrU9Q09d0L3Zfsyr
 | 
				
			||||||
            4UsvjjdnFtsXwEZ9ZzOQrpiN0Oz24s3csw5KckDni6kslaloJZsLGg==
 | 
					            4UsvjjdnFtsXwEZ9ZzOQrpiN0Oz24s3csw5KckDni6kslaloJZsLGg==
 | 
				
			||||||
            -----END AGE ENCRYPTED FILE-----
 | 
					            -----END AGE ENCRYPTED FILE-----
 | 
				
			||||||
    lastmodified: "2024-05-26T02:07:41Z"
 | 
					    lastmodified: "2024-08-13T19:49:24Z"
 | 
				
			||||||
    mac: ENC[AES256_GCM,data:CRaJefV1zcJc6eyzyjTLgd0+Wv46VT8o4iz2YAGU+c2b/Cr97Tj290LoEO6UXTI3uFwVfzii2yZ2l+4FK3nVVriD4Cx1O/9qWcnLa5gfK30U0zof6AsJx8qtGu1t6oiPlGUCF7sT0BW9Wp8cPumrY6cZp9QbhmIDV0o0aJNUNN4=,iv:8OSYV1eG6kYlJD4ovZZhcD1GaYnmy7vHPa/+7egM1nE=,tag:OPI13rpDh2l1ViFj8TBFWg==,type:str]
 | 
					    mac: ENC[AES256_GCM,data:AeJ53D+8A8mHYRmVHdqhcS1ZTbqVe5gQqJsJjMk4T/ZlNX8/V4M9mqAW2FB9m/JSdj234gDu+PBHcW70ZrCqeVsoUW/ETVgUX3W2gBmBgYJiRETp8I7/eks/5YEV6vIIxQsZNP/9dZTNX4T2wD74ELl23NSTXA/6k2tyzBlTMYo=,iv:DABafHvw+5w0PHCKqLgpwmQnv0uHOTyj+s8gdnHFTZ4=,tag:SNZ7W+6zdyuuv2AB9ir8eg==,type:str]
 | 
				
			||||||
    pgp:
 | 
					    pgp:
 | 
				
			||||||
        - created_at: "2024-08-04T00:03:28Z"
 | 
					        - created_at: "2024-08-04T00:03:28Z"
 | 
				
			||||||
          enc: |-
 | 
					          enc: |-
 | 
				
			||||||
@ -114,4 +116,4 @@ sops:
 | 
				
			|||||||
            -----END PGP MESSAGE-----
 | 
					            -----END PGP MESSAGE-----
 | 
				
			||||||
          fp: F7D37890228A907440E1FD4846B9228E814A2AAC
 | 
					          fp: F7D37890228A907440E1FD4846B9228E814A2AAC
 | 
				
			||||||
    unencrypted_suffix: _unencrypted
 | 
					    unencrypted_suffix: _unencrypted
 | 
				
			||||||
    version: 3.8.1
 | 
					    version: 3.9.0
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user