From 9a221f1a10f3de26e682a088097ad2b7b41cc465 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Tue, 11 Mar 2025 22:28:50 -0400 Subject: [PATCH] feat: improve makejinja functions and cloudflared deployment Signed-off-by: Devin Buhl --- templates/config/.sops.yaml.j2 | 4 +- .../external/cloudflared/dnsendpoint.yaml.j2 | 2 +- .../external/cloudflared/helmrelease.yaml.j2 | 16 +-- .../external/cloudflared/secret.sops.yaml.j2 | 8 +- .../components/common/sops-age.sops.yaml.j2 | 2 +- templates/scripts/plugin.py | 121 +++++++++++------- 6 files changed, 86 insertions(+), 67 deletions(-) diff --git a/templates/config/.sops.yaml.j2 b/templates/config/.sops.yaml.j2 index db6482adc51..bf44c2ca745 100644 --- a/templates/config/.sops.yaml.j2 +++ b/templates/config/.sops.yaml.j2 @@ -2,11 +2,11 @@ creation_rules: - path_regex: talos/.*\.sops\.ya?ml mac_only_encrypted: true - age: "#{ age_public_key() }#" + age: "#{ age_key('public') }#" - path_regex: (bootstrap|kubernetes)/.*\.sops\.ya?ml encrypted_regex: "^(data|stringData)$" mac_only_encrypted: true - age: "#{ age_public_key() }#" + age: "#{ age_key('public') }#" stores: yaml: indent: 2 diff --git a/templates/config/kubernetes/apps/network/external/cloudflared/dnsendpoint.yaml.j2 b/templates/config/kubernetes/apps/network/external/cloudflared/dnsendpoint.yaml.j2 index 71e95c6580f..a58bdda2d14 100644 --- a/templates/config/kubernetes/apps/network/external/cloudflared/dnsendpoint.yaml.j2 +++ b/templates/config/kubernetes/apps/network/external/cloudflared/dnsendpoint.yaml.j2 @@ -8,4 +8,4 @@ spec: endpoints: - dnsName: "external.${SECRET_DOMAIN}" recordType: CNAME - targets: ["#{ cloudflare_tunnel('TunnelID') }#.cfargotunnel.com"] + targets: ["#{ cloudflare_tunnel_id() }#.cfargotunnel.com"] diff --git a/templates/config/kubernetes/apps/network/external/cloudflared/helmrelease.yaml.j2 b/templates/config/kubernetes/apps/network/external/cloudflared/helmrelease.yaml.j2 index 61ce30f8410..abcfe7c88e6 100644 --- a/templates/config/kubernetes/apps/network/external/cloudflared/helmrelease.yaml.j2 +++ b/templates/config/kubernetes/apps/network/external/cloudflared/helmrelease.yaml.j2 @@ -29,15 +29,14 @@ spec: tag: 2025.2.1 env: NO_AUTOUPDATE: true - TUNNEL_CRED_FILE: /etc/cloudflared/credentials.json TUNNEL_METRICS: 0.0.0.0:8080 TUNNEL_ORIGIN_ENABLE_HTTP2: true TUNNEL_POST_QUANTUM: true TUNNEL_TRANSPORT_PROTOCOL: quic - args: - - tunnel - - run - - #{ cloudflare_tunnel('TunnelID') }# + envFrom: + - secretRef: + name: cloudflared-secret + args: ["tunnel", "run"] probes: liveness: &probes enabled: true @@ -89,10 +88,3 @@ spec: - path: /etc/cloudflared/config.yaml subPath: config.yaml readOnly: true - creds: - type: secret - name: cloudflared-secret - globalMounts: - - path: /etc/cloudflared/credentials.json - subPath: credentials.json - readOnly: true diff --git a/templates/config/kubernetes/apps/network/external/cloudflared/secret.sops.yaml.j2 b/templates/config/kubernetes/apps/network/external/cloudflared/secret.sops.yaml.j2 index 46695e1c1e1..d4cc65c8027 100644 --- a/templates/config/kubernetes/apps/network/external/cloudflared/secret.sops.yaml.j2 +++ b/templates/config/kubernetes/apps/network/external/cloudflared/secret.sops.yaml.j2 @@ -5,9 +5,5 @@ kind: Secret metadata: name: cloudflared-secret stringData: - credentials.json: | - { - "AccountTag": "#{ cloudflare_tunnel('AccountTag') }#", - "TunnelSecret": "#{ cloudflare_tunnel('TunnelSecret') }#", - "TunnelID": "#{ cloudflare_tunnel('TunnelID') }#" - } + TUNNEL_TOKEN: | + #{ cloudflare_tunnel_secret() }# diff --git a/templates/config/kubernetes/components/common/sops-age.sops.yaml.j2 b/templates/config/kubernetes/components/common/sops-age.sops.yaml.j2 index efce449a232..522cc345792 100644 --- a/templates/config/kubernetes/components/common/sops-age.sops.yaml.j2 +++ b/templates/config/kubernetes/components/common/sops-age.sops.yaml.j2 @@ -4,4 +4,4 @@ kind: Secret metadata: name: sops-age stringData: - age.agekey: "#{ age_private_key() }#" + age.agekey: "#{ age_key('private') }#" diff --git a/templates/scripts/plugin.py b/templates/scripts/plugin.py index 0b631e71f9f..c374096b4de 100644 --- a/templates/scripts/plugin.py +++ b/templates/scripts/plugin.py @@ -2,6 +2,7 @@ from typing import Any from netaddr import IPNetwork +import base64 import makejinja import re import json @@ -24,62 +25,92 @@ def nthhost(value: str, query: int) -> str: return value -# Return the age public key from age.key -def age_public_key() -> str: +# Return the age public or private key from age.key +def age_key(key_type: str, file_path: str = 'age.key') -> str: try: - with open('age.key', 'r') as file: + with open(file_path, 'r') as file: file_content = file.read().strip() - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: age.key") from e - key_match = re.search(r"# public key: (age1[\w]+)", file_content) - if not key_match: - raise ValueError("Could not find public key in age.key") - return key_match.group(1) - - -# Return the age private key from age.key -def age_private_key() -> str: - try: - with open('age.key', 'r') as file: - file_content = file.read().strip() - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: age.key") from e - key_match = re.search(r"(AGE-SECRET-KEY-[\w]+)", file_content) - if not key_match: - raise ValueError("Could not find private key in age.key") - return key_match.group(1) + if key_type == 'public': + key_match = re.search(r"# public key: (age1[\w]+)", file_content) + if not key_match: + raise ValueError("Could not find public key in the age key file.") + return key_match.group(1) + elif key_type == 'private': + key_match = re.search(r"(AGE-SECRET-KEY-[\w]+)", file_content) + if not key_match: + raise ValueError("Could not find private key in the age key file.") + return key_match.group(1) + else: + raise ValueError("Invalid key type. Use 'public' or 'private'.") + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {file_path}") + except Exception as e: + raise RuntimeError(f"Unexpected error while processing {file_path}: {e}") # Return cloudflare tunnel fields from cloudflare-tunnel.json -def cloudflare_tunnel(value: str) -> str: +def cloudflare_tunnel_id(file_path: str = 'cloudflare-tunnel.json') -> str: try: - with open('cloudflare-tunnel.json', 'r') as file: - try: - return json.load(file).get(value) - except json.JSONDecodeError as e: - raise ValueError(f"Could not decode cloudflare-tunnel.json file") from e - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: cloudflare-tunnel.json") from e + with open(file_path, 'r') as file: + data = json.load(file) + tunnel_id = data.get("TunnelID") + if tunnel_id is None: + raise KeyError(f"Missing 'TunnelID' key in {file_path}") + return tunnel_id + + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {file_path}") + except json.JSONDecodeError: + raise ValueError(f"Could not decode JSON file: {file_path}") + except KeyError as e: + raise KeyError(f"Error in JSON structure: {e}") + except Exception as e: + raise RuntimeError(f"Unexpected error while processing {file_path}: {e}") + + +# Return cloudflare tunnel fields from cloudflare-tunnel.json in TUNNEL_TOKEN format +def cloudflare_tunnel_secret(file_path: str = 'cloudflare-tunnel.json') -> str: + try: + with open(file_path, 'r') as file: + data = json.load(file) + transformed_data = { + "a": data["AccountTag"], + "t": data["TunnelID"], + "s": data["TunnelSecret"] + } + json_string = json.dumps(transformed_data, separators=(',', ':')) + return base64.b64encode(json_string.encode('utf-8')).decode('utf-8') + + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {file_path}") + except json.JSONDecodeError: + raise ValueError(f"Could not decode JSON file: {file_path}") + except KeyError as e: + raise KeyError(f"Missing key in JSON file {file_path}: {e}") + except Exception as e: + raise RuntimeError(f"Unexpected error while processing {file_path}: {e}") # Return the GitHub deploy key from github-deploy.key -def github_deploy_key() -> str: +def github_deploy_key(file_path: str = 'github-deploy.key') -> str: try: - with open('github-deploy.key', 'r') as file: - file_content = file.read().strip() - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: github-deploy.key") from e - return file_content + with open(file_path, 'r') as file: + return file.read().strip() + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {file_path}") + except Exception as e: + raise RuntimeError(f"Unexpected error while reading {file_path}: {e}") # Return the Flux / GitHub push token from github-push-token.txt -def github_push_token() -> str: +def github_push_token(file_path: str = 'github-push-token.txt') -> str: try: - with open('github-push-token.txt', 'r') as file: - file_content = file.read().strip() - except FileNotFoundError as e: - raise FileNotFoundError(f"File not found: github-push-token.txt") from e - return file_content + with open(file_path, 'r') as file: + return file.read().strip() + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {file_path}") + except Exception as e: + raise RuntimeError(f"Unexpected error while reading {file_path}: {e}") # Return a list of files in the talos patches directory @@ -129,9 +160,9 @@ def filters(self) -> makejinja.plugin.Filters: def functions(self) -> makejinja.plugin.Functions: return [ - age_private_key, - age_public_key, - cloudflare_tunnel, + age_key, + cloudflare_tunnel_id, + cloudflare_tunnel_secret, github_deploy_key, github_push_token, talos_patches