AWS Onboarding
After you successfully created an account you can add your AWS billing accounts.
Select your integration method
We recommend using the CloudFormation option. This deploys all the required permissions within a couple of minutes through a couple clicks. Or you can use Terraform, CLI, or console commands to deploy the required permissions.
Click here to open a pre-configured CloudFormation stack in a new tab.
Once in the page, scroll to the bottom and click "I acknowledge".
Finally, click "Create stack" to create the stack.
Create .tf file
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"
}Run the following command on the AWS CLI
aws cloudformation create-stack --stack-name NorthCloudformationAllFeatures --template-url https://north-cloudformation-template-public.s3.us-east-1.amazonaws.com/premium/north-premium-advanced.yaml --region us-east-1 --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=OrgId,ParameterValue=ADD_ORG_ID_HEREDeploy a CloudFormation stack in the AWS console that will create a read-only cross-account role. This will enable North to recommend the best savings posture based on your usage.
In the AWS Management Console, select CloudFormation and “Create Stack”.
Select the "Specify an Amazon S3 template URL" and enter the URL below:
https://north-cloudformation-template-public.s3.us-east-1.amazonaws.com/billing-and-usage-read-only-cf-stack/north-all-features.yamlOn the "Specify stack details" page, enter the following information:
Stack name: Enter "NorthCostAndUsageReadOnlyCloudFormationStack" (or your desired stack name)
Click the "Next" until you are able to click “Create stack”

Last updated



