AWS Onboarding
After you successfully created an account you can add your AWS billing accounts.
Add your payer account number(s)
Best practice: If your AWS account is part of an AWS organization, please signup using the billing account (also called manager account) only. This will allow North to analyze usage throughout all your accounts and centralize savings opportunities into one view.
Add your AWS Organization Unit ID, which can be found here. Additional AWS docs on where to find it here.

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.amazonaws.com/billing-and-usage-read-only-cf-stack/north-read-only-access.yaml --region us-east-1 --capabilities CAPABILITY_NAMED_IAM --paramaters ParameterKey=OrgId,ParameterValue=YOUR_ORG_ID_HERE
Deploy 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.yaml
On the "Specify stack details" page, enter the following information:
Stack name: Enter "NorthCostAndUsageReadOnlyCloudFormationStack" (or your desired stack name)
Add your AWS Org ID to the param "Org ID"
Click the "Next" until you are able to click “Create stack”

Verify access
We will automatically verify your access after your deployment. We recommend that you wait until all the permissions are successfully deployed. You can also verify manually or send over the instructions by email to another team member.

Make sure to have the proper permissions on your billing account to create roles and policies.
Last updated