Copy
provider "aws" {
region = "us-east-1"
}
variable "org_id" {
type = string
description = "The OrgId you want to deploy this stack in"
}
locals {
is_us_east_1 = data.aws_region.current.name == "us-east-1"
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
# IAM Role for Cost and Usage Access
resource "aws_iam_role" "north_cost_usage_role" {
name = "NorthCostAndUsageRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::480850768557:root"
}
Action = ["sts:AssumeRole"]
}
]
})
}
resource "aws_iam_policy" "north_cost_usage_policy" {
name = "NorthCostAndUsageReadOnlyPolicy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "NorthCostAndUsageReadOnlyPolicyID"
Effect = "Allow"
Action = [
"ce:Get*",
"ce:Describe*",
"ce:List*",
"ce:Start*",
"account:GetAccountInformation",
"billing:Get*",
"payments:List*",
"payments:Get*",
"tax:List*",
"tax:Get*",
"consolidatedbilling:Get*",
"consolidatedbilling:List*",
"invoicing:List*",
"invoicing:Get*",
"cur:Get*",
"cur:Validate*",
"freetier:Get*",
"ec2:DescribeCapacity*",
"ec2:DescribeReservedInstances*",
"ec2:DescribeSpot*",
"rds:DescribeReserved*",
"rds:DescribeDBRecommendations",
"rds:DescribeAccountAttributes",
"ecs:DescribeCapacityProviders",
"es:DescribeReserved*"
]
Resource = "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "north_cost_usage_policy_attachment" {
role = aws_iam_role.north_cost_usage_role.name
policy_arn = aws_iam_policy.north_cost_usage_policy.arn
}
# CloudWatch Cross-Account Access Role
resource "aws_iam_role" "cloudwatch_cross_account_access_role" {
name = "NorthCloudWatchCrossAccountAccessRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["sts:AssumeRole"]
Principal = {
Service = "cloudwatch-crossaccount.amazonaws.com"
}
},
{
Effect = "Allow"
Action = ["sts:AssumeRole"]
Principal = {
AWS = "arn:aws:iam::480850768557:role/NorthCURReadOnly"
}
}
]
})
path = "/"
tags = {
Purpose = "CrossAccountMonitoring"
}
}
resource "aws_iam_policy" "cloudwatch_cross_account_access_policy" {
name = "NorthCloudWatchCrossAccountAccessPolicy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["sts:AssumeRole"]
Resource = "arn:aws:iam::*:role/NorthCloudWatchCrossAccountSharingRole"
},
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket",
"s3:GetBucketLocation"
]
Resource = [
"arn:aws:s3:::north-cur-bucket-${data.aws_caller_identity.current.account_id}",
"arn:aws:s3:::north-cur-bucket-${data.aws_caller_identity.current.account_id}/*"
]
}
]
})
}
resource "aws_iam_role_policy_attachment" "cloudwatch_cross_account_access_policy_attachment" {
role = aws_iam_role.cloudwatch_cross_account_access_role.name
policy_arn = aws_iam_policy.cloudwatch_cross_account_access_policy.arn
}
# CloudWatch Cross-Account Sharing Role
resource "aws_iam_role" "cloudwatch_cross_account_sharing_role" {
name = "NorthCloudWatchCrossAccountSharingRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = [
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:root",
aws_iam_role.cloudwatch_cross_account_access_role.arn
]
}
Action = ["sts:AssumeRole"]
}
]
})
path = "/"
depends_on = [aws_iam_role.cloudwatch_cross_account_access_role]
}
resource "aws_iam_policy" "cloudwatch_cross_account_sharing_policy" {
name = "NorthCloudWatchCrossAccountSharingPolicy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["cloudwatch:GetMetricData"]
Resource = "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "cloudwatch_cross_account_sharing_policy_attachment" {
role = aws_iam_role.cloudwatch_cross_account_sharing_role.name
policy_arn = aws_iam_policy.cloudwatch_cross_account_sharing_policy.arn
}
# North Premium IAM Role
resource "aws_iam_role" "north_premium_role" {
name = "NorthCostAndUsageRolePremium"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::480850768557:root"
}
Action = ["sts:AssumeRole"]
}
]
})
}
resource "aws_iam_policy" "north_premium_policy" {
name = "NorthCostAndUsageReadOnlyPolicy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Sid = "NorthPremiumPolicy1"
Action = "iam:CreateServiceLinkedRole"
Resource = "arn:aws:iam::*:role/aws-service-role/compute-optimizer.amazonaws.com/AWSServiceRoleForComputeOptimizer*"
Condition = {
StringLike = {
"iam:AWSServiceName" = "compute-optimizer.amazonaws.com"
}
}
},
{
Effect = "Allow"
Sid = "NorthPremiumPolicy2"
Action = "iam:PutRolePolicy"
Resource = "arn:aws:iam::*:role/aws-service-role/compute-optimizer.amazonaws.com/AWSServiceRoleForComputeOptimizer"
},
{
Effect = "Allow"
Sid = "NorthPremiumPolicy3"
Action = [
"compute-optimizer:*",
"ec2:DescribeInstances",
"ec2:DescribeVolumes",
"ecs:List*",
"autoscaling:DescribeAutoScalingGroups",
"lambda:ListFunctions",
"lambda:ListProvisionedConcurrencyConfigs",
"organizations:ListAccounts",
"cloudwatch:GetMetricStatistics",
"rds:DescribeDBRecommendations",
"rds:DescribeReservedDBInstances*"
]
Resource = "*"
},
{
Effect = "Allow"
Sid = "NorthPremiumPolicy4"
Action = "organizations:EnableAWSServiceAccess"
Resource = "*"
Condition = {
StringEquals = {
"organizations:ServicePrincipal" = "compute-optimizer.amazonaws.com"
}
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "north_premium_policy_attachment" {
role = aws_iam_role.north_premium_role.name
policy_arn = aws_iam_policy.north_premium_policy.arn
}
# CUR S3 Bucket
resource "aws_s3_bucket" "cur_bucket" {
bucket = "north-cur-bucket-${data.aws_caller_identity.current.account_id}"
tags = {
Purpose = "CostAndUsageReports"
}
}
resource "aws_s3_bucket_ownership_controls" "cur_bucket_ownership" {
bucket = aws_s3_bucket.cur_bucket.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
resource "aws_s3_bucket_public_access_block" "cur_bucket_public_access" {
bucket = aws_s3_bucket.cur_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "cur_bucket_versioning" {
bucket = aws_s3_bucket.cur_bucket.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "cur_bucket_encryption" {
bucket = aws_s3_bucket.cur_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_lifecycle_configuration" "cur_bucket_lifecycle" {
bucket = aws_s3_bucket.cur_bucket.id
rule {
id = "expire365days"
status = "Enabled"
expiration {
days = 365
}
}
}
resource "aws_s3_bucket_policy" "cur_bucket_policy" {
bucket = aws_s3_bucket.cur_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AWSBillingDeliveryPermissions"
Effect = "Allow"
Principal = {
Service = "billingreports.amazonaws.com"
}
Action = [
"s3:PutObject",
"s3:GetBucketAcl",
"s3:GetBucketLocation"
]
Resource = [
"arn:aws:s3:::${aws_s3_bucket.cur_bucket.id}/*",
"arn:aws:s3:::${aws_s3_bucket.cur_bucket.id}"
]
},
{
Sid = "AllowCrossAccountAccess"
Effect = "Allow"
Principal = {
AWS = aws_iam_role.cloudwatch_cross_account_access_role.arn
}
Action = [
"s3:GetObject",
"s3:ListBucket",
"s3:GetBucketLocation"
]
Resource = [
"arn:aws:s3:::${aws_s3_bucket.cur_bucket.id}/*",
"arn:aws:s3:::${aws_s3_bucket.cur_bucket.id}"
]
}
]
})
depends_on = [
aws_s3_bucket.cur_bucket,
aws_iam_role.cloudwatch_cross_account_access_role
]
}
# CUR Report (conditionally created in us-east-1)
resource "aws_cur_report_definition" "cur_report" {
count = local.is_us_east_1 ? 1 : 0
report_name = "aws-cost-report"
time_unit = "HOURLY"
format = "Parquet"
compression = "Parquet"
additional_schema_elements = ["RESOURCES", "SPLIT_COST_ALLOCATION_DATA"]
s3_bucket = aws_s3_bucket.cur_bucket.id
s3_prefix = "cur-reports"
s3_region = "us-east-1"
report_versioning = "CREATE_NEW_REPORT"
refresh_closed_reports = false
depends_on = [aws_s3_bucket_policy.cur_bucket_policy]
}
# CloudWatch StackSet
resource "aws_cloudformation_stack_set" "north_cloudwatch_stackset" {
name = "NorthCloudWatchStackSet"
permission_model = "SERVICE_MANAGED"
auto_deployment {
enabled = true
retain_stacks_on_account_removal = false
}
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
parameters = {
ParentAccountId = data.aws_caller_identity.current.account_id
}
operation_preferences {
failure_tolerance_percentage = 100
max_concurrent_percentage = 100
}
template_body = <<TEMPLATE
AWSTemplateFormatVersion: '2010-09-09'
Description: Propagates Stack to 'Enable CloudWatch in central monitoring account to assume permissions to view CloudWatch data in the downstream accounts'
Parameters:
ParentAccountId:
Type: String
Resources:
NorthCloudWatchCrossAccountSharingRole:
Type: AWS::IAM::Role
Properties:
RoleName: NorthCloudWatchCrossAccountSharingRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS:
- !Sub arn:aws:iam::${ParentAccountId}:root
- !Sub arn:aws:iam::${ParentAccountId}:role/NorthCloudWatchCrossAccountAccessRole
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: NorthCloudWatchCrossAccountSharingPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- cloudwatch:GetMetricData
Resource: "*"
TEMPLATE
depends_on = [aws_iam_role.cloudwatch_cross_account_access_role]
}
resource "aws_cloudformation_stack_set_instance" "north_cloudwatch_stackset_instance" {
stack_set_name = aws_cloudformation_stack_set.north_cloudwatch_stackset.name
deployment_targets {
organizational_unit_ids = [var.org_id]
}
region = "us-east-1"
}
# Outputs
output "cur_bucket_name" {
description = "Name of the CUR S3 bucket"
value = aws_s3_bucket.cur_bucket.id
}
output "cur_bucket_arn" {
description = "ARN of the CUR S3 bucket"
value = aws_s3_bucket.cur_bucket.arn
}
output "cross_account_role_arn" {
description = "ARN of the CloudWatch cross-account access role"
value = aws_iam_role.cloudwatch_cross_account_access_role.arn
}
output "premium_role_arn" {
description = "ARN of the North Premium IAM role"
value = aws_iam_role.north_premium_role.arn
}
output "cur_report_status" {
description = "Status of CUR Report creation"
value = local.is_us_east_1 ? "Created in us-east-1" : "Not created - deploy in us-east-1"
}