75 lines
2.7 KiB
Python
75 lines
2.7 KiB
Python
"""Publish client links for every external node, using its public DNS name."""
|
|||
|
|
|
||
|
|
import base64
|
||
|
|
import json
|
||
|
|
import urllib.error
|
||
|
|
import urllib.parse
|
||
|
|
import urllib.request
|
||
|
|
|
||
|
|
|
||
|
|
API = "https://kubernetes.default.svc"
|
||
|
|
NAMESPACE = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", encoding="utf-8").read().strip()
|
||
|
|
TOKEN = open("/var/run/secrets/kubernetes.io/serviceaccount/token", encoding="utf-8").read().strip()
|
||
|
|
CA = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
||
|
|
|
||
|
|
|
||
|
|
def request(method, path, body=None):
|
||
|
|
import ssl
|
||
|
|
|
||
|
|
data = json.dumps(body).encode() if body is not None else None
|
||
|
|
req = urllib.request.Request(
|
||
|
|
API + path,
|
||
|
|
data=data,
|
||
|
|
method=method,
|
||
|
|
headers={
|
||
|
|
"Authorization": "Bearer " + TOKEN,
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
with urllib.request.urlopen(req, context=ssl.create_default_context(cafile=CA), timeout=15) as response:
|
||
|
|
return json.load(response)
|
||
|
|
|
||
|
|
|
||
|
|
def publish(name, links):
|
||
|
|
path = f"/api/v1/namespaces/{NAMESPACE}/secrets"
|
||
|
|
data = {node: base64.b64encode(link.encode()).decode() for node, link in links.items()}
|
||
|
|
try:
|
||
|
|
current = request("GET", f"{path}/{name}")
|
||
|
|
except urllib.error.HTTPError as error:
|
||
|
|
if error.code != 404:
|
||
|
|
raise
|
||
|
|
request("POST", path, {"apiVersion": "v1", "kind": "Secret", "metadata": {"name": name}, "type": "Opaque", "data": data})
|
||
|
|
return
|
||
|
|
if current.get("data", {}) == data:
|
||
|
|
return
|
||
|
|
# PUT replaces the data map, removing links for nodes that are no longer external.
|
||
|
|
request("PUT", f"{path}/{name}", {
|
||
|
|
"apiVersion": "v1",
|
||
|
|
"kind": "Secret",
|
||
|
|
"metadata": {"name": name, "resourceVersion": current["metadata"]["resourceVersion"]},
|
||
|
|
"type": "Opaque",
|
||
|
|
"data": data,
|
||
|
|
})
|
||
|
|
|
||
|
|
|
||
|
|
secret = open("/config/SECRET", encoding="utf-8").read().strip()
|
||
|
|
if len(secret) != 32 or any(char not in "0123456789abcdefABCDEF" for char in secret):
|
||
|
|
raise ValueError("Vaultwarden Secret must be 32 hexadecimal characters")
|
||
|
|
|
||
|
|
selector = urllib.parse.quote("homelab.hexor.cy/exposure=external", safe="")
|
||
|
|
nodes = request("GET", f"/api/v1/nodes?labelSelector={selector}")["items"]
|
||
|
|
if not nodes:
|
||
|
|
raise ValueError("No external nodes found")
|
||
|
|
|
||
|
|
classic = {}
|
||
|
|
tls = {}
|
||
|
|
for node in nodes:
|
||
|
|
name = node["metadata"]["name"]
|
||
|
|
# DNS is maintained outside the cluster; no public IP needs to be stored on a Node.
|
||
|
|
host = f"{name}.hexor.cy"
|
||
|
|
classic[name] = f"tg://proxy?server={host}&port=30443&secret={secret}"
|
||
|
|
tls[name] = f"tg://proxy?server={host}&port=30444&secret=ee{secret}79612e7275"
|
||
|
|
|
||
|
|
publish("mtproxy-links", classic)
|
||
|
|
publish("telemt-links", tls)
|
||
|
|
print(f"Published MTProxy links for {len(nodes)} external nodes")
|