net-kingdom/sso-mfa/k8s/privacyidea/pi_api.py
tegwick 30f0d27840
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
security: preserve realm configuration and remove shell credential evaluation
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 22:47:24 +02:00

68 lines
3 KiB
Python

"""Captured JSON transport for protected callers; never run against live data to stdout."""
import json
import ssl
import sys
import urllib.error
import urllib.parse
import urllib.request
from mfa_state import NoRedirect, endpoint, unique_object
MAX_BODY = 1024 * 1024
def request(url, payload=None, token=None, *, method=None):
try:
parsed = urllib.parse.urlsplit(url)
base = endpoint(urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, '', '', '')))
if parsed.fragment or not parsed.path.startswith('/') or any(ord(c) < 32 for c in url):
return 0, None
# Origin validated before creating a request containing protected values.
headers = {'Accept': 'application/json'}
data = None
if payload is not None:
data = json.dumps(payload).encode('utf-8')
if len(data) > MAX_BODY: return 0, None
headers['Content-Type'] = 'application/json'
if token:
if not isinstance(token, str) or len(token) > 16384 or any(ord(c) < 33 or ord(c) > 126 for c in token):
return 0, None
headers['Authorization'] = token
req = urllib.request.Request(base + parsed.path + ('?' + parsed.query if parsed.query else ''),
data=data, headers=headers, method=method or ('POST' if payload is not None else 'GET'))
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(cafile=ssl.get_default_verify_paths().openssl_cafile)
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect(),
urllib.request.HTTPSHandler(context=context))
with opener.open(req, timeout=20) as response:
status, raw = response.status, response.read(MAX_BODY + 1)
if len(raw) > MAX_BODY: return status, None
return status, json.loads(raw, object_pairs_hook=unique_object) if raw else None
except urllib.error.HTTPError as exc:
exc.close()
return exc.code, None
except Exception:
# Callers emit fixed phases, never exception strings or response bodies.
return 0, None
def main():
try:
if len(sys.argv) != 3 or sys.argv[1] not in ('GET', 'POST'):
raise ValueError()
token = sys.stdin.readline(16386).removesuffix('\n')
body = sys.stdin.read(MAX_BODY + 1)
if len(body) > MAX_BODY: raise ValueError()
status, result = request(sys.argv[2], json.loads(body, object_pairs_hook=unique_object) if body else None,
token or None, method=sys.argv[1])
if not 200 <= status < 300 or not isinstance(result, dict): raise ValueError()
# Private shell adapter: caller must capture this; it can contain a JWT/config.
print(json.dumps(result))
return 0
except Exception:
print('CURL_FAILED')
return 1
if __name__ == '__main__':
raise SystemExit(main())