
Operationalizing least privilege: Automate IAM remediation through your CI/CD pipeline
AWS Security Blog walkthrough of an open-source Lambda-based automation that closes the loop between IAM Access Analyzer's unused-permission findings and actual remediation: it classifies each flagged role by origin via CloudTrail (CloudFormation/CDK-managed, manually created, or unused), then uses Amazon Bedrock (Claude Sonnet for code generation, Claude Haiku for explanations) to open a pull request with right-sized CDK IAM policy code, a GitHub/GitLab issue with migration guidance, or a soft-disable decommission issue respectively. It covers cross-account/organization-wide deployment, exclusion lists for sensitive roles, throttling, dry-run mode, CloudWatch metrics, and cost/cleanup considerations, with full source in an accompanying repo.
What it is
An open-source AWS Lambda automation (CDK stacks, source in the linked GitHub repo) that closes the loop between IAM Access Analyzer's unused-permission findings and actual code/issue remediation, triggered daily via EventBridge.
How it works
On each run the Lambda calls Access Analyzer's GenerateFindingRecommendation/GetFindingRecommendation APIs to get right-sized policies, then queries CloudTrail's CreateRole event to classify origin: a cloudformation.amazonaws.com service principal + user agent indicates CDK/CloudFormation (then looks up a Repository tag to find the source repo); an IAMUser type with console.amazonaws.com or aws-cli/2.x.x user agent indicates manual creation. For organization-level findings, it assumes a cross-account role (CROSS_ACCOUNT_ROLE_NAME, default OrganizationAccountAccessRole) in the member account to query that account's CloudTrail; if the assume fails, it falls back to classifying the role as unknown-origin rather than erroring out.
flowchart TD
F[Finding] --> E{Excluded?}
E -->|yes| Skip
E -->|no| T{Finding type}
T -->|UnusedPermission| O{Role origin via CloudTrail}
T -->|UnusedIAMRole| Soft[Soft-disable issue]
O -->|CFN/CDK principal| PR[PR: Bedrock-generated CDK code]
O -->|IAMUser console/CLI| Issue[Issue: policy + IaC migration guidance]
O -->|cross-account assume fails| Unknown[Issue: unknown origin]For generated CDK code, the Lambda compiles the Python and checks for required iam/PolicyStatement patterns before allowing it into a PR; failed validation logs an error instead of opening a broken PR. Shared managed policies (used by multiple roles) are routed to an issue rather than a PR, since editing them could affect other roles. Model split: Claude Sonnet on Bedrock for CDK code generation, Claude Haiku for plain-English explanations — both model IDs are configurable via BEDROCK_CODEGEN_MODEL/BEDROCK_EXPLANATION_MODEL since Bedrock retires older models over time.
Preconditions / Affected
Requires an ACCOUNT_UNUSED_ACCESS or ORGANIZATION_UNUSED_ACCESS IAM Access Analyzer already enabled, Python 3.11+, AWS CDK v2, a CI/CD platform token in Secrets Manager, and Bedrock model access for the chosen Claude models. Access Analyzer's unused-access analyzer bills per role/user analyzed per month, so cost scales with org size (example given: 500 roles across 20 accounts costs more than 50 roles in one account).
What to do
- Configure
config/exclusions.json(break-glass roles, service-linked roles,CriticalService-tagged roles,min_unused_daysthreshold,approval_required_for_tagsfor sensitive roles that should get issues instead of auto-PRs) andconfig/ci_cd_config.json. - Deploy with
cdk deploy --all; validate first with"dry_run": true(orCI_CD_PLATFORM=dryrun) before flipping to live. - Tune throttling:
max_findings_per_run(default 50UnusedPermissionfindings/run) andMAX_UNUSED_ROLE_ISSUES(default 10/run) — start conservative and raise as review capacity grows. - Set a CloudWatch alarm on
errors > 5per run; trackprs_created + issues_createdtrending down over time, and watchiac_roles_foundvsmanual_roles_foundshift toward IaC. - Unused-role decommission is a manual 3-stage process the issue recommends, not automated: attach a deny-all inline policy, monitor 30 days, then delete.
- Cleanup:
cdk destroy --all, then separately delete the Secrets Manager token (aws secretsmanager delete-secret --secret-id <name> --recovery-window-in-days 7), the orphaned Lambda log group (aws logs delete-log-group --log-group-name /aws/lambda/<function-name>), and the Access Analyzer itself (aws accessanalyzer delete-analyzer --analyzer-name <name>) — none of these are removed by the CDK destroy and the analyzer deletion wipes all finding history.
Caveats
The solution does not call Access Analyzer's ValidatePolicy API on Bedrock-generated policies before PR creation — the post flags this explicitly as a gap teams should add themselves. Cross-account attribution degrades gracefully to "unknown origin" rather than guaranteeing correct classification.
Organisations named here
- IAM Access Analyzer
Technologies named here
- AWS IAM Access Analyzer
- AWS Identity and Access Management
- AWS CloudFormation
- AWS CloudTrail
- Amazon Bedrock
- AWS Cloud Development Kit
- Amazon EventBridge
- AWS Lambda
- Amazon CloudWatch
- AWS Secrets Manager
- AWS CLI
- CI/CD pipeline
- AWS IAM
- AWS Bedrock
- AWS CDK
Techniques named here
- Least privilege enforcement via automated IAM remediation
- Role attribution via CloudTrail CreateRole event analysis
- Cross-account role attribution
- Soft-disable decommission workflow
- Dry-run validation mode
- Generated code validation
Related in DevSecOps
JFrog: The Governance Gap Between Your Policy and Your Pipeline

Dev Machine Guard Now Supports Windows - StepSecurity

Socket: TanStack npm Packages Compromised
