Overview
The IAM Role resource provides identity-based access control for AWS services and resources. It is a thin wrapper around the CDK IAM Role with the same properties and methods, used throughout Fjall for service permissions.Resource Class
import { Role } from "@fjall/components-infrastructure/lib/resources/aws/iam/role";
Basic Usage
const role = new Role(this, "MyRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
description: "Lambda execution role",
});
Configuration Options
All standard CDK Role properties are supported:| Property | Type | Description |
|---|---|---|
assumedBy | IPrincipal | Principal that can assume this role |
description | string | Role description |
roleName | string | Custom role name |
managedPolicies | IManagedPolicy[] | AWS managed policies |
inlinePolicies | {[name: string]: PolicyDocument} | Inline policies |
maxSessionDuration | Duration | Maximum session duration |
path | string | Path for the role |
Common Patterns
Lambda Execution Role
const lambdaRole = new Role(this, "LambdaRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
description: "Lambda function execution role",
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicExecutionRole",
),
],
inlinePolicies: {
S3Access: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ["s3:GetObject", "s3:PutObject"],
resources: ["arn:aws:s3:::my-bucket/*"],
}),
],
}),
},
});
ECS Task Role
const taskRole = new Role(this, "TaskRole", {
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"),
description: "ECS task role",
inlinePolicies: {
SecretsAccess: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ["secretsmanager:GetSecretValue", "kms:Decrypt"],
resources: ["*"],
}),
],
}),
},
});
EC2 Instance Role
const instanceRole = new Role(this, "InstanceRole", {
assumedBy: new ServicePrincipal("ec2.amazonaws.com"),
description: "EC2 instance role",
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"),
],
});
Service Principals
AWS Service Principals
// Lambda
new ServicePrincipal("lambda.amazonaws.com");
// ECS Tasks
new ServicePrincipal("ecs-tasks.amazonaws.com");
// EC2
new ServicePrincipal("ec2.amazonaws.com");
// CodeBuild
new ServicePrincipal("codebuild.amazonaws.com");
// Step Functions
new ServicePrincipal("states.amazonaws.com");
// Glue
new ServicePrincipal("glue.amazonaws.com");
Federated Principals
// GitHub Actions OIDC
const githubRole = new Role(this, "GitHubActionsRole", {
assumedBy: new OpenIdConnectPrincipal(oidcProvider, {
StringEquals: {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub":
"repo:myorg/myrepo:ref:refs/heads/main",
},
}),
description: "GitHub Actions deployment role",
});
Account Principals
// Cross-account access
const crossAccountRole = new Role(this, "CrossAccountRole", {
assumedBy: new AccountPrincipal("123456789012"),
description: "Cross-account access role",
});
Inline Policies
Single Statement
const role = new Role(this, "SinglePolicyRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
inlinePolicies: {
DynamoAccess: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ["dynamodb:GetItem", "dynamodb:PutItem"],
resources: [table.tableArn],
}),
],
}),
},
});
Multiple Policies
const role = new Role(this, "MultiPolicyRole", {
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"),
inlinePolicies: {
S3Access: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ["s3:GetObject"],
resources: ["arn:aws:s3:::config-bucket/*"],
}),
],
}),
SQSAccess: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ["sqs:SendMessage", "sqs:ReceiveMessage"],
resources: [queue.queueArn],
}),
],
}),
SecretsAccess: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ["secretsmanager:GetSecretValue"],
resources: [secret.secretArn],
}),
],
}),
},
});
Managed Policies
AWS Managed Policies
const role = new Role(this, "ManagedPolicyRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicExecutionRole",
),
ManagedPolicy.fromAwsManagedPolicyName("AWSXRayDaemonWriteAccess"),
],
});
Customer Managed Policies
const customPolicy = new ManagedPolicy(this, "CustomPolicy", {
statements: [
new PolicyStatement({
actions: ["s3:ListBucket"],
resources: ["arn:aws:s3:::my-bucket"],
}),
],
});
const role = new Role(this, "CustomManagedRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [customPolicy],
});
Adding Permissions
Add to Policy
const role = new Role(this, "ExpandableRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
});
// Add permissions later
role.addToPolicy(
new PolicyStatement({
actions: ["s3:GetObject"],
resources: ["arn:aws:s3:::my-bucket/*"],
}),
);
// Add managed policy
role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess"));
Grant Methods
const role = new Role(this, "GrantRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
});
// Use grant methods from resources
bucket.grantRead(role);
table.grantReadWriteData(role);
secret.grantRead(role);
topic.grantPublish(role);
Trust Relationships
Basic Trust Policy
const role = new Role(this, "TrustRole", {
assumedBy: new CompositePrincipal(
new ServicePrincipal("lambda.amazonaws.com"),
new ServicePrincipal("events.amazonaws.com"),
),
});
Conditional Trust
const role = new Role(this, "ConditionalRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com").withConditions({
StringEquals: {
"sts:ExternalId": "unique-external-id",
},
}),
});
Session Tags
const role = new Role(this, "SessionTagRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(),
description: "Role with session tag support",
});
Role Boundaries
Permission Boundaries
const boundary = ManagedPolicy.fromManagedPolicyArn(
this,
"Boundary",
"arn:aws:iam::123456789012:policy/BoundaryPolicy",
);
const role = new Role(this, "BoundedRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
permissionsBoundary: boundary,
});
Integration Examples
With Lambda Function
const executionRole = new Role(this, "LambdaExecutionRole", {
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicExecutionRole",
),
],
});
const fn = new Function(this, "MyFunction", {
runtime: Runtime.NODEJS_18_X,
handler: "index.handler",
code: Code.fromAsset("lambda"),
role: executionRole,
});
With ECS Task
const taskRole = new Role(this, "TaskRole", {
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"),
});
const executionRole = new Role(this, "ExecutionRole", {
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AmazonECSTaskExecutionRolePolicy",
),
],
});
const taskDefinition = new FargateTaskDefinition(this, "TaskDef", {
taskRole: taskRole,
executionRole: executionRole,
});
With CodeBuild
const buildRole = new Role(this, "CodeBuildRole", {
assumedBy: new ServicePrincipal("codebuild.amazonaws.com"),
inlinePolicies: {
BuildPolicy: new PolicyDocument({
statements: [
new PolicyStatement({
actions: [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
resources: ["*"],
}),
new PolicyStatement({
actions: ["ecr:*"],
resources: [repository.repositoryArn],
}),
],
}),
},
});
Complete Example
import { Role } from "@fjall/components-infrastructure/lib/resources/aws/iam/role";
import {
ServicePrincipal,
ManagedPolicy,
PolicyDocument,
PolicyStatement,
CompositePrincipal,
} from "aws-cdk-lib/aws-iam";
// Application service role with full permissions
const appServiceRole = new Role(this, "ApplicationServiceRole", {
roleName: `${props.appName}-service-role-${props.environment}`,
description: "Main application service role",
// Multiple services can assume this role
assumedBy: new CompositePrincipal(
new ServicePrincipal("lambda.amazonaws.com"),
new ServicePrincipal("ecs-tasks.amazonaws.com"),
),
// AWS managed policies
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicExecutionRole",
),
ManagedPolicy.fromAwsManagedPolicyName("AWSXRayDaemonWriteAccess"),
],
// Inline policies for specific access
inlinePolicies: {
DatabaseAccess: new PolicyDocument({
statements: [
new PolicyStatement({
sid: "RDSDataAccess",
actions: [
"rds-data:ExecuteStatement",
"rds-data:BatchExecuteStatement",
],
resources: ["arn:aws:rds:us-east-1:123456789012:cluster:api-db"],
}),
],
}),
StorageAccess: new PolicyDocument({
statements: [
new PolicyStatement({
sid: "S3BucketAccess",
actions: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
resources: [`${bucket.bucketArn}/*`],
}),
new PolicyStatement({
sid: "S3ListAccess",
actions: ["s3:ListBucket"],
resources: [bucket.bucketArn],
conditions: {
StringLike: {
"s3:prefix": ["uploads/*", "processed/*"],
},
},
}),
],
}),
SecretsAccess: new PolicyDocument({
statements: [
new PolicyStatement({
sid: "GetSecrets",
actions: [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
],
resources: [appSecret.secretArn, dbSecret.secretArn],
}),
new PolicyStatement({
sid: "KMSDecrypt",
actions: ["kms:Decrypt", "kms:DescribeKey"],
resources: ["*"],
conditions: {
StringEquals: {
"kms:ViaService": `secretsmanager.${this.region}.amazonaws.com`,
},
},
}),
],
}),
},
// Session duration
maxSessionDuration: Duration.hours(1),
});
// Grant additional permissions from resources
dynamoTable.grantReadWriteData(appServiceRole);
queue.grantConsumeMessages(appServiceRole);
topic.grantPublish(appServiceRole);
// Add tags for compliance
Tags.of(appServiceRole).add("Environment", props.environment);
Tags.of(appServiceRole).add("Application", props.appName);
Tags.of(appServiceRole).add("ManagedBy", "Fjall");
// Output role ARN
new CfnOutput(this, "ServiceRoleArn", {
value: appServiceRole.roleArn,
description: "Application service role ARN",
});
Best Practices
- Use least privilege - Only grant required permissions
- Prefer managed policies for common patterns
- Use conditions to restrict access further
- Enable MFA for sensitive roles
- Set appropriate session duration
- Use permission boundaries in multi-tenant environments
- Tag roles for cost allocation and compliance
Security Considerations
Avoid Wildcard Permissions
// Bad - Too permissive
new PolicyStatement({
actions: ["*"],
resources: ["*"],
});
// Good - Specific permissions
new PolicyStatement({
actions: ["s3:GetObject", "s3:PutObject"],
resources: ["arn:aws:s3:::my-bucket/uploads/*"],
});
Use Conditions
// IP restrictions
new PolicyStatement({
actions: ["s3:*"],
resources: ["*"],
conditions: {
IpAddress: {
"aws:SourceIp": ["203.0.113.0/24"],
},
},
});
// Time-based access
new PolicyStatement({
actions: ["ec2:TerminateInstances"],
resources: ["*"],
conditions: {
DateGreaterThan: {
"aws:CurrentTime": "2026-01-01T00:00:00Z",
},
DateLessThan: {
"aws:CurrentTime": "2026-12-31T23:59:59Z",
},
},
});
Troubleshooting
Common Issues
- Access denied: Check trust policy and permissions
- Invalid principal: Verify service principal format
- Policy size limit: Use managed policies for large policies
- Circular dependencies: Use
addToPolicyafter creation
Debug Commands
# Get role details
aws iam get-role --role-name MyRole
# List role policies
aws iam list-role-policies --role-name MyRole
aws iam list-attached-role-policies --role-name MyRole
# Simulate policy
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/MyRole \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::my-bucket/*
Next Steps
Secrets Manager
Store and grant access to credentials and API keys.
KMS Key
Encrypt data and scope decryption to specific roles.
Lambda Function
Attach an execution role to a serverless function.
ECS Cluster
Assign task and execution roles to containers.