#!/usr/bin/env python3
"""EU-host TokenRequest/TokenReview preflight; no token files or token output."""
import argparse
import base64
import json
import ssl
import subprocess
import sys
import urllib.error
import urllib.request
from pathlib import Path

AUDIENCE = "cloudtaser.io/secret-envelope"


def run(kubeconfig):
    def kubectl(*args):
        return subprocess.check_output(
            ["kubectl", "--kubeconfig", kubeconfig, *args], text=True
        ).strip()

    config = json.loads(kubectl("config", "view", "--minify", "--flatten", "--raw", "-o", "json"))
    cluster = config["clusters"][0]["cluster"]
    server = cluster["server"].rstrip("/")
    if not server.startswith("https://") or cluster.get("insecure-skip-tls-verify"):
        raise ValueError("verified HTTPS API required")
    if cluster.get("certificate-authority-data"):
        context = ssl.create_default_context(cadata=base64.b64decode(
            cluster["certificate-authority-data"], validate=True).decode("ascii"))
    elif cluster.get("certificate-authority"):
        context = ssl.create_default_context(cafile=cluster["certificate-authority"])
    else:
        context = ssl.create_default_context()
    # Each invocation is captured independently: create token emits no newline.
    reviewer = kubectl("-n", "payments-checks", "create", "token", "token-reviewer", "--duration=10m")

    def request(path, data=None):
        payload = None if data is None else json.dumps(data).encode()
        req = urllib.request.Request(server + path, data=payload, headers={
            "Authorization": "Bearer " + reviewer, "Content-Type": "application/json"})
        # Do not follow redirects carrying the reviewer credential.
        class NoRedirect(urllib.request.HTTPRedirectHandler):
            def redirect_request(self, *_args, **_kwargs):
                return None
        opener = urllib.request.build_opener(
            NoRedirect(), urllib.request.HTTPSHandler(context=context))
        with opener.open(req, timeout=20) as response:
            return json.load(response)

    def token(sa, audience):
        return kubectl("-n", "payments", "create", "token", sa,
                       "--duration=10m", "--audience=" + audience)

    def review(jwt):
        return request("/apis/authentication.k8s.io/v1/tokenreviews", {
            "apiVersion": "authentication.k8s.io/v1", "kind": "TokenReview",
            "spec": {"token": jwt, "audiences": [AUDIENCE]}})["status"]

    def require(condition, message):
        if not condition:
            raise ValueError(message)

    reader = token("reader", AUDIENCE)
    reader_uid = kubectl("-n", "payments", "get", "sa", "reader", "-o", "jsonpath={.metadata.uid}")
    status = review(reader)
    require(status.get("authenticated") is True, "reader authentication failed")
    require(status.get("user", {}).get("username") == "system:serviceaccount:payments:reader",
            "reader username mismatch")
    require(status["user"].get("uid") == reader_uid and AUDIENCE in status.get("audiences", []),
            "reader UID/audience mismatch")
    require(not review(token("reader", "example.invalid/wrong")).get("authenticated"),
            "wrong audience accepted")
    head, body, signature = reader.split(".")
    signature_bytes = bytearray(base64.urlsafe_b64decode(signature + "=" * (-len(signature) % 4)))
    signature_bytes[0] ^= 1
    altered = base64.urlsafe_b64encode(signature_bytes).decode().rstrip("=")
    require(not review(head + "." + body + "." + altered).get("authenticated"),
            "altered signature accepted")
    other = review(token("other-reader", AUDIENCE))
    other_uid = kubectl("-n", "payments", "get", "sa", "other-reader", "-o", "jsonpath={.metadata.uid}")
    require(other.get("authenticated") is True and
            other.get("user", {}).get("username") == "system:serviceaccount:payments:other-reader" and
            other["user"].get("uid") == other_uid and other_uid != reader_uid and
            AUDIENCE in other.get("audiences", []), "other-SA identity check failed")
    try:
        request("/api/v1/namespaces/kube-system/secrets")
    except urllib.error.HTTPError as error:
        require(error.code == 403, "reviewer negative scope check did not return 403")
    else:
        raise ValueError("reviewer unexpectedly read Secrets")
    print("TokenReview passed: reader identity/audience, wrong audience, altered signature, other SA; reviewer Secrets denied")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--kubeconfig", required=True)
    args = parser.parse_args()
    try:
        run(str(Path(args.kubeconfig).resolve(strict=True)))
    except (OSError, ValueError, KeyError, subprocess.SubprocessError):
        # Avoid credential-bearing subprocess/HTTP response bodies in diagnostics.
        sys.exit("TokenReview gate failed; inspect API connectivity, TLS, RBAC and identities privately")
