The aws:iam/role:Role resource, part of the Pulumi AWS provider, defines an IAM role: its trust policy, name, and optional policy attachments. This guide focuses on two capabilities: trust policy configuration and managed policy attachment.
IAM roles require a trust policy that specifies which AWS services or principals can assume the role. Roles may also reference managed policies for permissions. The examples are intentionally small. Combine them with your own policy documents and permission requirements.
Create a role with an assume role policy
Most deployments start by defining who can assume the role through a trust policy.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const testRole = new aws.iam.Role("test_role", {
name: "test_role",
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Sid: "",
Principal: {
Service: "ec2.amazonaws.com",
},
}],
}),
tags: {
"tag-key": "tag-value",
},
});
import pulumi
import json
import pulumi_aws as aws
test_role = aws.iam.Role("test_role",
name="test_role",
assume_role_policy=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Sid": "",
"Principal": {
"Service": "ec2.amazonaws.com",
},
}],
}),
tags={
"tag-key": "tag-value",
})
package main
import (
"encoding/json"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
tmpJSON0, err := json.Marshal(map[string]interface{}{
"Version": "2012-10-17",
"Statement": []map[string]interface{}{
map[string]interface{}{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Sid": "",
"Principal": map[string]interface{}{
"Service": "ec2.amazonaws.com",
},
},
},
})
if err != nil {
return err
}
json0 := string(tmpJSON0)
_, err = iam.NewRole(ctx, "test_role", &iam.RoleArgs{
Name: pulumi.String("test_role"),
AssumeRolePolicy: pulumi.String(json0),
Tags: pulumi.StringMap{
"tag-key": pulumi.String("tag-value"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var testRole = new Aws.Iam.Role("test_role", new()
{
Name = "test_role",
AssumeRolePolicy = JsonSerializer.Serialize(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Statement"] = new[]
{
new Dictionary<string, object?>
{
["Action"] = "sts:AssumeRole",
["Effect"] = "Allow",
["Sid"] = "",
["Principal"] = new Dictionary<string, object?>
{
["Service"] = "ec2.amazonaws.com",
},
},
},
}),
Tags =
{
{ "tag-key", "tag-value" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import static com.pulumi.codegen.internal.Serialization.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var testRole = new Role("testRole", RoleArgs.builder()
.name("test_role")
.assumeRolePolicy(serializeJson(
jsonObject(
jsonProperty("Version", "2012-10-17"),
jsonProperty("Statement", jsonArray(jsonObject(
jsonProperty("Action", "sts:AssumeRole"),
jsonProperty("Effect", "Allow"),
jsonProperty("Sid", ""),
jsonProperty("Principal", jsonObject(
jsonProperty("Service", "ec2.amazonaws.com")
))
)))
)))
.tags(Map.of("tag-key", "tag-value"))
.build());
}
}
resources:
testRole:
type: aws:iam:Role
name: test_role
properties:
name: test_role
assumeRolePolicy:
fn::toJSON:
Version: 2012-10-17
Statement:
- Action: sts:AssumeRole
Effect: Allow
Sid: ""
Principal:
Service: ec2.amazonaws.com
tags:
tag-key: tag-value
The assumeRolePolicy property contains a JSON trust policy that grants permission to assume the role. Here, the Principal specifies that EC2 instances can assume this role. The Action “sts:AssumeRole” is required in all trust policies. Without additional policies attached, this role grants no permissions to AWS resources.
Generate assume role policies with getPolicyDocument
Rather than writing JSON inline, the getPolicyDocument data source constructs trust policies programmatically.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const instanceAssumeRolePolicy = aws.iam.getPolicyDocument({
statements: [{
actions: ["sts:AssumeRole"],
principals: [{
type: "Service",
identifiers: ["ec2.amazonaws.com"],
}],
}],
});
const instance = new aws.iam.Role("instance", {
name: "instance_role",
path: "/system/",
assumeRolePolicy: instanceAssumeRolePolicy.then(instanceAssumeRolePolicy => instanceAssumeRolePolicy.json),
});
import pulumi
import pulumi_aws as aws
instance_assume_role_policy = aws.iam.get_policy_document(statements=[{
"actions": ["sts:AssumeRole"],
"principals": [{
"type": "Service",
"identifiers": ["ec2.amazonaws.com"],
}],
}])
instance = aws.iam.Role("instance",
name="instance_role",
path="/system/",
assume_role_policy=instance_assume_role_policy.json)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
instanceAssumeRolePolicy, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Actions: []string{
"sts:AssumeRole",
},
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "Service",
Identifiers: []string{
"ec2.amazonaws.com",
},
},
},
},
},
}, nil)
if err != nil {
return err
}
_, err = iam.NewRole(ctx, "instance", &iam.RoleArgs{
Name: pulumi.String("instance_role"),
Path: pulumi.String("/system/"),
AssumeRolePolicy: pulumi.String(instanceAssumeRolePolicy.Json),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var instanceAssumeRolePolicy = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Actions = new[]
{
"sts:AssumeRole",
},
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "Service",
Identifiers = new[]
{
"ec2.amazonaws.com",
},
},
},
},
},
});
var instance = new Aws.Iam.Role("instance", new()
{
Name = "instance_role",
Path = "/system/",
AssumeRolePolicy = instanceAssumeRolePolicy.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
final var instanceAssumeRolePolicy = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.actions("sts:AssumeRole")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("Service")
.identifiers("ec2.amazonaws.com")
.build())
.build())
.build());
var instance = new Role("instance", RoleArgs.builder()
.name("instance_role")
.path("/system/")
.assumeRolePolicy(instanceAssumeRolePolicy.json())
.build());
}
}
resources:
instance:
type: aws:iam:Role
properties:
name: instance_role
path: /system/
assumeRolePolicy: ${instanceAssumeRolePolicy.json}
variables:
instanceAssumeRolePolicy:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- actions:
- sts:AssumeRole
principals:
- type: Service
identifiers:
- ec2.amazonaws.com
The getPolicyDocument data source builds the trust policy from structured configuration. The statements array defines who can assume the role; principals specifies the AWS service. This approach avoids JSON formatting issues and enables reuse across multiple roles.
Attach managed policies directly to the role
Roles need permissions from AWS-managed or custom-managed policies to access AWS services.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const policyOne = new aws.iam.Policy("policy_one", {
name: "policy-618033",
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: ["ec2:Describe*"],
Effect: "Allow",
Resource: "*",
}],
}),
});
const policyTwo = new aws.iam.Policy("policy_two", {
name: "policy-381966",
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: [
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:HeadBucket",
],
Effect: "Allow",
Resource: "*",
}],
}),
});
const example = new aws.iam.Role("example", {
name: "yak_role",
assumeRolePolicy: instanceAssumeRolePolicy.json,
managedPolicyArns: [
policyOne.arn,
policyTwo.arn,
],
});
import pulumi
import json
import pulumi_aws as aws
policy_one = aws.iam.Policy("policy_one",
name="policy-618033",
policy=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Action": ["ec2:Describe*"],
"Effect": "Allow",
"Resource": "*",
}],
}))
policy_two = aws.iam.Policy("policy_two",
name="policy-381966",
policy=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Action": [
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:HeadBucket",
],
"Effect": "Allow",
"Resource": "*",
}],
}))
example = aws.iam.Role("example",
name="yak_role",
assume_role_policy=instance_assume_role_policy["json"],
managed_policy_arns=[
policy_one.arn,
policy_two.arn,
])
package main
import (
"encoding/json"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
tmpJSON0, err := json.Marshal(map[string]interface{}{
"Version": "2012-10-17",
"Statement": []map[string]interface{}{
map[string]interface{}{
"Action": []string{
"ec2:Describe*",
},
"Effect": "Allow",
"Resource": "*",
},
},
})
if err != nil {
return err
}
json0 := string(tmpJSON0)
policyOne, err := iam.NewPolicy(ctx, "policy_one", &iam.PolicyArgs{
Name: pulumi.String("policy-618033"),
Policy: pulumi.String(json0),
})
if err != nil {
return err
}
tmpJSON1, err := json.Marshal(map[string]interface{}{
"Version": "2012-10-17",
"Statement": []map[string]interface{}{
map[string]interface{}{
"Action": []string{
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:HeadBucket",
},
"Effect": "Allow",
"Resource": "*",
},
},
})
if err != nil {
return err
}
json1 := string(tmpJSON1)
policyTwo, err := iam.NewPolicy(ctx, "policy_two", &iam.PolicyArgs{
Name: pulumi.String("policy-381966"),
Policy: pulumi.String(json1),
})
if err != nil {
return err
}
_, err = iam.NewRole(ctx, "example", &iam.RoleArgs{
Name: pulumi.String("yak_role"),
AssumeRolePolicy: pulumi.Any(instanceAssumeRolePolicy.Json),
ManagedPolicyArns: pulumi.StringArray{
policyOne.Arn,
policyTwo.Arn,
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var policyOne = new Aws.Iam.Policy("policy_one", new()
{
Name = "policy-618033",
PolicyDocument = JsonSerializer.Serialize(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Statement"] = new[]
{
new Dictionary<string, object?>
{
["Action"] = new[]
{
"ec2:Describe*",
},
["Effect"] = "Allow",
["Resource"] = "*",
},
},
}),
});
var policyTwo = new Aws.Iam.Policy("policy_two", new()
{
Name = "policy-381966",
PolicyDocument = JsonSerializer.Serialize(new Dictionary<string, object?>
{
["Version"] = "2012-10-17",
["Statement"] = new[]
{
new Dictionary<string, object?>
{
["Action"] = new[]
{
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:HeadBucket",
},
["Effect"] = "Allow",
["Resource"] = "*",
},
},
}),
});
var example = new Aws.Iam.Role("example", new()
{
Name = "yak_role",
AssumeRolePolicy = instanceAssumeRolePolicy.Json,
ManagedPolicyArns = new[]
{
policyOne.Arn,
policyTwo.Arn,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.iam.Policy;
import com.pulumi.aws.iam.PolicyArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import static com.pulumi.codegen.internal.Serialization.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var policyOne = new Policy("policyOne", PolicyArgs.builder()
.name("policy-618033")
.policy(serializeJson(
jsonObject(
jsonProperty("Version", "2012-10-17"),
jsonProperty("Statement", jsonArray(jsonObject(
jsonProperty("Action", jsonArray("ec2:Describe*")),
jsonProperty("Effect", "Allow"),
jsonProperty("Resource", "*")
)))
)))
.build());
var policyTwo = new Policy("policyTwo", PolicyArgs.builder()
.name("policy-381966")
.policy(serializeJson(
jsonObject(
jsonProperty("Version", "2012-10-17"),
jsonProperty("Statement", jsonArray(jsonObject(
jsonProperty("Action", jsonArray(
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:HeadBucket"
)),
jsonProperty("Effect", "Allow"),
jsonProperty("Resource", "*")
)))
)))
.build());
var example = new Role("example", RoleArgs.builder()
.name("yak_role")
.assumeRolePolicy(instanceAssumeRolePolicy.json())
.managedPolicyArns(
policyOne.arn(),
policyTwo.arn())
.build());
}
}
resources:
example:
type: aws:iam:Role
properties:
name: yak_role
assumeRolePolicy: ${instanceAssumeRolePolicy.json}
managedPolicyArns:
- ${policyOne.arn}
- ${policyTwo.arn}
policyOne:
type: aws:iam:Policy
name: policy_one
properties:
name: policy-618033
policy:
fn::toJSON:
Version: 2012-10-17
Statement:
- Action:
- ec2:Describe*
Effect: Allow
Resource: '*'
policyTwo:
type: aws:iam:Policy
name: policy_two
properties:
name: policy-381966
policy:
fn::toJSON:
Version: 2012-10-17
Statement:
- Action:
- s3:ListAllMyBuckets
- s3:ListBucket
- s3:HeadBucket
Effect: Allow
Resource: '*'
The managedPolicyArns property attaches managed policies by ARN. When configured, Pulumi ensures only the listed policies remain attached, removing any added out-of-band. Note that managedPolicyArns is deprecated; for new projects, use the aws.iam.RolePolicyAttachment resource instead for more flexible policy management.
Beyond these examples
These snippets focus on specific role-level features: trust policy definition (inline JSON and data sources) and managed policy attachment. They’re intentionally minimal rather than full IAM configurations.
The examples may reference pre-existing infrastructure such as AWS services that will assume the role (EC2, Lambda, etc.) and managed policies to attach (AWS-managed or custom). They focus on configuring the role rather than provisioning the surrounding IAM infrastructure.
To keep things focused, common role patterns are omitted, including:
- Inline policies (inlinePolicies property, now deprecated)
- Permissions boundaries (permissionsBoundary)
- Session duration limits (maxSessionDuration)
- Force detach behavior (forceDetachPolicies)
These omissions are intentional: the goal is to illustrate how each role feature is wired, not provide drop-in IAM modules. See the IAM Role resource reference for all available configuration options.
Let's create and Configure IAM Roles
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Policy Management & Conflicts
aws.iam.PolicyAttachment and modifying the role’s name or path, you must set forceDetachPolicies to true and apply it before the rename. The recommended aws.iam.RolePolicyAttachment resource doesn’t have this requirement.managedPolicyArns or inlinePolicies on the role makes it take exclusive management of those policy types, which conflicts with separate resources like aws.iam.PolicyAttachment, aws.iam.RolePolicyAttachment, and aws.iam.RolePolicy. Choose one approach and stick with it.managedPolicyArns with an empty array ([]) to remove all managed policy attachments, or inlinePolicies with an empty block ([{}]) to remove all inline policies added outside Pulumi.Assume Role Policy Configuration
assumeRolePolicy is a trust policy with different structure than standard IAM policies. Use aws.iam.getPolicyDocument data source or explicit JSON encoding instead.aws.iam.getPolicyDocument data source and pass its json output to assumeRolePolicy. This maintains consistency and avoids JSON formatting issues.assumeRolePolicy is a trust policy that defines who can assume the role (principals), while regular IAM policies define what actions the role can perform (permissions).Role Properties & Limits
maxSessionDuration can range from 1 hour to 12 hours. If not specified, it defaults to 1 hour.name and path are immutable. Changing them requires recreating the role. If using aws.iam.PolicyAttachment, you must also set forceDetachPolicies to true first.Deprecations & Recommended Alternatives
aws.iam.RolePolicyAttachment for managed policies and aws.iam.RolePolicy for inline policies instead. For exclusive management, add aws.iam.RolePolicyAttachmentsExclusive or aws.iam.RolePoliciesExclusive.aws.iam.RolePolicyAttachment is recommended and doesn’t require forceDetachPolicies when modifying role names or paths. aws.iam.PolicyAttachment has this limitation and should be avoided.