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

Build an AI AWS Security Auditor with Claude API and Boto3

Use Python, boto3, and the Claude API to automatically audit your AWS environment for security misconfigurations and get AI-powered remediation recommendations.

DevOpsBoys5 min read
Share:Tweet

Security teams spend hours reviewing AWS configs manually. In this project you will build a Python script that uses boto3 to collect your AWS security posture, sends it to the Claude API for analysis, and produces a prioritised remediation report. Run it weekly on a schedule and email the results.

What the Auditor Checks

  1. S3 buckets with public access enabled
  2. Security groups with 0.0.0.0/0 inbound rules
  3. IAM users without MFA enabled
  4. RDS instances that are publicly accessible
  5. CloudTrail status per region

Prerequisites

bash
pip install anthropic boto3

Set your credentials:

bash
export AWS_DEFAULT_REGION=us-east-1
export ANTHROPIC_API_KEY=sk-ant-...

The AWS credentials need read-only permissions: SecurityAudit managed policy covers everything used here.

The Audit Script

python
import boto3
import json
import anthropic
from datetime import datetime
 
def audit_s3_public_buckets():
    s3 = boto3.client('s3')
    findings = []
    buckets = s3.list_buckets().get('Buckets', [])
 
    for bucket in buckets:
        name = bucket['Name']
        try:
            acl = s3.get_public_access_block(Bucket=name)
            config = acl['PublicAccessBlockConfiguration']
            if not all([
                config.get('BlockPublicAcls', False),
                config.get('BlockPublicPolicy', False),
                config.get('IgnorePublicAcls', False),
                config.get('RestrictPublicBuckets', False)
            ]):
                findings.append({
                    "resource": f"s3://{name}",
                    "issue": "Public access block not fully enabled",
                    "severity": "HIGH"
                })
        except s3.exceptions.NoSuchPublicAccessBlockConfiguration:
            findings.append({
                "resource": f"s3://{name}",
                "issue": "No public access block configuration set",
                "severity": "HIGH"
            })
        except Exception:
            pass
 
    return findings
 
 
def audit_security_groups():
    ec2 = boto3.client('ec2')
    findings = []
    paginator = ec2.get_paginator('describe_security_groups')
 
    for page in paginator.paginate():
        for sg in page['SecurityGroups']:
            for rule in sg.get('IpPermissions', []):
                for ip_range in rule.get('IpRanges', []):
                    if ip_range.get('CidrIp') == '0.0.0.0/0':
                        port = rule.get('FromPort', 'ALL')
                        findings.append({
                            "resource": f"sg/{sg['GroupId']} ({sg['GroupName']})",
                            "issue": f"Inbound 0.0.0.0/0 on port {port}",
                            "severity": "HIGH" if port in [22, 3389, 3306, 5432] else "MEDIUM"
                        })
 
    return findings
 
 
def audit_iam_mfa():
    iam = boto3.client('iam')
    findings = []
    paginator = iam.get_paginator('list_users')
 
    for page in paginator.paginate():
        for user in page['Users']:
            username = user['UserName']
            mfa_devices = iam.list_mfa_devices(UserName=username)['MFADevices']
            if not mfa_devices:
                # Only flag users that have console access
                try:
                    iam.get_login_profile(UserName=username)
                    findings.append({
                        "resource": f"iam/user/{username}",
                        "issue": "Console access without MFA",
                        "severity": "CRITICAL"
                    })
                except iam.exceptions.NoSuchEntityException:
                    pass  # No console access, skip
 
    return findings
 
 
def audit_rds_public():
    rds = boto3.client('rds')
    findings = []
    paginator = rds.get_paginator('describe_db_instances')
 
    for page in paginator.paginate():
        for db in page['DBInstances']:
            if db.get('PubliclyAccessible', False):
                findings.append({
                    "resource": f"rds/{db['DBInstanceIdentifier']}",
                    "issue": f"RDS instance publicly accessible ({db['Engine']})",
                    "severity": "CRITICAL"
                })
 
    return findings
 
 
def audit_cloudtrail():
    ct = boto3.client('cloudtrail')
    findings = []
 
    try:
        trails = ct.describe_trails(includeShadowTrails=False)['trailList']
        if not trails:
            findings.append({
                "resource": "cloudtrail",
                "issue": "No CloudTrail trails configured",
                "severity": "HIGH"
            })
        else:
            for trail in trails:
                status = ct.get_trail_status(Name=trail['TrailARN'])
                if not status.get('IsLogging', False):
                    findings.append({
                        "resource": f"cloudtrail/{trail['Name']}",
                        "issue": "CloudTrail trail exists but logging is disabled",
                        "severity": "HIGH"
                    })
    except Exception as e:
        findings.append({
            "resource": "cloudtrail",
            "issue": f"Could not check CloudTrail: {str(e)}",
            "severity": "UNKNOWN"
        })
 
    return findings
 
 
def analyse_with_claude(findings: list) -> str:
    client = anthropic.Anthropic()
 
    findings_text = json.dumps(findings, indent=2)
 
    message = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=2048,
        messages=[
            {
                "role": "user",
                "content": f"""You are an AWS security expert. Analyse these security findings from an automated AWS audit and provide:
 
1. Executive summary (2-3 sentences for a non-technical manager)
2. Top 3 critical risks to fix immediately, with specific AWS CLI or console steps to remediate each
3. Quick wins that can be fixed in under 10 minutes
4. Overall security score out of 10
 
Findings:
{findings_text}
 
Be specific and actionable. Include the exact AWS CLI commands where possible."""
            }
        ]
    )
 
    return message.content[0].text
 
 
def run_audit():
    print(f"Starting AWS Security Audit at {datetime.now().isoformat()}")
    print("=" * 60)
 
    all_findings = []
 
    print("Checking S3 public access...")
    all_findings.extend(audit_s3_public_buckets())
 
    print("Checking security groups...")
    all_findings.extend(audit_security_groups())
 
    print("Checking IAM MFA...")
    all_findings.extend(audit_iam_mfa())
 
    print("Checking RDS public access...")
    all_findings.extend(audit_rds_public())
 
    print("Checking CloudTrail...")
    all_findings.extend(audit_cloudtrail())
 
    print(f"\nFound {len(all_findings)} findings. Sending to Claude for analysis...\n")
 
    analysis = analyse_with_claude(all_findings)
 
    report = {
        "audit_date": datetime.now().isoformat(),
        "account_id": boto3.client('sts').get_caller_identity()['Account'],
        "total_findings": len(all_findings),
        "findings_by_severity": {
            "CRITICAL": len([f for f in all_findings if f['severity'] == 'CRITICAL']),
            "HIGH": len([f for f in all_findings if f['severity'] == 'HIGH']),
            "MEDIUM": len([f for f in all_findings if f['severity'] == 'MEDIUM']),
        },
        "raw_findings": all_findings,
        "ai_analysis": analysis
    }
 
    with open("security-report.json", "w") as f:
        json.dump(report, f, indent=2)
 
    print("=" * 60)
    print("AI ANALYSIS")
    print("=" * 60)
    print(analysis)
    print(f"\nFull report saved to security-report.json")
 
    return report
 
 
if __name__ == "__main__":
    run_audit()

Sample Output

Starting AWS Security Audit at 2026-06-26T09:15:32
============================================================
Checking S3 public access...
Checking security groups...
Checking IAM MFA...
Checking RDS public access...
Checking CloudTrail...

Found 7 findings. Sending to Claude for analysis...

============================================================
AI ANALYSIS
============================================================
Executive Summary: Your AWS account has 2 critical security
gaps that expose sensitive data and systems to unauthorized
access. IAM users with console access lack MFA, and one RDS
instance is publicly reachable from the internet.

Top 3 Critical Risks:

1. IAM User Without MFA (CRITICAL)
   User 'deploy-user' has console access with no MFA.
   Fix: aws iam create-virtual-mfa-device --virtual-mfa-device-name deploy-user-mfa
   Then enforce MFA via IAM policy or enable org-level SCP.

2. RDS Instance Publicly Accessible (CRITICAL)
   prod-postgres-db is reachable from the internet.
   Fix: aws rds modify-db-instance --db-instance-identifier prod-postgres-db
        --no-publicly-accessible --apply-immediately

3. Security Group with SSH Open to World (HIGH)
   sg-0abc123 allows 0.0.0.0/0 on port 22.
   Fix: Replace with your office CIDR or use AWS SSM Session Manager instead of SSH.

Quick Wins (under 10 minutes):
- Enable S3 Block Public Access at account level (1 click in console)
- Delete unused IAM users without recent activity

Overall Security Score: 4/10

Running Weekly with GitHub Actions

Add this to .github/workflows/security-audit.yml:

yaml
name: Weekly AWS Security Audit
 
on:
  schedule:
    - cron: '0 8 * * 1'
  workflow_dispatch:
 
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
 
      - name: Install dependencies
        run: pip install anthropic boto3
 
      - name: Run audit
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: us-east-1
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: python audit.py
 
      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: security-report-${{ github.run_id }}
          path: security-report.json

The report artifact is available in the Actions UI. Add an SES or SMTP step to email it to your team.

This audit costs roughly $0.002 per run using claude-haiku-4-5 — about $0.10 per year. The IAM SecurityAudit managed policy means you can run it with read-only access and never worry about the script making unintended changes.


For more AWS security hardening, see our DevSecOps pipeline 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

Comments