mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 20:52:51 +00:00
2fb4bf9eee
Use a dedicated Auth Keys write client restricted to tag:ci-deploy, never the broader demo registration. Reject missing configuration and validate the actual running Self tag and DNS before Serve. Keep exact-source, denial-stop, no-replay and cleanup contracts, with connected offline registration and Serve controls. Change-source: pulse-maintainer
48 lines
1.5 KiB
Python
48 lines
1.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate only the joined CI node's identity before exposing its loopback UI.
|
|
|
|
Reads local `tailscale status --json`; makes no network request and never emits
|
|
peer inventory, raw status or credentials. OAuth/ACL scope is configured by the
|
|
Tailnet owner, not inferred from a DNS name or a tag observation.
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
MAX_STATUS_BYTES = 1024 * 1024
|
|
ERROR = "Release browser requires a running Self node with only tag:ci-deploy and a business-tailnet DNS name"
|
|
|
|
|
|
def self_origin(status):
|
|
if not isinstance(status, dict) or status.get("BackendState") != "Running":
|
|
raise ValueError(ERROR)
|
|
node = status.get("Self")
|
|
if (not isinstance(node, dict) or node.get("Online") is not True
|
|
or node.get("Tags") != ["tag:ci-deploy"]):
|
|
raise ValueError(ERROR)
|
|
name = node.get("DNSName")
|
|
if not isinstance(name, str):
|
|
raise ValueError(ERROR)
|
|
name = name.removesuffix(".")
|
|
if not re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.tawny-powan\.ts\.net", name):
|
|
raise ValueError(ERROR)
|
|
return "https://" + name
|
|
|
|
|
|
def main():
|
|
try:
|
|
raw = sys.stdin.buffer.read(MAX_STATUS_BYTES + 1)
|
|
if len(raw) > MAX_STATUS_BYTES:
|
|
raise ValueError(ERROR)
|
|
origin = self_origin(json.loads(raw))
|
|
except (ValueError, UnicodeError):
|
|
print(ERROR, file=sys.stderr)
|
|
return 2
|
|
print(origin)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|