The aws:iam/role:Role resource, part of the Pulumi AWS provider, defines an IAM role: its trust policy (who can assume it), name, and optional policy attachments. This guide focuses on two capabilities: trust policy configuration and managed policy attachment.
IAM roles reference AWS services or IAM principals in their trust policies and may attach managed policies created separately. The examples are intentionally small. Combine them with your own policy documents and permission requirements.
Create a role with an inline trust policy
Most deployments start by defining who can assume the role through a trust policy, establishing the identity relationship for AWS services or users.
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 document. The Principal.Service field specifies which AWS service can assume the role; here, EC2 instances gain the ability to act with this role’s permissions. The Action “sts:AssumeRole” grants the assume operation itself.
Generate trust policies with getPolicyDocument
Rather than writing JSON directly, the getPolicyDocument data source constructs trust policies programmatically, reducing syntax errors.
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 inputs. The statements array defines permissions; principals specify who can assume the role. The resulting JSON feeds into assumeRolePolicy, maintaining the same trust relationship as inline JSON but with type-safe construction.
Attach managed policies with exclusive control
When roles need permissions from reusable policy documents, managed policies provide centralized management with drift prevention.
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 lists policy ARNs to attach. Pulumi enforces this as the complete set: any policies attached outside your configuration are automatically detached on the next apply. This prevents permission drift but requires listing all intended policies explicitly. Note that managedPolicyArns is deprecated; for new projects, use the aws.iam.RolePolicyAttachment resource instead.
Beyond these examples
These snippets focus on specific role-level features: trust policy definition (inline JSON and data sources) and managed policy attachment with exclusive control. 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.). They focus on configuring the role rather than provisioning the services that use it.
To keep things focused, common role patterns are omitted, including:
- Inline policies (inlinePolicies property, deprecated)
- Permission boundaries (permissionsBoundary)
- Session duration limits (maxSessionDuration)
- Path-based organization (path property)
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 attempting the change. The recommended aws.iam.RolePolicyAttachment resource doesn’t have this requirement.managedPolicyArns or inlinePolicies causes the role to take exclusive management of those policy types, making them incompatible with aws.iam.PolicyAttachment, aws.iam.RolePolicyAttachment, and aws.iam.RolePolicy. Mixing these approaches will cause resource cycling and errors.aws.iam.RolePolicy for inline policies and aws.iam.RolePolicyAttachment for managed policies instead. For exclusive management, combine these with aws.iam.RolePoliciesExclusive or aws.iam.RolePolicyAttachmentsExclusive.Trust Policy & Assume Role
assumeRolePolicy is a trust policy that controls who can assume the role, not what the role can do. It’s similar to but different from standard IAM policies and cannot use an aws.iam.Policy resource. Instead, use aws.iam.getPolicyDocument data source or explicit JSON encoding.Configuration & Limits
maxSessionDuration can range from 1 hour to 12 hours. If not specified, the default maximum of one hour is applied.name and path properties are immutable. Changing them will force replacement of the role.