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

274 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""Open Policy Agent (OPA) policy-as-code agent.
Evaluates security policies against infrastructure configurations using
the OPA REST API or CLI. Supports evaluating Rego policies for Kubernetes
admission control, Terraform plans, IAM policies, and custom security rules.
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
requests = None
def find_opa_binary():
"""Locate the OPA binary on the system."""
custom_path = os.environ.get("OPA_PATH")
if custom_path and os.path.isfile(custom_path):
return custom_path
for name in ["opa", "opa.exe"]:
for directory in os.environ.get("PATH", "").split(os.pathsep):
full_path = os.path.join(directory, name)
if os.path.isfile(full_path):
return full_path
return None
def eval_policy_cli(opa_bin, policy_path, input_path, data_path=None, query="data"):
"""Evaluate a Rego policy using OPA CLI."""
cmd = [opa_bin, "eval", "--format", "json"]
cmd.extend(["--bundle", policy_path])
if input_path:
cmd.extend(["--input", input_path])
if data_path:
cmd.extend(["--data", data_path])
cmd.append(query)
print(f"[*] Running: {' '.join(cmd)}")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=120,
)
if result.returncode != 0:
print(f"[!] OPA error: {result.stderr}", file=sys.stderr)
return None
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
print(f"[!] Failed to parse OPA output", file=sys.stderr)
return None
def eval_policy_api(opa_url, policy_path, input_data):
"""Evaluate a policy via OPA REST API."""
if not requests:
print("[!] 'requests' library required for API mode", file=sys.stderr)
sys.exit(1)
url = f"{opa_url}/v1/data/{policy_path.replace('.', '/')}"
print(f"[*] Querying OPA API: {url}")
resp = requests.post(
url,
json={"input": input_data},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def test_policies(opa_bin, policy_dir):
"""Run OPA test suite against policy directory."""
cmd = [opa_bin, "test", "--format", "json", policy_dir, "-v"]
print(f"[*] Running policy tests: {' '.join(cmd)}")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=120,
)
try:
test_results = json.loads(result.stdout)
except json.JSONDecodeError:
test_results = []
return test_results, result.returncode
def check_policy_syntax(opa_bin, policy_path):
"""Check Rego policy syntax."""
cmd = [opa_bin, "check", "--format", "json", policy_path]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
)
if result.returncode == 0:
print(f"[+] Policy syntax valid: {policy_path}")
return True, []
try:
errors = json.loads(result.stdout)
except json.JSONDecodeError:
errors = [{"message": result.stderr}]
print(f"[!] Syntax errors in {policy_path}")
return False, errors
def extract_violations(eval_result, violation_key="violations"):
"""Extract policy violations from OPA evaluation result."""
violations = []
if not eval_result:
return violations
results = eval_result.get("result", [])
if isinstance(results, list):
for entry in results:
bindings = entry.get("bindings", {})
expressions = entry.get("expressions", [])
for expr in expressions:
value = expr.get("value", {})
if isinstance(value, dict):
for key, val in value.items():
if key == violation_key and isinstance(val, list):
violations.extend(val)
elif isinstance(val, dict):
nested_violations = val.get(violation_key, [])
if isinstance(nested_violations, list):
violations.extend(nested_violations)
elif isinstance(results, dict):
violations = results.get(violation_key, [])
return violations
def format_summary(violations, test_results, policy_path, input_path):
"""Print evaluation summary."""
print(f"\n{'='*60}")
print(f" OPA Policy Evaluation Report")
print(f"{'='*60}")
print(f" Policy : {policy_path}")
print(f" Input : {input_path or 'N/A'}")
print(f" Violations: {len(violations)}")
if test_results:
passed = sum(1 for t in test_results if t.get("pass", t.get("result") == "pass"))
failed = len(test_results) - passed
print(f" Tests : {passed} passed, {failed} failed")
if violations:
severity_counts = {}
for v in violations:
sev = "HIGH"
if isinstance(v, dict):
sev = v.get("severity", v.get("level", "HIGH"))
severity_counts[sev] = severity_counts.get(sev, 0) + 1
print(f"\n Violations by Severity:")
for sev, count in sorted(severity_counts.items()):
print(f" {sev:10s}: {count}")
print(f"\n Violation Details:")
for v in violations[:20]:
if isinstance(v, dict):
msg = v.get("msg", v.get("message", str(v)))
resource = v.get("resource", v.get("name", ""))
sev = v.get("severity", "HIGH")
print(f" [{sev:6s}] {resource:30s} | {msg[:60]}")
else:
print(f" {str(v)[:80]}")
return len(violations)
def main():
parser = argparse.ArgumentParser(
description="Open Policy Agent policy-as-code evaluation agent"
)
sub = parser.add_subparsers(dest="command", help="Action")
p_eval = sub.add_parser("eval", help="Evaluate policy against input")
p_eval.add_argument("--policy", required=True, help="Path to Rego policy or bundle dir")
p_eval.add_argument("--input", dest="input_file", help="Path to input JSON")
p_eval.add_argument("--data", help="Path to external data JSON")
p_eval.add_argument("--query", default="data", help="OPA query (default: data)")
p_eval.add_argument("--violation-key", default="violations",
help="Key in result containing violations")
p_api = sub.add_parser("api", help="Evaluate via OPA REST API")
p_api.add_argument("--url", default="http://localhost:8181", help="OPA server URL")
p_api.add_argument("--policy-path", required=True, help="OPA document path (e.g., authz.allow)")
p_api.add_argument("--input", dest="input_file", required=True, help="Input JSON file")
p_test = sub.add_parser("test", help="Run OPA test suite")
p_test.add_argument("--policy-dir", required=True, help="Directory containing policies and tests")
p_check = sub.add_parser("check", help="Check Rego syntax")
p_check.add_argument("--policy", required=True, help="Policy file or directory")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
opa_bin = find_opa_binary()
violations = []
test_results = []
if args.command == "eval":
if not opa_bin:
print("[!] OPA binary not found", file=sys.stderr)
sys.exit(1)
eval_result = eval_policy_cli(
opa_bin, args.policy, args.input_file, args.data, args.query
)
violations = extract_violations(eval_result, args.violation_key)
format_summary(violations, [], args.policy, args.input_file)
elif args.command == "api":
with open(args.input_file, "r") as f:
input_data = json.load(f)
eval_result = eval_policy_api(args.url, args.policy_path, input_data)
violations = extract_violations(eval_result)
format_summary(violations, [], args.policy_path, args.input_file)
elif args.command == "test":
if not opa_bin:
print("[!] OPA binary not found", file=sys.stderr)
sys.exit(1)
test_results, returncode = test_policies(opa_bin, args.policy_dir)
format_summary([], test_results, args.policy_dir, None)
elif args.command == "check":
if not opa_bin:
print("[!] OPA binary not found", file=sys.stderr)
sys.exit(1)
valid, errors = check_policy_syntax(opa_bin, args.policy)
if not valid:
for e in errors:
print(f" Error: {e}")
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "Open Policy Agent",
"command": args.command,
"violations_count": len(violations),
"violations": violations,
"test_results": test_results,
"risk_level": (
"CRITICAL" if len(violations) > 10
else "HIGH" if len(violations) > 0
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()