Automating IAM Least Privilege Policies in AWS using Access Analyzer
How I built a reliable WhatsApp AI shopping assistant for Clickmothercare that survives hallucinated products, silent save failures, and multi-agent handoff bugs.
Over-permissive IAM roles are the root cause of countless cloud breaches. Developers often grant s3:* or dynamodb:* during prototyping to avoid permission errors, but these wildcard privileges inevitably make it into production. Achieving strict least privilege manually is an arduous, error-prone task. AWS IAM Access Analyzer bridges this gap by generating policies based on actual API activity.
The Challenge: Replacing Wildcards with Granular Permissions
To implement least privilege, you must know exactly which API calls your application makes. Guessing leads to application downtime, while wildcards lead to security vulnerabilities.
The Solution: Automated Policy Generation via AWS CLI
AWS CloudTrail logs every API call. IAM Access Analyzer can digest these logs and output a perfectly tailored JSON IAM policy containing only the actions actually invoked by a role over a specific timeframe. Here is the automation script using the AWS CLI.
#!/bin/bash
ROLE_ARN="arn:aws:iam::123456789012:role/OverPermissiveAppRole"
TRAIL_ARN="arn:aws:cloudtrail:us-east-1:123456789012:trail/management-events"
START_TIME=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)
END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# Step 1: Initiate Policy Generation
JOB_ID=$(aws accessanalyzer start-policy-generation \
--policy-generation-details "principalArn=$ROLE_ARN" \
--cloud-trail-details "accessRole=arn:aws:iam::123456789012:role/AnalyzerRole,startTime=$START_TIME,endTime=$END_TIME,trails=[$TRAIL_ARN]" \
--query 'jobId' --output text)
echo "Started job: $JOB_ID. Waiting for completion..."
# Step 2: Poll for Completion
while true; do
STATUS=$(aws accessanalyzer get-generated-policy \
--job-id "$JOB_ID" \
--query 'jobDetails.status' --output text)
if [ "$STATUS" == "SUCCEEDED" ]; then break; fi
if [ "$STATUS" == "FAILED" ]; then echo "Generation failed"; exit 1; fi
sleep 10
done
# Step 3: Retrieve and Save the Granular Policy
aws accessanalyzer get-generated-policy \
--job-id "$JOB_ID" \
--include-resource-placeholders \
--query 'generatedPolicyResult.generatedPolicies[0].policy' \
--output json > least_privilege_policy.json
echo "Policy saved to least_privilege_policy.json"
Script Execution Analysis
Let's break down the mechanics of this automated CloudOps workflow:
START_TIMEandEND_TIME: We dynamically calculate a 7-day lookback window. This ensures we capture a full cycle of application behavior, including background cron jobs or weekly batch processing tasks.start-policy-generation: This API call commands Access Analyzer to ingest CloudTrail data for the specificprincipalArn. It requires a dedicatedaccessRolethat grants the Analyzer service read access to your CloudTrail S3 bucket.while true; do ... sleep 10: Policy generation is an asynchronous, compute-intensive background process on AWS's side. This loop implements exponential backoff (simplified here to 10s) to poll the job state until it transitions toSUCCEEDED.--include-resource-placeholders: A crucial flag. While Access Analyzer identifies the API actions (e.g.,s3:GetObject), it cannot always infer the exact ARN syntax for specific resources. This flag injects placeholder values (like${ResourceName}) into the JSON output, prompting the security engineer to manually scope the policy down to specific bucket ARNs or table names before deployment.--query 'generatedPolicyResult...policy': Uses JMESPath to extract only the raw JSON IAM document from the nested API response, piping it cleanly into a file ready for Terraform or CloudFormation ingestion.
By executing this workflow periodically, you can confidently strip away wildcards and maintain mathematically provable least privilege in your cloud environments.
Is your AI agent's infrastructure secure and reliable?
Book a Free 15-Min Technical Audit