diff --git a/modules/externalrole/README.md b/modules/externalrole/README.md
new file mode 100644
index 0000000..d9f28eb
--- /dev/null
+++ b/modules/externalrole/README.md
@@ -0,0 +1,74 @@
+# Observe AWS External Role
+
+This module configures an IAM role that can be assumed by Observe.
+
+## Usage
+
+```hcl
+resource "random_pet" "this" {}
+
+data "observe_cloud_info" {}
+
+data "observe_workspace" "default" {
+ name = "Default"
+}
+
+resource "observe_datastream" "example" {
+ workspace = data.observe_workspace.default.oid
+ name = random_pet.this.id
+}
+
+module "external_role" {
+ source = "observeinc/collection/aws//modules/externalrole"
+ name = random_pet.this.id
+
+ observe_aws_account_id = data.observe_cloud_info.account_id
+ datastream_ids = [observe_datastream.example.id]
+ allowed_actions = [
+ "cloudwatch:ListMetrics",
+ "cloudwatch:GetMetricsData",
+ "tags:GetResources",
+ ]
+}
+
+```
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.3 |
+| [aws](#requirement\_aws) | >= 4.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | >= 4.0 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [allowed\_actions](#input\_allowed\_actions) | Set of IAM actions external entity is allowed to perform. | `list(string)` | n/a | yes |
+| [datastream\_ids](#input\_datastream\_ids) | Observe datastreams collected data is intended for. | `list(string)` | n/a | yes |
+| [name](#input\_name) | Name for IAM role. | `string` | n/a | yes |
+| [observe\_aws\_account\_id](#input\_observe\_aws\_account\_id) | AWS account ID for Observe tenant | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [role](#output\_role) | IAM role to be assummed by Observe |
+
diff --git a/modules/externalrole/main.tf b/modules/externalrole/main.tf
new file mode 100644
index 0000000..31f5229
--- /dev/null
+++ b/modules/externalrole/main.tf
@@ -0,0 +1,39 @@
+resource "aws_iam_role" "this" {
+ name = var.name
+
+ assume_role_policy = jsonencode({
+ Version = "2012-10-17",
+ Statement = [
+ {
+ Action = "sts:AssumeRole",
+ Effect = "Allow",
+ Principal = {
+ AWS = [
+ "arn:aws:iam::${var.observe_aws_account_id}:root"
+ ]
+ },
+ Condition = {
+ StringEquals = {
+ "sts:ExternalId" = var.datastream_ids
+ }
+ }
+ }
+ ]
+ })
+
+ inline_policy {
+ name = "allowed"
+
+ policy = jsonencode({
+ Version = "2012-10-17",
+ Statement = [
+ {
+ Action = var.allowed_actions
+
+ Effect = "Allow",
+ Resource = "*"
+ }
+ ]
+ })
+ }
+}
diff --git a/modules/externalrole/outputs.tf b/modules/externalrole/outputs.tf
new file mode 100644
index 0000000..68e58de
--- /dev/null
+++ b/modules/externalrole/outputs.tf
@@ -0,0 +1,4 @@
+output "role" {
+ description = "IAM role to be assummed by Observe"
+ value = aws_iam_role.this
+}
diff --git a/modules/externalrole/tests/externalrole.tftest.hcl b/modules/externalrole/tests/externalrole.tftest.hcl
new file mode 100644
index 0000000..6a61aa7
--- /dev/null
+++ b/modules/externalrole/tests/externalrole.tftest.hcl
@@ -0,0 +1,16 @@
+run "setup" {
+ module {
+ source = "../testing/setup"
+ }
+}
+
+run "install" {
+ variables {
+ name = run.setup.id
+ observe_aws_account_id = "158067661102"
+ datastream_ids = ["4100001"]
+ allowed_actions = [
+ "cloudwatch:ListMetrics",
+ ]
+ }
+}
diff --git a/modules/externalrole/variables.tf b/modules/externalrole/variables.tf
new file mode 100644
index 0000000..9e5b59d
--- /dev/null
+++ b/modules/externalrole/variables.tf
@@ -0,0 +1,49 @@
+variable "name" {
+ type = string
+ nullable = false
+ description = <<-EOF
+ Name for IAM role.
+ EOF
+
+ validation {
+ condition = length(var.name) <= 64
+ error_message = "Name must be under 64 characters."
+ }
+}
+
+variable "observe_aws_account_id" {
+ description = "AWS account ID for Observe tenant"
+ type = string
+ nullable = false
+
+ validation {
+ condition = can(regex("^\\d{12}$", var.observe_aws_account_id))
+ error_message = "Account ID must have 12 digits."
+ }
+}
+
+variable "datastream_ids" {
+ description = <<-EOF
+ Observe datastreams collected data is intended for.
+ EOF
+ type = list(string)
+ nullable = false
+
+ validation {
+ condition = length(var.datastream_ids) > 0
+ error_message = "At least one datastream must be provided."
+ }
+}
+
+variable "allowed_actions" {
+ description = <<-EOF
+ Set of IAM actions external entity is allowed to perform.
+ EOF
+ type = list(string)
+ nullable = false
+
+ validation {
+ condition = length(var.allowed_actions) > 0
+ error_message = "At least one action must be provided."
+ }
+}
diff --git a/modules/externalrole/versions.tf b/modules/externalrole/versions.tf
new file mode 100644
index 0000000..4c505f8
--- /dev/null
+++ b/modules/externalrole/versions.tf
@@ -0,0 +1,9 @@
+terraform {
+ required_version = ">= 1.3"
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 4.0"
+ }
+ }
+}