#!/usr/bin/env python3 """Zscaler Private Access (ZPA) ZTNA audit agent using ZPA API.""" import json import sys import argparse from datetime import datetime try: import requests except ImportError: print("Install: pip install requests") sys.exit(1) class ZPAClient: """Zscaler Private Access API client.""" def __init__(self, client_id, client_secret, customer_id): self.base_url = "https://config.private.zscaler.com" self.customer_id = customer_id self.token = self._authenticate(client_id, client_secret) def _authenticate(self, client_id, client_secret): resp = requests.post(f"{self.base_url}/signin", json={ "client_id": client_id, "client_secret": client_secret, }, timeout=30) resp.raise_for_status() return resp.json()["token"] def _get(self, endpoint): resp = requests.get(f"{self.base_url}/mgmtconfig/v1/admin/customers/{self.customer_id}/{endpoint}", headers={"Authorization": f"Bearer {self.token}"}, timeout=30) resp.raise_for_status() return resp.json() def list_app_segments(self): return self._get("application") def list_server_groups(self): return self._get("serverGroup") def list_app_connectors(self): return self._get("connector") def list_access_policies(self): return self._get("policySet/rules") def list_segment_groups(self): return self._get("segmentGroup") def audit_zpa_config(client): """Audit ZPA configuration for security posture.""" findings = [] apps = client.list_app_segments() for app in apps.get("list", []): if not app.get("enabled"): findings.append({ "type": "disabled_app_segment", "name": app.get("name", ""), "severity": "LOW", }) if app.get("bypassType") == "ALWAYS": findings.append({ "type": "bypass_enabled", "name": app.get("name", ""), "severity": "HIGH", "recommendation": "Remove bypass to enforce ZPA inspection", }) connectors = client.list_app_connectors() for conn in connectors.get("list", []): if conn.get("runtimeStatus") != "running": findings.append({ "type": "connector_down", "name": conn.get("name", ""), "status": conn.get("runtimeStatus", ""), "severity": "CRITICAL", }) return findings def run_audit(client_id, client_secret, customer_id): """Execute ZPA ZTNA audit.""" client = ZPAClient(client_id, client_secret, customer_id) print(f"\n{'='*60}") print(f" ZSCALER PRIVATE ACCESS ZTNA AUDIT") print(f" Generated: {datetime.utcnow().isoformat()} UTC") print(f"{'='*60}\n") apps = client.list_app_segments() app_list = apps.get("list", []) print(f"--- APPLICATION SEGMENTS ({len(app_list)}) ---") for a in app_list[:10]: print(f" {a.get('name', '')}: enabled={a.get('enabled', '')} bypass={a.get('bypassType', '')}") connectors = client.list_app_connectors() conn_list = connectors.get("list", []) print(f"\n--- APP CONNECTORS ({len(conn_list)}) ---") for c in conn_list[:10]: print(f" {c.get('name', '')}: {c.get('runtimeStatus', '')}") findings = audit_zpa_config(client) print(f"\n--- AUDIT FINDINGS ({len(findings)}) ---") for f in findings: print(f" [{f['severity']}] {f['type']}: {f.get('name', '')}") return {"apps": len(app_list), "connectors": len(conn_list), "findings": findings} def main(): parser = argparse.ArgumentParser(description="ZPA ZTNA Audit Agent") parser.add_argument("--client-id", required=True, help="ZPA API client ID") parser.add_argument("--client-secret", required=True, help="ZPA API client secret") parser.add_argument("--customer-id", required=True, help="ZPA customer ID") parser.add_argument("--output", help="Save report to JSON file") args = parser.parse_args() report = run_audit(args.client_id, args.client_secret, args.customer_id) if args.output: with open(args.output, "w") as f: json.dump(report, f, indent=2, default=str) print(f"\n[+] Report saved to {args.output}") if __name__ == "__main__": main()