# AWS Onboarding

{% embed url="<https://www.loom.com/share/c976d38a9d874424a9438a392a906dcc>" %}

{% hint style="info" %}
Prerequisites:

* [ ] If you don't have an account, we recommend you create a free account first at [app.north.cloud](https://app.north.cloud)
* [ ] You need to have the appropriate permissions to create IAM permissions into the payer account
* [ ] Enable cloudformation stackset. Guide from the [official AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-orgs-activate-trusted-access.html).
  {% endhint %}

{% stepper %}
{% step %}

### Get your account information ready

### AWS Account ID

{% hint style="info" %}
If your AWS account is part of an AWS organization, please sign up using the billing account only (also called payer account). This will allow North to analyze usage throughout all your accounts and centralize savings opportunities into one view.
{% endhint %}

{% embed url="<https://www.loom.com/share/f79bcfc77f9e4907991499a9e73389bc>" %}

### AWS Organizational Unit ID

{% hint style="info" %}
You can find your Organizational Unit ID [here](https://us-east-1.console.aws.amazon.com/organizations/v2/home/accounts). &#x20;
{% endhint %}

<figure><img src="https://1793025303-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FeLw7hj1pxE38VmG1sYVW%2Fuploads%2FM0YVNyV1TDx5Agvlsh5z%2Faws-org-id-location.png?alt=media&#x26;token=b786d26b-3063-49d6-9923-3d1acc114c93" alt=""><figcaption></figcaption></figure>

{% endstep %}

{% step %}

### 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.](https://docs.north.cloud/docs/getting-started/permissions-required)

{% hint style="info" %}
If you are running the setup manually, make sure to replace the placeholder  ADD\_ORG\_ID\_HERE with your Organizational Unit ID as shown [here](#add-your-payer-account-number-s).
{% endhint %}

{% tabs %}
{% tab title="CloudFormation" %}

* [Click here to open a pre-configured CloudFormation stack](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https%3A%2F%2Fnorth-cloudformation-template-public.s3.us-east-1.amazonaws.com%2Fbilling-and-usage-read-only-cf-stack%2Fnorth-all-features.yaml\&stackName=NorthCloudformationAllFeatures\&param_OrgId=ADD_ORG_ID_HERE) 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.
  {% endtab %}

{% tab title="Terraform" %}

* Create .tf file

<pre class="language-hcl" data-full-width="true"><code class="lang-hcl">
<strong>provider "aws" {
</strong>  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 = &#x3C;&#x3C;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"
}
</code></pre>

{% endtab %}

{% tab title="CLI" %}

* Run the following command on the AWS CLI. *The code below has a placeholder for your Organizaitonal Unit ID, please add your own.*

{% code overflow="wrap" %}

```bash
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_HERE
```

{% endcode %}
{% endtab %}

{% tab title="Console (manual)" %}

* 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:

```url
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 "NorthCloudformationAllFeatures" (or your desired stack name)
  * Click  "Next"&#x20;
  * Click “Create stack”
    {% endtab %}
    {% endtabs %}
    {% endstep %}

{% step %}

### Verify your access

North.Cloud will automatically verify your access after your deployment and confirm if your onboarding has been completed successfully. You can also verify manually if any failures happen or need to run the processes asynchronously.&#x20;

{% embed url="<https://www.loom.com/share/8c7731bc294a4acaa54d83699ed20d23>" %}
{% endstep %}

{% step %}

### Start saving

Once North.Cloud completes your savings and data analysis, we'll send you a confirmation email. You'll then have access to all our AI-powered optimization features, the same tools that help leading companies achieve top 1% cloud efficiency.
{% endstep %}
{% endstepper %}
