#!/usr/bin/env python3
"""Run inside the guest. IMDS tokens stay in memory; output is PRIVATE evidence."""
import base64
import datetime
import http.client
import json
import socket
import sys
import time
import urllib.parse
import urllib.request
import urllib.error


def token(client_id, audience):
    query = urllib.parse.urlencode({"api-version": "2018-02-01", "resource": audience, "client_id": client_id})
    req = urllib.request.Request("http://169.254.169.254/metadata/identity/oauth2/token?" + query, headers={"Metadata": "true"})
    # Bypass any machine proxy for the link-local managed identity endpoint.
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
    for attempt in range(5):
        try:
            with opener.open(req, timeout=15) as response:
                return json.load(response)["access_token"]
        except urllib.error.HTTPError as exc:
            if exc.code not in (404, 410, 429, 500, 502, 503, 504) or attempt == 4:
                raise RuntimeError("IMDS token acquisition failed; status=" + str(exc.code)) from None
            time.sleep(min(15, 2 ** attempt))
    raise RuntimeError("IMDS retries exhausted")


def request(url, bearer, payload):
    parts = urllib.parse.urlsplit(url)
    conn = http.client.HTTPSConnection(parts.hostname, timeout=25)
    result = {"status": 0, "peer": None, "body": None}
    try:
        conn.connect()
        result["peer"] = conn.sock.getpeername()[0]
        conn.request("POST", parts.path + ("?" + parts.query if parts.query else ""),
                     body=json.dumps(payload).encode(),
                     headers={"Authorization": "Bearer " + bearer, "Content-Type": "application/json"})
        response = conn.getresponse()
        result["status"] = response.status
        raw = response.read(131072).decode("utf-8", "replace")
        try:
            body = json.loads(raw) if raw else None
        except ValueError:
            body = raw[:1600]
        result["body"] = body
        result["retryAfter"] = response.getheader("Retry-After")
    except (OSError, http.client.HTTPException) as exc:
        result["transportError"] = type(exc).__name__
    finally:
        conn.close()
    return result


def main(config):
    now = datetime.datetime.now(datetime.timezone.utc).isoformat()
    result = {"source": config["source"], "stage": config["stage"], "time": now, "dns": {}}
    for host in config["dnsHosts"]:
        try:
            result["dns"][host] = sorted(set(x[4][0] for x in socket.getaddrinfo(host, 443, family=socket.AF_INET)))
        except OSError:
            result["dns"][host] = []
    if config.get("send", True):
        ingestion_token = token(config["clientId"], "https://monitor.azure.com/")
        result["ingestion"] = request(config["ingestionUrl"], ingestion_token, [{
            "TimeGenerated": now, "RunKey": config["runKey"], "Stage": config["stage"],
            "Source": config["source"], "Marker": config["marker"]}])
        del ingestion_token
    query_token = token(config["clientId"], "https://api.loganalytics.io")
    result["query"] = request(config["queryUrl"], query_token, {"query": config["query"], "timespan": "PT2H"})
    del query_token
    print("AMPLS_RESULT=" + json.dumps(result, separators=(",", ":")))


if __name__ == "__main__":
    main(json.loads(base64.b64decode(sys.argv[1])))
