Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

134 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Agent for auditing Zscaler Private Access (ZPA) zero trust configuration via API."""
import argparse
import json
import os
import requests
from datetime import datetime, timezone
ZPA_BASE = os.environ.get("ZPA_BASE_URL", "https://config.private.zscaler.com")
def authenticate(client_id, client_secret, customer_id):
"""Authenticate to Zscaler ZPA API."""
url = f"{ZPA_BASE}/signin"
payload = {"client_id": client_id, "client_secret": client_secret}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = requests.post(url, data=payload, headers=headers, timeout=30)
resp.raise_for_status()
token = resp.json()["access_token"]
print("[*] Authenticated to ZPA API")
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
def list_app_segments(headers, customer_id):
"""List ZPA application segments."""
url = f"{ZPA_BASE}/mgmtconfig/v1/admin/customers/{customer_id}/application"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
apps = resp.json().get("list", [])
print(f"\n[*] Application Segments: {len(apps)}")
findings = []
for app in apps:
bypass = app.get("bypassType", "NEVER")
if bypass != "NEVER":
findings.append({"name": app["name"], "bypass": bypass, "severity": "HIGH"})
print(f" {app['name']} - bypass={bypass}, enabled={app.get('enabled', False)}")
return apps, findings
def list_server_groups(headers, customer_id):
"""List server groups and their connectors."""
url = f"{ZPA_BASE}/mgmtconfig/v1/admin/customers/{customer_id}/serverGroup"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
groups = resp.json().get("list", [])
print(f"\n[*] Server Groups: {len(groups)}")
for g in groups:
connectors = g.get("connectors", [])
print(f" {g['name']} - {len(connectors)} connectors, enabled={g.get('enabled')}")
return groups
def list_access_policies(headers, customer_id):
"""List ZPA access policies to verify least-privilege enforcement."""
url = f"{ZPA_BASE}/mgmtconfig/v1/admin/customers/{customer_id}/policySet/rules"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
rules = resp.json().get("list", [])
findings = []
print(f"\n[*] Access Policy Rules: {len(rules)}")
for r in rules:
action = r.get("action", "")
conditions = r.get("conditions", [])
if action == "ALLOW" and not conditions:
findings.append({"rule": r.get("name"), "issue": "ALLOW with no conditions",
"severity": "CRITICAL"})
print(f" [!] {r.get('name')}: ALLOW without conditions")
else:
print(f" {r.get('name')}: action={action}, conditions={len(conditions)}")
return rules, findings
def check_connector_health(headers, customer_id):
"""Check connector enrollment and health status."""
url = f"{ZPA_BASE}/mgmtconfig/v1/admin/customers/{customer_id}/connector"
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
connectors = resp.json().get("list", [])
issues = []
for c in connectors:
status = c.get("currentVersion", "unknown")
enabled = c.get("enabled", False)
if not enabled:
issues.append({"connector": c.get("name"), "issue": "disabled", "severity": "MEDIUM"})
print(f" {c.get('name')}: enabled={enabled}, version={status}")
print(f"[*] Connectors: {len(connectors)} total, {len(issues)} disabled")
return connectors, issues
def generate_report(apps, app_findings, policy_findings, connector_issues, output_path):
"""Generate ZPA audit report."""
report = {
"audit_date": datetime.now(timezone.utc).isoformat(),
"summary": {"app_segments": len(apps), "bypass_findings": len(app_findings),
"policy_findings": len(policy_findings),
"connector_issues": len(connector_issues)},
"bypass_findings": app_findings, "policy_findings": policy_findings,
"connector_issues": connector_issues,
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[*] Report saved to {output_path}")
def main():
parser = argparse.ArgumentParser(description="Zscaler ZPA Zero Trust Audit Agent")
parser.add_argument("action", choices=["apps", "servers", "policies", "connectors", "full-audit"])
parser.add_argument("--client-id", required=True)
parser.add_argument("--client-secret", required=True)
parser.add_argument("--customer-id", required=True)
parser.add_argument("-o", "--output", default="zpa_audit.json")
args = parser.parse_args()
headers = authenticate(args.client_id, args.client_secret, args.customer_id)
if args.action == "apps":
list_app_segments(headers, args.customer_id)
elif args.action == "servers":
list_server_groups(headers, args.customer_id)
elif args.action == "policies":
list_access_policies(headers, args.customer_id)
elif args.action == "connectors":
check_connector_health(headers, args.customer_id)
elif args.action == "full-audit":
apps, af = list_app_segments(headers, args.customer_id)
list_server_groups(headers, args.customer_id)
_, pf = list_access_policies(headers, args.customer_id)
_, ci = check_connector_health(headers, args.customer_id)
generate_report(apps, af, pf, ci, args.output)
if __name__ == "__main__":
main()