AWS Onboarding
Learn how to integrate your AWS account with North.Cloud
Select your integration method
We recommend using the CloudFormation stack which deploys all the required permissions. We also offer Terraform, CLI, or console commands. Learn more about the permissions.
Click here to open a pre-configured CloudFormation stack in a new tab. This link has a placeholder for your Organizaitonal Unit ID, please add your own.
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. The code below has a placeholder for your Organizaitonal Unit ID, please add your own.
aws cloudformation create-stack --stack-name NorthCloudformationAllFeatures --template-url https://north-cloudformation-template-public.s3.us-east-1.amazonaws.com/billing-and-usage-read-only-cf-stack/north-all-features.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 "NorthCloudformationAllFeatures" (or your desired stack name)
Click "Next"
Click “Create stack”
Last updated
Was this helpful?

