Skip to main content

Overview

Connect your AWS account to give TierZero read and (optional) write access to your cloud infrastructure. TierZero uses a cross-account IAM role to query CloudWatch metrics and logs, inspect resource configurations, and correlate infrastructure state with incidents without storing long-lived credentials.

Prerequisites

  • Administrative access to your AWS account
  • Permission to create IAM roles and attach policies

Setup Instructions

Step 1: Navigate to Integration Settings

  1. Log into your TierZero dashboard
  2. Go to Settings → Integrations
  3. Click Connect next to AWS
  4. Click View Instructions to see a setup guide with values specific to your organization (Account ID, External ID)

Step 2: Create an IAM Role

Create a new IAM role in your AWS account that TierZero can assume via cross-account access.
  1. In the AWS Console, go to IAM → Roles → Create role
  2. Select Custom trust policy and paste the trust policy below (replacing the placeholder values with the ones from the TierZero setup wizard):
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::851725519002:role/tierzero-managed"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<your-external-id>"
        }
      }
    }
  ]
}

Step 3: Attach Permissions

TierZero needs read-only access across your AWS services. Because AWS limits each role to 10 managed policy attachments, we recommend using the Terraform setup below for full coverage. If you prefer to attach managed policies manually, attach the following core set (up to the 10-policy limit):
PolicyPurpose
CloudWatchReadOnlyAccessLogs, metrics, and alarms
AmazonEC2ReadOnlyAccessEC2, VPC, and networking metadata
AmazonS3ReadOnlyAccessS3 bucket metadata
AmazonRDSReadOnlyAccessRDS instance metadata
AWSLambda_ReadOnlyAccessLambda function metadata
IAMReadOnlyAccessIAM role and policy metadata
AWSBillingReadOnlyAccessBilling and cost data
ElasticLoadBalancingReadOnlyLoad balancer metadata
AutoScalingReadOnlyAccessAuto Scaling group metadata
AmazonEKSMCPReadOnlyAccessEKS cluster and Kubernetes access
The managed policy approach covers the most common services but is limited to 10 policies. For comprehensive coverage including DynamoDB, ElastiCache, EBS/EFS, Route53, CloudFront, SQS, and more, use the Terraform setup which consolidates all permissions into inline policies.
  1. Name the role (e.g., TierZeroAccess) and click Create role

Step 4: Provide the Role ARN

  1. Copy the IAM Role ARN from your AWS console
  2. Paste it into the TierZero setup wizard
  3. Click Connect

Terraform Setup

If you manage infrastructure as code, use the following Terraform configuration to create the cross-account IAM role with comprehensive read-only access. This approach uses inline policies to consolidate permissions from 18+ AWS managed policies, avoiding the 10-policy attachment limit.
Replace the external_id variable value with the External ID shown in your TierZero setup wizard.
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0"
    }
  }
}

variable "role_name" {
  description = "Name for the IAM role"
  type        = string
  default     = "TierZeroAccess"
}

variable "external_id" {
  description = "External ID from TierZero setup wizard"
  type        = string
}

# IAM Role with Trust Policy

resource "aws_iam_role" "tierzero" {
  name = var.role_name

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::851725519002:role/tierzero-managed"
        }
        Action = "sts:AssumeRole"
        Condition = {
          StringEquals = {
            "sts:ExternalId" = var.external_id
          }
        }
      }
    ]
  })

  tags = {
    ManagedBy = "Terraform"
    Purpose   = "TierZero cross-account access"
  }
}

# EKS MCP Read-Only (managed policy — no inline equivalent)

resource "aws_iam_role_policy_attachment" "eks_mcp_readonly" {
  role       = aws_iam_role.tierzero.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSMCPReadOnlyAccess"
}

# Compute & Networking Read-Only
# Covers: EC2, Lambda, ECS, ELB, Auto Scaling, VPC, CloudFormation, Step Functions

resource "aws_iam_role_policy" "compute_networking_readonly" {
  name = "compute-networking-readonly"
  role = aws_iam_role.tierzero.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "EC2ReadOnly"
        Effect = "Allow"
        Action = [
          "ec2:Describe*",
          "ec2:Get*",
          "ec2:List*",
          "ec2:Search*",
        ]
        Resource = "*"
      },
      {
        Sid    = "LambdaReadOnly"
        Effect = "Allow"
        Action = [
          "lambda:Get*",
          "lambda:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "ECSReadOnly"
        Effect = "Allow"
        Action = [
          "ecs:Describe*",
          "ecs:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "ELBReadOnly"
        Effect = "Allow"
        Action = [
          "elasticloadbalancing:Describe*",
          "elasticloadbalancing:Get*",
        ]
        Resource = "*"
      },
      {
        Sid    = "AutoScalingReadOnly"
        Effect = "Allow"
        Action = [
          "autoscaling:Describe*",
          "application-autoscaling:DescribeScalableTargets",
          "application-autoscaling:DescribeScalingActivities",
          "application-autoscaling:DescribeScalingPolicies",
        ]
        Resource = "*"
      },
      {
        Sid    = "CloudFormationReadOnly"
        Effect = "Allow"
        Action = [
          "cloudformation:DescribeStacks",
          "cloudformation:ListStacks",
          "cloudformation:ListStackResources",
        ]
        Resource = "*"
      },
    ]
  })
}

# Storage & Database Read-Only
# Covers: S3, RDS, DynamoDB, DAX, EBS, EFS, ElastiCache, Data Pipeline, Kinesis

resource "aws_iam_role_policy" "storage_database_readonly" {
  name = "storage-database-readonly"
  role = aws_iam_role.tierzero.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "S3MetadataOnly"
        Effect = "Allow"
        Action = [
          "s3:ListAllMyBuckets",
          "s3:ListBucket",
          "s3:ListBucketVersions",
          "s3:ListBucketMultipartUploads",
          "s3:ListMultipartUploadParts",
          "s3:Describe*",
          "s3:GetBucket*",
          "s3:GetAccessPoint*",
          "s3:GetMultiRegionAccessPoint*",
          "s3:GetStorageLens*",
          "s3:GetEncryptionConfiguration",
          "s3:GetLifecycleConfiguration",
          "s3:GetReplicationConfiguration",
          "s3:GetAccelerateConfiguration",
          "s3:GetAnalyticsConfiguration",
          "s3:GetIntelligentTieringConfiguration",
          "s3:GetInventoryConfiguration",
          "s3:GetMetricsConfiguration",
          "s3:GetObjectTagging",
          "s3:GetObjectVersionTagging",
          "s3:GetObjectAcl",
          "s3:GetObjectVersionAcl",
          "s3:GetObjectRetention",
          "s3:GetObjectLegalHold",
          "s3:GetObjectAttributes",
          "s3:GetObjectVersionAttributes",
        ]
        Resource = "*"
      },
      {
        Sid    = "RDSReadOnly"
        Effect = "Allow"
        Action = [
          "rds:Describe*",
          "rds:ListTagsForResource",
        ]
        Resource = "*"
      },
      {
        Sid    = "DynamoDBMetadataOnly"
        Effect = "Allow"
        Action = [
          "dynamodb:Describe*",
          "dynamodb:List*",
          "dynamodb:GetAbacStatus",
          "dynamodb:GetResourcePolicy",
        ]
        Resource = "*"
      },
      {
        Sid    = "EBSReadOnly"
        Effect = "Allow"
        Action = [
          "ec2:DescribeVolumes",
          "ec2:DescribeVolumeStatus",
          "ec2:DescribeVolumeAttribute",
          "ec2:DescribeSnapshots",
          "ec2:DescribeSnapshotAttribute",
          "ec2:DescribeSnapshotTierStatus",
          "ec2:ListSnapshotsInRecycleBin",
          "ebs:ListSnapshotBlocks",
          "ebs:ListChangedBlocks",
        ]
        Resource = "*"
      },
      {
        Sid    = "EFSReadOnly"
        Effect = "Allow"
        Action = [
          "elasticfilesystem:Describe*",
          "elasticfilesystem:ListTagsForResource",
        ]
        Resource = "*"
      },
      {
        Sid    = "ElastiCacheReadOnly"
        Effect = "Allow"
        Action = [
          "elasticache:Describe*",
          "elasticache:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "KinesisReadOnly"
        Effect = "Allow"
        Action = [
          "kinesis:ListStreams",
          "kinesis:DescribeStream",
          "kinesis:DescribeStreamSummary",
        ]
        Resource = "*"
      },
      {
        Sid    = "DynamoDBContributorInsights"
        Effect = "Allow"
        Action = [
          "cloudwatch:GetInsightRuleReport",
        ]
        Resource = "arn:aws:cloudwatch:*:*:insight-rule/DynamoDBContributorInsights*"
      },
    ]
  })
}

# Monitoring & Observability Read-Only
# Covers: CloudWatch, Logs, Synthetics, RUM, Application Signals,
#         Observability Admin, CloudTrail

resource "aws_iam_role_policy" "monitoring_observability_readonly" {
  name = "monitoring-observability-readonly"
  role = aws_iam_role.tierzero.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "CloudWatchReadOnly"
        Effect = "Allow"
        Action = [
          "cloudwatch:BatchGet*",
          "cloudwatch:Describe*",
          "cloudwatch:GenerateQuery",
          "cloudwatch:Get*",
          "cloudwatch:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "LogsReadOnly"
        Effect = "Allow"
        Action = [
          "logs:Describe*",
          "logs:Get*",
          "logs:List*",
          "logs:StartQuery",
          "logs:StopQuery",
          "logs:TestMetricFilter",
          "logs:FilterLogEvents",
          "logs:StartLiveTail",
          "logs:StopLiveTail",
        ]
        Resource = "*"
      },
      {
        Sid    = "ApplicationSignalsReadOnly"
        Effect = "Allow"
        Action = [
          "application-signals:BatchGet*",
          "application-signals:Get*",
          "application-signals:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "SyntheticsReadOnly"
        Effect = "Allow"
        Action = [
          "synthetics:Describe*",
          "synthetics:Get*",
          "synthetics:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "RUMReadOnly"
        Effect = "Allow"
        Action = [
          "rum:BatchGet*",
          "rum:Get*",
          "rum:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "ObservabilityAdminReadOnly"
        Effect = "Allow"
        Action = [
          "observabilityadmin:Get*",
          "observabilityadmin:List*",
          "observabilityadmin:Test*",
          "observabilityadmin:Validate*",
        ]
        Resource = "*"
      },
      {
        Sid    = "CloudTrailReadOnly"
        Effect = "Allow"
        Action = [
          "cloudtrail:ListChannels",
        ]
        Resource = "*"
      },
      {
        Sid    = "CloudTrailAppSignalsScoped"
        Effect = "Allow"
        Action = [
          "cloudtrail:GetChannel",
        ]
        Resource = "arn:aws:cloudtrail:*:*:channel/aws-service-channel/application-signals/*"
      },
      {
        Sid    = "ServiceQuotasScoped"
        Effect = "Allow"
        Action = [
          "servicequotas:GetServiceQuota",
        ]
        Resource = [
          "arn:aws:servicequotas:*:*:s3/*",
          "arn:aws:servicequotas:*:*:dynamodb/*",
          "arn:aws:servicequotas:*:*:kinesis/*",
          "arn:aws:servicequotas:*:*:sns/*",
          "arn:aws:servicequotas:*:*:bedrock/*",
          "arn:aws:servicequotas:*:*:lambda/*",
          "arn:aws:servicequotas:*:*:fargate/*",
          "arn:aws:servicequotas:*:*:elasticloadbalancing/*",
          "arn:aws:servicequotas:*:*:ec2/*",
        ]
      },
    ]
  })
}

# Billing & Cost Read-Only

resource "aws_iam_role_policy" "billing_cost_readonly" {
  name = "billing-cost-readonly"
  role = aws_iam_role.tierzero.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "CostAnalysis"
        Effect = "Allow"
        Action = [
          "ce:Describe*",
          "ce:Get*",
          "ce:List*",
          "cur:Describe*",
          "cur:Get*",
          "budgets:Describe*",
          "budgets:View*",
        ]
        Resource = "*"
      },
    ]
  })
}

# Security, DNS, Messaging & CDN Read-Only
# Covers: IAM, STS, KMS, ACM, WAF, SQS, Route53, CloudFront

resource "aws_iam_role_policy" "security_dns_messaging_readonly" {
  name = "security-dns-messaging-readonly"
  role = aws_iam_role.tierzero.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "IAMReadOnly"
        Effect = "Allow"
        Action = [
          "iam:GenerateCredentialReport",
          "iam:GenerateServiceLastAccessedDetails",
          "iam:Get*",
          "iam:List*",
          "iam:SimulateCustomPolicy",
          "iam:SimulatePrincipalPolicy",
        ]
        Resource = "*"
      },
      {
        Sid    = "STSReadOnly"
        Effect = "Allow"
        Action = [
          "sts:GetCallerIdentity",
        ]
        Resource = "*"
      },
      {
        Sid    = "KMSReadOnly"
        Effect = "Allow"
        Action = [
          "kms:DescribeKey",
          "kms:ListAliases",
        ]
        Resource = "*"
      },
      {
        Sid    = "ACMReadOnly"
        Effect = "Allow"
        Action = [
          "acm:DescribeCertificate",
          "acm:ListCertificates",
        ]
        Resource = "*"
      },
      {
        Sid    = "WAFReadOnly"
        Effect = "Allow"
        Action = [
          "waf:ListWebACLs",
          "waf:GetWebACL",
          "wafv2:ListWebACLs",
          "wafv2:GetWebACL",
        ]
        Resource = "*"
      },
      {
        Sid    = "SQSReadOnly"
        Effect = "Allow"
        Action = [
          "sqs:GetQueueAttributes",
          "sqs:GetQueueUrl",
          "sqs:ListDeadLetterSourceQueues",
          "sqs:ListQueues",
          "sqs:ListMessageMoveTasks",
          "sqs:ListQueueTags",
        ]
        Resource = "*"
      },
      {
        Sid    = "Route53ReadOnly"
        Effect = "Allow"
        Action = [
          "route53:Get*",
          "route53:List*",
          "route53:TestDNSAnswer",
        ]
        Resource = "*"
      },
      {
        Sid    = "CloudFrontReadOnly"
        Effect = "Allow"
        Action = [
          "cloudfront:Describe*",
          "cloudfront:Get*",
          "cloudfront:List*",
          "cloudfront-keyvaluestore:Describe*",
          "cloudfront-keyvaluestore:Get*",
          "cloudfront-keyvaluestore:List*",
        ]
        Resource = "*"
      },
      {
        Sid    = "ResourceGroupsAndTags"
        Effect = "Allow"
        Action = [
          "resource-groups:ListGroups",
          "resource-groups:ListGroupResources",
          "resource-groups:GetGroup",
          "resource-groups:GetGroupQuery",
          "tag:GetResources",
        ]
        Resource = "*"
      },
    ]
  })
}

output "role_arn" {
  description = "ARN of the TierZero IAM role — provide this to TierZero"
  value       = aws_iam_role.tierzero.arn
}
Apply with:
terraform init
terraform apply -var="external_id=<your-external-id>"
Then paste the outputted role_arn into the TierZero setup wizard.

Inline Policy Summary

The Terraform configuration above creates 1 managed policy attachment + 5 inline policies:
PolicyServices Covered
Managed: AmazonEKSMCPReadOnlyAccessEKS clusters, Kubernetes API
compute-networking-readonlyEC2, Lambda, ECS, ELB, Auto Scaling, CloudFormation, VPC
storage-database-readonlyS3, RDS, DynamoDB, EBS, EFS, ElastiCache, Kinesis
monitoring-observability-readonlyCloudWatch, Logs, Synthetics, RUM, Application Signals, Observability Admin, CloudTrail
billing-cost-readonlyCost Explorer, Cost & Usage Reports, Budgets
security-dns-messaging-readonlyIAM, STS, KMS, ACM, WAF, SQS, Route53, CloudFront

Kubernetes (EKS)

If your team uses Amazon EKS, you can grant TierZero access to your Kubernetes clusters for inspecting workloads, pods, and cluster state during investigations. The AmazonEKSMCPReadOnlyAccess policy is already included in both the managed policy list and the Terraform configuration above.

Create an EKS Access Entry

Grant the IAM role access to your EKS cluster:
  1. In the AWS Console, go to EKS → Clusters → your cluster → Access
  2. Click Create access entry
  3. Select the TierZero IAM role as the principal
  4. Assign the AmazonEKSViewPolicy access policy with Cluster scope
  5. Click Create
Repeat for each EKS cluster you want TierZero to access.

Allow TierZero’s IP Addresses (If Required)

If your EKS cluster’s API server endpoint restricts access by IP, you need to add TierZero’s outbound IP addresses to the allowlist:
  1. In the AWS Console, go to EKS → Clusters → your cluster → Networking
  2. Under API server endpoint access, click Manage
  3. Add TierZero’s outbound IP addresses (available in your TierZero dashboard under Settings → Integrations → AWS)
This step is only required if your EKS cluster has IP-based restrictions on the API server endpoint. Clusters with public endpoint access enabled without CIDR restrictions do not need this.

What TierZero Accesses

  • CloudWatch Logs & Metrics: Query log groups, log streams, metrics, alarms, and X-Ray traces
  • Compute: EC2 instances, Lambda functions, ECS services, ELB, Auto Scaling groups
  • Storage & Databases: S3 buckets, RDS instances, DynamoDB tables, EBS volumes, EFS file systems, ElastiCache clusters
  • Networking: VPCs, subnets, security groups, Route53 DNS, CloudFront distributions
  • EKS clusters: Kubernetes workloads, pods, and cluster state (if configured)
  • IAM metadata: Roles, policies, and trust relationships
  • Billing data: Cost and usage, budgets, invoices

Security

  • TierZero uses cross-account IAM role assumption, so no long-lived credentials are stored
  • An External ID is required to prevent confused deputy attacks
  • The default policies are read-only; write access is opt-in based on your needs
  • Revoke access at any time by deleting the IAM role or updating its trust policy
  • All API calls are logged in AWS CloudTrail

Troubleshooting

”Access Denied” Errors

  • Verify the IAM role’s trust policy allows TierZero’s account (851725519002) to assume it
  • Check that the External ID matches the value shown in the TierZero setup wizard
  • Ensure all required policies are attached to the role (check both managed and inline policies)

EKS Cluster Not Accessible

  • Confirm the EKS access entry exists for the TierZero IAM role
  • Verify the AmazonEKSViewPolicy is assigned with Cluster scope
  • If using IP restrictions, ensure TierZero’s outbound IPs are allowlisted

Missing Data

  • Verify the IAM role has the specific policy for the data type you expect (e.g., monitoring-observability-readonly for CloudWatch logs)
  • Check that the role ARN entered in TierZero matches the role in your AWS account
  • If using the managed policy approach, note that some services (DynamoDB, ElastiCache, EBS/EFS, etc.) require the Terraform setup for coverage