Create and Configure IAM Roles

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
Why am I getting 'resource cycling' or conflicts when managing role policies?
Using 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.
What's the recommended way to attach policies to a role?
Use separate resources: aws.iam.RolePolicyAttachment for managed policies and aws.iam.RolePolicy for inline policies. The managedPolicyArns and inlinePolicies arguments on the role are deprecated.
Why am I getting a DeleteConflict error when changing my role's name?
If you’re using 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.
What happens if I use `managedPolicyArns` with an empty array?
Pulumi will detach all managed policies from the role on the next apply. This is useful for removing all out-of-band policy attachments.
Will Pulumi remove policies I add manually in the AWS console?
Yes, if you’ve configured 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
What's the difference between assumeRolePolicy and regular IAM policies?
The 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.
Can I use aws.iam.getPolicyDocument for the assume role policy?
Yes, aws.iam.getPolicyDocument is the recommended way to generate the assume role policy. Pass its .json output to the assumeRolePolicy argument.
Should I write assumeRolePolicy as a JSON string or use a data source?
The schema recommends using explicit JSON encoding or aws.iam.getPolicyDocument to avoid formatting issues, whitespace inconsistencies, and other JSON nuances.
Configuration & Limits
What are the session duration limits for IAM roles?
The default maxSessionDuration is 1 hour, with a range of 1 to 12 hours.
What does permissionsBoundary do?
The permissionsBoundary is an ARN of a policy that sets the maximum permissions the role can have, regardless of what other policies grant.
Naming & Immutability
Can I change a role's name or path after creation?
No, both name and path are immutable. Changing them will force replacement of the role.
What's the difference between name and namePrefix?
Use 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