🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Build an AI Deployment Validator with Claude API and OPA

Combine Claude API and Open Policy Agent to build an intelligent deployment validator that catches misconfigurations, security issues, and policy violations before they hit production — with natural language explanations.

Shubham6 min read
Share:Tweet

OPA gives you policy enforcement. Claude gives you intelligent analysis. Combined, you get a validator that not only blocks bad deployments but explains exactly why and how to fix them in plain English.

What We're Building

A pre-deployment validation tool that:

  1. Runs OPA policies (Rego rules) for hard policy violations
  2. Sends the manifest to Claude API for intelligent security and config analysis
  3. Combines results into a human-readable report with actionable fixes
  4. Returns exit code 0 (pass) or 1 (fail) for CI/CD integration

Setup

bash
pip install anthropic opa-python-client pyyaml
# Or install OPA binary directly:
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
chmod +x opa && mv opa /usr/local/bin/

Step 1: OPA Policies (Rego)

rego
# policies/k8s_security.rego
package k8s.security
 
import future.keywords.if
import future.keywords.in
 
# Deny privileged containers
deny[msg] if {
    container := input.spec.template.spec.containers[_]
    container.securityContext.privileged == true
    msg := sprintf("Container '%v' runs as privileged — this grants root-level host access", [container.name])
}
 
# Deny containers without resource limits
deny[msg] if {
    container := input.spec.template.spec.containers[_]
    not container.resources.limits
    msg := sprintf("Container '%v' has no resource limits — it can consume unbounded CPU/memory and cause node OOM", [container.name])
}
 
# Deny latest tag
deny[msg] if {
    container := input.spec.template.spec.containers[_]
    endswith(container.image, ":latest")
    msg := sprintf("Container '%v' uses :latest tag — prevents reproducible deployments and rollbacks", [container.name])
}
 
# Deny running as root
deny[msg] if {
    container := input.spec.template.spec.containers[_]
    container.securityContext.runAsUser == 0
    msg := sprintf("Container '%v' runs as root (UID 0) — violates least-privilege principle", [container.name])
}
 
# Warn on missing readiness probe
warn[msg] if {
    container := input.spec.template.spec.containers[_]
    not container.readinessProbe
    msg := sprintf("Container '%v' has no readiness probe — traffic may reach pods before they are ready", [container.name])
}
 
# Deny hostNetwork
deny[msg] if {
    input.spec.template.spec.hostNetwork == true
    msg := "hostNetwork: true exposes host network namespace to the pod — severe security risk"
}

Step 2: Python Validator

python
import anthropic
import json
import subprocess
import sys
import yaml
from pathlib import Path
 
 
OPA_POLICY_DIR = "policies/"
FAIL_ON_WARN = False
 
 
def run_opa_eval(manifest: dict, policy_file: str) -> dict:
    """Run OPA evaluation against a manifest."""
    manifest_json = json.dumps(manifest)
 
    result = subprocess.run(
        [
            "opa", "eval",
            "--data", policy_file,
            "--input", "/dev/stdin",
            "--format", "json",
            "data.k8s.security"
        ],
        input=manifest_json,
        capture_output=True,
        text=True,
        timeout=30
    )
 
    if result.returncode != 0:
        return {"error": result.stderr, "deny": [], "warn": []}
 
    output = json.loads(result.stdout)
    results = output.get("result", [{}])[0].get("expressions", [{}])[0].get("value", {})
 
    return {
        "deny": results.get("deny", []),
        "warn": results.get("warn", [])
    }
 
 
def analyze_with_claude(manifest: dict, opa_results: dict) -> dict:
    """Get Claude's security and configuration analysis."""
    client = anthropic.Anthropic()
 
    manifest_yaml = yaml.dump(manifest, default_flow_style=False)
    opa_violations = json.dumps(opa_results, indent=2)
 
    prompt = f"""You are a Kubernetes security expert reviewing a deployment manifest.
 
## Manifest
```yaml
{manifest_yaml}
```
 
## OPA Policy Violations Found
{opa_violations}
 
Analyze this manifest and identify:
1. Security issues NOT caught by OPA (beyond the violations listed above)
2. Reliability risks (single replica, no disruption budget, etc.)
3. Operational best practices violations (missing labels, annotations, etc.)
4. The 3 most critical issues overall (OPA + your analysis combined)
5. Specific code fixes for each critical issue
 
Return JSON:
{{
  "additional_security_issues": ["..."],
  "reliability_risks": ["..."],
  "best_practice_violations": ["..."],
  "critical_issues": [
    {{
      "issue": "description",
      "severity": "critical|high|medium",
      "fix": "exact YAML or command to fix this"
    }}
  ],
  "overall_risk": "high|medium|low",
  "recommendation": "approve|approve_with_changes|reject"
}}"""
 
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=2000,
        messages=[{"role": "user", "content": prompt}]
    )
 
    text = response.content[0].text.strip()
    if "```" in text:
        text = text.split("```")[1]
        if text.startswith("json"):
            text = text[4:]
 
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        return {"error": "Could not parse Claude response", "raw": text}
 
 
def validate_manifest(manifest_path: str) -> bool:
    """Main validation function. Returns True if manifest passes, False if it fails."""
    with open(manifest_path) as f:
        manifest = yaml.safe_load(f)
 
    resource_name = f"{manifest.get('kind', 'Unknown')}/{manifest.get('metadata', {}).get('name', 'unknown')}"
    print(f"\n{'='*60}")
    print(f"Validating: {resource_name}")
    print("="*60)
 
    # Step 1: OPA evaluation
    print("\n[1/2] Running OPA policy checks...")
    opa_results = {"deny": [], "warn": []}
    for policy_file in Path(OPA_POLICY_DIR).glob("*.rego"):
        result = run_opa_eval(manifest, str(policy_file))
        opa_results["deny"].extend(result.get("deny", []))
        opa_results["warn"].extend(result.get("warn", []))
 
    # Step 2: Claude analysis
    print("[2/2] Running AI security analysis...")
    claude_analysis = analyze_with_claude(manifest, opa_results)
 
    # Display results
    passed = True
 
    if opa_results["deny"]:
        print(f"\n POLICY VIOLATIONS ({len(opa_results['deny'])} found):")
        for violation in opa_results["deny"]:
            print(f"  ✗ {violation}")
        passed = False
 
    if opa_results["warn"]:
        print(f"\n WARNINGS ({len(opa_results['warn'])} found):")
        for warning in opa_results["warn"]:
            print(f"  ⚠ {warning}")
        if FAIL_ON_WARN:
            passed = False
 
    critical = claude_analysis.get("critical_issues", [])
    if critical:
        print(f"\n AI ANALYSIS — CRITICAL ISSUES ({len(critical)} found):")
        for issue in critical:
            severity = issue.get("severity", "unknown").upper()
            print(f"\n  [{severity}] {issue.get('issue', '')}")
            fix = issue.get("fix", "")
            if fix:
                print(f"  Fix:\n{chr(10).join('    ' + line for line in fix.split(chr(10)))}")
 
        # Fail if AI finds critical issues
        critical_count = sum(1 for i in critical if i.get("severity") == "critical")
        if critical_count > 0:
            passed = False
 
    recommendation = claude_analysis.get("recommendation", "unknown")
    risk = claude_analysis.get("overall_risk", "unknown")
    print(f"\n Risk Level: {risk.upper()}")
    print(f" Recommendation: {recommendation.upper().replace('_', ' ')}")
 
    if passed:
        print("\n VALIDATION PASSED — Safe to deploy")
    else:
        print("\n VALIDATION FAILED — Fix issues before deploying")
 
    return passed
 
 
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python validator.py <manifest.yaml> [manifest2.yaml ...]")
        sys.exit(1)
 
    all_passed = True
    for manifest_file in sys.argv[1:]:
        passed = validate_manifest(manifest_file)
        if not passed:
            all_passed = False
 
    sys.exit(0 if all_passed else 1)

Step 3: CI/CD Integration

GitHub Actions:

yaml
name: Validate Kubernetes Manifests
 
on:
  pull_request:
    paths:
      - 'k8s/**/*.yaml'
      - 'k8s/**/*.yml'
 
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
 
      - name: Install dependencies
        run: pip install anthropic pyyaml
 
      - name: Install OPA
        run: |
          curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
          chmod +x opa && sudo mv opa /usr/local/bin/
 
      - name: Validate changed manifests
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          # Get list of changed YAML files
          CHANGED=$(git diff --name-only origin/main...HEAD | grep -E '\.ya?ml$' | grep 'k8s/')
          if [ -z "$CHANGED" ]; then
            echo "No Kubernetes manifests changed"
            exit 0
          fi
          python validator.py $CHANGED

Example Output

============================================================
Validating: Deployment/payment-service
============================================================

[1/2] Running OPA policy checks...
[2/2] Running AI security analysis...

 POLICY VIOLATIONS (2 found):
  ✗ Container 'payment-api' uses :latest tag — prevents reproducible deployments and rollbacks
  ✗ Container 'payment-api' has no resource limits — it can consume unbounded CPU/memory

 AI ANALYSIS — CRITICAL ISSUES (3 found):

  [CRITICAL] Payment service exposes debug port 9229 (Node.js debugger) — allows remote code execution
  Fix:
    Remove port 9229 from the ports list. Add to securityContext:
    capabilities:
      drop: ["ALL"]

  [HIGH] Single replica with no PodDisruptionBudget — node drain will cause downtime
  Fix:
    Set replicas: 2 and add PodDisruptionBudget with minAvailable: 1

  [HIGH] No network policy — payment service can communicate with any pod in the cluster
  Fix:
    Apply a NetworkPolicy restricting ingress to api-gateway only

 Risk Level: HIGH
 Recommendation: REJECT

 VALIDATION FAILED — Fix issues before deploying

Adding this to your PR process catches security issues before they reach production — before human review, before staging, before anything.


More DevSecOps tools? Read our Build AI Dockerfile security scanner and Kubernetes OPA Gatekeeper setup guide.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Build an AI Blue-Green Deployment Risk Scorer with Claude API

Blue-green deployments cut traffic fully at cutover, unlike gradual canaries — which means the decision to cut over needs to be right the first time. Build a tool that scores cutover risk before you flip the switch, using Claude API to reason across the diff, test results, and deployment history.

S
4 min readRead

Comments