Implementing Zero Trust Architecture on AWS
Traditional network security assumes that traffic inside the perimeter is trusted. Zero trust eliminates that assumption — every request is verified regardless of where it originates. AWS provides the building blocks to implement zero trust, but assembling them correctly requires understanding how identity, network, and data controls interact.
Identity-Centric Access
Zero trust starts with identity. On AWS, this means eliminating implicit trust based on network location and requiring verified identity for every API call.
Eliminating Long-Lived Credentials
import boto3
import json
def enforce_credential_hygiene():
"""Audit and enforce short-lived credential usage"""
iam = boto3.client('iam')
findings = []
# Find all users with long-lived access keys
paginator = iam.get_paginator('list_users')
for page in paginator.paginate():
for user in page['Users']:
keys = iam.list_access_keys(UserName=user['UserName'])
for key in keys['AccessKeyMetadata']:
age_days = (datetime.now(key['CreateDate'].tzinfo) - key['CreateDate']).days
if age_days > 90:
findings.append({
'user': user['UserName'],
'key_id': key['AccessKeyId'],
'age_days': age_days,
'recommendation': 'Migrate to IAM role with temporary credentials'
})
# Check for users without MFA
for page in paginator.paginate():
for user in page['Users']:
mfa_devices = iam.list_mfa_devices(UserName=user['UserName'])
if not mfa_devices['MFADevices']:
findings.append({
'user': user['UserName'],
'issue': 'No MFA device configured',
'recommendation': 'Enforce MFA via IAM policy condition'
})
return findings
Zero trust credential principles:
- Use IAM roles everywhere — EC2 instance profiles, ECS task roles, Lambda execution roles. No access keys.
- Enforce MFA — require
aws:MultiFactorAuthPresentcondition on all human-interactive sessions - Shorten session duration — reduce
MaxSessionDurationon roles from the default 1 hour to 15-30 minutes for sensitive operations - Rotate remaining keys — for the rare cases where keys are unavoidable, enforce 90-day rotation
Microsegmentation with Security Groups
Security groups act as per-instance firewalls. In a zero trust model, every workload gets its own security group with the minimum required connectivity.
Application-Tier Segmentation
Resources:
# Web tier can only receive traffic from the ALB
WebTierSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Web tier - ALB ingress only
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
# App tier can only receive traffic from the web tier
AppTierSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: App tier - web tier ingress only
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8443
ToPort: 8443
SourceSecurityGroupId: !Ref WebTierSecurityGroup
# Database tier can only receive traffic from the app tier
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Database tier - app tier ingress only
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref AppTierSecurityGroup
# Explicitly deny all other ingress (default behavior)
# No egress rules needed for RDS
The key insight: security groups reference other security groups, not CIDR blocks. This means access follows identity (which group a resource belongs to), not network location.
Continuous Verification with IAM Conditions
Zero trust requires that every request is evaluated in context. IAM conditions let you enforce checks on every API call.
Context-Aware IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowWithContinuousVerification",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
},
"NumericLessThan": {
"aws:MultiFactorAuthAge": "3600"
},
"IpAddress": {
"aws:SourceIp": ["10.0.0.0/8", "172.16.0.0/12"]
},
"StringEquals": {
"aws:RequestedRegion": ["us-east-1", "us-east-2"]
},
"DateGreaterThan": {
"aws:CurrentTime": "2025-01-01T08:00:00Z"
},
"DateLessThan": {
"aws:CurrentTime": "2025-12-31T20:00:00Z"
}
}
}
]
}
This single policy enforces:
- MFA is active and was authenticated within the last hour
- Request originates from a private network (not the public internet)
- Request targets an approved region (prevents data exfiltration to foreign regions)
- Request occurs during business hours (limits off-hours abuse of compromised credentials)
Session Policies for Blast Radius Reduction
When a role is assumed, you can pass a session policy that further restricts what the temporary credentials can do — even if the role itself has broader permissions.
import boto3
sts = boto3.client('sts')
def assume_scoped_role(role_arn, task_bucket, task_prefix):
"""Assume a role with session policy scoped to a specific S3 prefix"""
session_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": f"arn:aws:s3:::{task_bucket}/{task_prefix}/*"
}
]
}
response = sts.assume_role(
RoleArn=role_arn,
RoleSessionName='scoped-data-processing',
Policy=json.dumps(session_policy),
DurationSeconds=900 # 15 minutes
)
return boto3.Session(
aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token=response['Credentials']['SessionToken']
)
The effective permissions are the intersection of the role's policies and the session policy. Even if the role has s3:* on *, the session can only access one specific prefix for 15 minutes.
AWS Verified Access
AWS Verified Access replaces VPNs for internal application access. It evaluates identity and device trust on every connection, not just at session establishment.
Key capabilities:
- Identity trust providers — integrates with AWS Identity Center, Okta, CrowdStrike, and other IdPs
- Device posture checks — verify endpoint security status before granting access
- Per-application policies — each internal application gets its own access policy
- No VPN required — users connect directly to applications through Verified Access endpoints
Implementing Zero Trust with AccessLens
Zero trust on AWS depends entirely on correct IAM configuration. A single overpermissive role, a missing MFA condition, or a wildcard resource grant can undermine your entire zero trust architecture.
AccessLens helps enforce zero trust by providing:
- Continuous IAM verification that identifies policies missing MFA, source IP, or region conditions
- Blast radius analysis that maps the effective permissions of every role and session
- Trust relationship visualization that reveals implicit trust paths across accounts
- Privilege escalation detection that finds roles capable of bypassing your zero trust controls
Zero trust is not a product — it is a continuous process of verification. AccessLens provides the IAM visibility foundation that makes zero trust enforceable across your AWS organization.
Build zero trust on a solid foundation with AccessLens and verify that your IAM controls actually enforce the trust model you designed.