This document covers the internal mechanics of IAM Access Analyzer: how policies are evaluated, what resources are analyzed, what types of findings are produced, and how the finding lifecycle is managed.
- Architecture Overview
- The Zelkova Automated Reasoning Engine
- Zone of Trust
- Supported Resource Types
- Finding Types
- How Findings Are Generated and Updated
- Finding Lifecycle and States
- Archive Rules
- Limitations and Edge Cases
IAM Access Analyzer is a regional, event-driven service. Each analyzer is scoped to a specific AWS region and a specific zone of trust (either a single account or an entire AWS Organization). You can have multiple analyzers per region — for example, one for external access analysis and one for unused access analysis.
When you create an analyzer, it performs an initial scan of all supported resource types within the zone of trust. After the initial scan, the analyzer subscribes to AWS CloudTrail events and resource configuration changes. When a policy or configuration changes, Access Analyzer re-evaluates the affected resource within minutes.
┌─────────────────────────────────────────────────────────────────┐
│ AWS Account / Org │
│ │
│ Resource Policies IAM Entity Policies │
│ (S3, KMS, Lambda…) (Roles, Users) │
│ │ │ │
│ └──────────┬───────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ IAM Access Analyzer│◄── CloudTrail Events │
│ │ (Zelkova Engine) │◄── Config Change Events │
│ └─────────┬───────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ Findings Unused Access │
│ (External) Findings │
│ │ │ │
│ └────────┬────────┘ │
│ ▼ │
│ EventBridge → Security Hub / Slack / Jira │
└─────────────────────────────────────────────────────────────────┘
Most cloud security tools use heuristic analysis: they pattern-match known bad configurations (e.g., "this S3 bucket has Principal: *") and flag them. Heuristics are fast but produce both false positives (flagging safe configurations) and false negatives (missing complex unsafe configurations).
IAM Access Analyzer uses automated reasoning — specifically, a technique from formal verification called satisfiability modulo theories (SMT). The Zelkova engine (named after a genus of trees, following AWS's tradition of internal codenames) translates IAM policy documents into mathematical logical formulas and then determines, with mathematical certainty, whether a given principal outside the zone of trust can satisfy the conditions required to access the resource.
Zelkova considers the full policy evaluation logic defined by AWS:
- Identity-based policies (inline and managed) attached to the calling principal
- Resource-based policies on the resource being accessed
- Service control policies (SCPs) in the organizational hierarchy (for org-level analyzers)
- Permission boundaries on the calling principal
- Session policies (for assumed-role sessions)
- IAM condition keys — including
aws:SourceAccount,aws:SourceArn,aws:PrincipalOrgID,aws:RequestedRegion,aws:ViaAWSService, etc.
This means Access Analyzer correctly understands nuanced configurations like:
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-exampleorgid"
}
}
}The above is a resource policy that grants access to * but with a condition restricting it to principals in your AWS Organization. Zelkova evaluates this and correctly does not generate an external access finding, because no principal outside the org can satisfy the condition.
The key guarantee is: when Access Analyzer reports no finding, it is a mathematical proof that no external principal can access the resource under the current policies. This is stronger than any heuristic tool can claim.
When Access Analyzer does report a finding, it includes the specific principals and conditions under which access is possible, enabling precise triage.
Zelkova operates on the static policy text. It cannot:
- Predict run-time behavior of applications using the credentials.
- Evaluate IP-based conditions against dynamic IP ranges (it models
aws:SourceIpconditions but marks findings as conditional rather than definitively external). - Evaluate VPC endpoint conditions without knowledge of the endpoint's policy.
The zone of trust is the set of principals considered "internal." Any access from outside this boundary triggers a finding.
| Analyzer Type | Zone of Trust |
|---|---|
ACCOUNT |
The single AWS account where the analyzer is created |
ORGANIZATION |
All accounts within the AWS Organization |
ACCOUNT_UNUSED_ACCESS |
The single AWS account (for unused access findings) |
ORGANIZATION_UNUSED_ACCESS |
All accounts in the AWS Organization |
- An
ACCOUNTanalyzer will flag cross-account access from your own sibling AWS accounts. This generates significant noise in environments with legitimate cross-account access patterns. For most enterprises, anORGANIZATIONanalyzer is correct. - With an
ORGANIZATIONanalyzer, access granted toarn:aws:iam::111122223333:role/DataPipelineRole(another account in your org) is considered internal and does not generate a finding. - Access granted to
arn:aws:iam::999999999999:root(an account outside your org) always generates a finding. - Access granted to
Principal: "*"always generates a finding even with an org-level analyzer, because*includes principals outside the org. The correct pattern for org-wide access is theaws:PrincipalOrgIDcondition key.
Access Analyzer supports two categories of analysis. Not all resource types support both.
| Resource Type | IAM Resource Type String | What Is Analyzed |
|---|---|---|
| S3 Buckets | AWS::S3::Bucket |
Bucket policy, bucket ACL, access points, multi-region access points |
| S3 Access Points | AWS::S3::AccessPoint |
Access point policy |
| S3 Multi-Region Access Points | AWS::S3::MultiRegionAccessPoint |
MRAP policy |
| IAM Roles | AWS::IAM::Role |
Role trust policy (who can assume the role) |
| KMS Keys | AWS::KMS::Key |
Key policy |
| Lambda Functions | AWS::Lambda::Function |
Function resource-based policy |
| Lambda Layers | AWS::Lambda::LayerVersion |
Layer permission policy |
| SQS Queues | AWS::SQS::Queue |
Queue policy |
| SNS Topics | AWS::SNS::Topic |
Topic policy |
| Secrets Manager Secrets | AWS::SecretsManager::Secret |
Secret resource policy |
| EFS File Systems | AWS::EFS::FileSystem |
File system policy |
| DynamoDB Streams | AWS::DynamoDB::Stream |
Resource policy on a DynamoDB stream |
| DynamoDB Tables | AWS::DynamoDB::Table |
Resource policy on a DynamoDB table |
| ECR Repositories | AWS::ECR::Repository |
Repository policy |
| IAM Identity Center Instances | AWS::SSO::Instance |
Permission set policies |
Unused access analysis operates on IAM entities rather than resource-level policies:
| Entity Type | What Is Analyzed |
|---|---|
| IAM Roles | Whether the role has been assumed within the lookback window |
| IAM Users | Whether the user has accessed the console or used access keys |
| IAM Access Keys | Whether each access key (up to 2 per user) has been used |
| IAM Role Permissions | Which services and actions within attached policies have been used (via CloudTrail data) |
Generated when a resource policy allows access from a principal outside the zone of trust. Each finding includes:
- Resource ARN — the specific resource with external access.
- Resource type — e.g.,
AWS::S3::Bucket. - Principal — the external principal granted access (an AWS account ID, IAM ARN, AWS service, federated identity, or
*). - Action — the specific IAM actions the external principal can perform.
- Condition — any IAM conditions that apply (e.g.,
aws:SourceIp). If conditions exist, the finding is marked as conditional access rather than unrestricted access. - Is public — boolean indicating whether the resource is publicly accessible (i.e., accessible by any unauthenticated internet user).
Example finding (JSON):
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"analyzedAt": "2025-10-15T14:32:00Z",
"createdAt": "2025-10-15T14:32:00Z",
"updatedAt": "2025-10-15T14:32:00Z",
"status": "ACTIVE",
"resourceType": "AWS::S3::Bucket",
"resource": "arn:aws:s3:::my-data-bucket",
"resourceOwnerAccount": "123456789012",
"findingType": "ExternalAccess",
"principal": {
"AWS": "arn:aws:iam::999999999999:root"
},
"action": ["s3:GetObject", "s3:ListBucket"],
"isPublic": false,
"condition": {}
}Generated when IAM entities or their permissions have not been used within the configured lookback window (7 to 180 days, default 90 days).
Unused access finding subtypes:
| Subtype | Description |
|---|---|
UNUSED_PERMISSION |
A specific IAM action or service in the entity's policy has never been used within the lookback window. |
UNUSED_IAM_ROLE |
The IAM role has not been assumed at all within the lookback window. |
UNUSED_IAM_USER_ACCESS_KEY |
An IAM user access key has not been used within the lookback window. |
UNUSED_IAM_USER_PASSWORD |
An IAM user has not signed into the console within the lookback window. |
Example unused access finding:
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"findingType": "UnusedAccess",
"resourceType": "AWS::IAM::Role",
"resource": "arn:aws:iam::123456789012:role/OldBatchProcessingRole",
"unusedAccess": {
"unusedPermissionsInfo": {
"lastAccessed": "2025-07-01T00:00:00Z",
"serviceNamespace": "ec2"
},
"unusedIamRoleInfo": null
}
}The ValidatePolicy API is a separate capability that checks a policy document against AWS policy grammar and security best practices. It does not require an analyzer to be configured. Validation findings use severity levels:
| Severity | Meaning |
|---|---|
ERROR |
Policy contains a syntax error and will be rejected by IAM. |
SECURITY_WARNING |
Policy grants overly broad permissions (e.g., iam:*, Resource: *). |
WARNING |
Policy uses deprecated features or patterns. |
SUGGESTION |
Minor improvements available. |
When an analyzer is created, it scans all supported resources in the region. For an organization-level analyzer, this covers all accounts in the organization. The initial scan can take from minutes to hours depending on the size of the environment.
After the initial scan, Access Analyzer subscribes to:
- CloudTrail management events — any
Put*,Create*,Update*,Delete*,Attach*,Detach*,Set*API calls that could change resource policies or IAM entity configurations. - AWS Config configuration change events — for resource types monitored by AWS Config.
When a relevant event is detected, the affected resource is re-analyzed. Finding updates propagate to EventBridge within minutes.
In addition to event-driven updates, Access Analyzer performs periodic re-scans of all resources. This catches scenarios where a policy change does not emit a CloudTrail event (rare but possible with some older APIs) or where eventual consistency means the initial event-driven analysis saw stale state.
If a resource policy changes in a way that removes the external access:
- The finding status transitions to
RESOLVED. - The finding is retained in the console and API for audit history.
If a resource policy changes in a way that modifies (but does not remove) the external access:
- The existing finding is updated in place (same finding ID) with the new details.
┌─────────┐
Created ──► │ ACTIVE │
└────┬────┘
│
┌───────────┴──────────┐
│ │
Manual archive Archive rule
or policy fix matches finding
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ RESOLVED │ │ ARCHIVED │
└──────────┘ └────┬─────┘
│
Policy re-opens
the access
│
▼
┌─────────┐
│ ACTIVE │
└─────────┘
| State | Description |
|---|---|
ACTIVE |
The finding is current and the external access or unused access exists. Requires attention. |
ARCHIVED |
The finding has been suppressed — either manually or via an archive rule. The underlying access may still exist. Archived findings do not appear in the default filtered view. |
RESOLVED |
The access has been removed. Access Analyzer confirmed the resource policy no longer grants the flagged access. Finding is retained for audit history. |
Important: Archiving a finding does NOT remove the underlying access. It suppresses the alert. Only modifying the resource policy or removing the IAM entity resolves the underlying issue.
Archive rules automatically archive findings that match specified filter criteria. They are used to suppress findings for known-good, intentional cross-account or cross-org access so that the active findings list represents only genuine issues requiring attention.
- A trusted partner account that legitimately reads from your S3 bucket (contractual obligation, data sharing agreement).
- An AWS service principal (
logs.amazonaws.com,delivery.logs.amazonaws.com) that writes to S3 for logging. - A specific cross-account role assumption that is part of your organization's support model.
Archive rules support filtering on:
| Filter Field | Example Values |
|---|---|
principal.AWS |
arn:aws:iam::999999999999:role/PartnerReadRole |
principal.Federated |
cognito-identity.amazonaws.com |
principal.Service |
delivery.logs.amazonaws.com |
resourceType |
AWS::S3::Bucket |
resource |
arn:aws:s3:::my-logging-bucket |
isPublic |
true or false |
error |
Any error code |
status |
ACTIVE, ARCHIVED, RESOLVED |
Filters support eq (equals), neq (not equals), contains, and exists operators.
aws accessanalyzer create-archive-rule \
--analyzer-name org-analyzer \
--rule-name partner-s3-read-access \
--filter '{
"principal.AWS": {
"eq": ["arn:aws:iam::999999999999:role/PartnerReadRole"]
},
"resourceType": {
"eq": ["AWS::S3::Bucket"]
}
}' \
--region us-east-1- Be specific. Scope rules as narrowly as possible — use both
principal.AWSandresourcefilters together, not justprincipal.AWS. - Document every archive rule. Add a comment or tag to the analyzer with the business justification, owner, and review date for each archive rule.
- Review archive rules quarterly. Access patterns change; archived findings for decommissioned partner integrations become dangerously invisible.
- Do not archive findings for
isPublic: trueunless the resource is intentionally public (e.g., a static website bucket). Even then, document the justification explicitly. - Use separate archive rules for separate justifications. Avoid building a single rule that suppresses broad categories of findings.
Archive rules are for ongoing suppression of known-good access patterns. Access Preview (a separate Access Analyzer feature) is for pre-deployment validation — modeling what findings would look like if a proposed policy were applied. Use Access Preview in deployment pipelines; use archive rules for post-deployment noise reduction.
| Limitation | Detail |
|---|---|
| Regional scope | Each analyzer is regional. You need analyzers in every region where you have resources, or use the AWS Security Hub integration to aggregate findings. |
| IAM evaluation order | Zelkova evaluates the same logic as IAM's authorization engine. Explicit Deny always wins; implicit Deny (no matching Allow) is handled correctly. |
| VPC endpoint conditions | aws:SourceVpc and aws:SourceVpce conditions are partially supported. Findings may be marked as conditional rather than definitively resolved. |
| Service-linked roles | Service-linked roles are analyzed for their trust policies but are not included in unused access analysis. |
| New services | Support for new resource types and condition keys is added over time. Check the AWS documentation for the current list of supported resources and condition keys. |
| CloudTrail dependency for unused access | Unused access analysis requires CloudTrail to be enabled in the account. If CloudTrail was recently enabled, the lookback window data may be incomplete. |
| Cross-region resource policies | Analysis is per-region. An S3 bucket in us-west-2 is analyzed by the us-west-2 analyzer, not the us-east-1 analyzer. |
| Propagation delay | Finding updates can take up to 30 minutes after a policy change in complex organizational environments due to event processing pipelines. |