Create and Configure IAM Roles

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 FREE

Frequently Asked Questions

Policy Management & Conflicts
Why am I getting a DeleteConflict error when renaming my IAM role?
If you’re using 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.
Why am I getting resource cycling errors with my role policies?
Using 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.
What does exclusive management mean for managedPolicyArns and inlinePolicies?
When you configure these arguments, Pulumi takes over complete management of that policy type. It will attach/detach managed policies or add/remove inline policies to match your configuration exactly, removing any policies added outside of Pulumi.
How do I prevent out-of-band policy changes from persisting?
Configure 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
Can I use aws.iam.Policy for my assume role policy?
No. The assumeRolePolicy is a trust policy with different structure than standard IAM policies. Use aws.iam.getPolicyDocument data source or explicit JSON encoding instead.
What's the recommended way to create an assume role policy?
Use the aws.iam.getPolicyDocument data source and pass its json output to assumeRolePolicy. This maintains consistency and avoids JSON formatting issues.
What's the difference between assumeRolePolicy and regular IAM policies?
The 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
What are the session duration limits for IAM roles?
The maxSessionDuration can range from 1 hour to 12 hours. If not specified, it defaults to 1 hour.
Can I change a role's name or path after creation?
No, both 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
Should I use managedPolicyArns and inlinePolicies on my role?
No, both arguments are deprecated. Use 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.
What's the difference between aws.iam.PolicyAttachment and aws.iam.RolePolicyAttachment?
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.

Using a different cloud?

Explore security guides for other cloud providers: