The aws:iam/role:Role resource, part of the Pulumi AWS provider, provisions an IAM role that defines trust relationships (who can assume it) and optionally manages attached policies. This guide focuses on two capabilities: assume role policy configuration and deprecated exclusive policy management patterns.
IAM roles don’t grant permissions on their own. They define trust relationships through assume role policies, then gain permissions through attached policies (managed or inline). The examples show deprecated policy management patterns; for new projects, use companion resources like aws.iam.RolePolicyAttachment and aws.iam.RolePolicy instead. The examples reference AWS services in trust policies and may attach separately-created managed policies.
Create a role with an assume role policy
Most deployments start by defining who can assume the role. EC2, Lambda, and other services need this permission before accessing AWS resources.
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 defines the trust relationship using a JSON policy document. The Principal block specifies which AWS service can assume the role. Here, EC2 instances can assume the role and inherit whatever permissions are later attached to it.
Build assume role policies with a data source
Teams managing multiple roles often generate assume role policies programmatically, avoiding JSON string formatting.
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 aws.iam.getPolicyDocument data source builds the policy document from structured configuration. The statements array defines trust relationships; principals specify who can assume the role. This approach enables validation and reuse across multiple roles without copying JSON strings.
Attach managed policies for exclusive control
Roles gain permissions by attaching AWS-managed or custom managed policies. The managedPolicyArns property provides exclusive control, removing policies attached outside Pulumi.
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: '*'
When managedPolicyArns is set, Pulumi enforces that only the listed policies remain attached. Any policies added manually or by other tools are detached on the next apply. Note that managedPolicyArns is deprecated; for new deployments, use aws.iam.RolePolicyAttachment with aws.iam.RolePolicyAttachmentsExclusive for the same behavior.
Beyond These Examples
These snippets focus on specific role-level features: assume role policy definition (inline JSON and data source) and deprecated exclusive policy management (inline and managed). They’re intentionally minimal rather than complete IAM configurations.
The examples may reference pre-existing infrastructure such as AWS services that will assume the role (EC2, Lambda, etc.) and managed policies for attachment examples. They focus on configuring the role rather than building complete permission models.
To keep things focused, common role patterns are omitted, including:
- Permissions boundaries (permissionsBoundary)
- Session duration limits (maxSessionDuration)
- Role paths for organization (path)
- Companion resources for policy management (RolePolicy, RolePolicyAttachment)
- 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.
Frequently Asked Questions
Policy Management & Conflicts
managedPolicyArns or inlinePolicies on the role resource makes it take exclusive control of those policy types, which conflicts with separate policy attachment resources like aws.iam.RolePolicyAttachment or aws.iam.RolePolicy. Choose one approach and stick with it.aws.iam.RolePolicyAttachment for managed policies and aws.iam.RolePolicy for inline policies. The managedPolicyArns and inlinePolicies arguments on the role are deprecated.aws.iam.PolicyAttachment and modifying name or path, you must set forceDetachPolicies to true and apply before changing the name. The aws.iam.RolePolicyAttachment resource doesn’t have this requirement.managedPolicyArns or inlinePolicies. These arguments give Pulumi exclusive control, so any out-of-band changes will be reverted on the next apply.Trust Relationships & Assume Role Policy
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 can’t use the aws.iam.Policy resource.aws.iam.getPolicyDocument is the recommended way to generate the assume role policy. Pass its .json output to the assumeRolePolicy argument.aws.iam.getPolicyDocument to avoid formatting issues, whitespace inconsistencies, and other JSON nuances.Configuration & Limits
maxSessionDuration is 1 hour, with a range of 1 to 12 hours.permissionsBoundary is an ARN of a policy that sets the maximum permissions the role can have, regardless of what other policies grant.Naming & Immutability
name and path are immutable. Changing them will force replacement of the role.name for an explicit role name or namePrefix to generate a unique name with that prefix. These arguments conflict with each other.Ready to get started?
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Create free account