Startups and personal projects using AWS rarely are setup in ways that follow best practices. Because of this I recently started working on some terraform (codename Sprout) that could be used to setup an AWS account following these best practices. The hope is that it can make doing the right thing easy and I (or you) can avoid the burden of migrating things as projects/companies grow.

Use an Organization Link to heading

First you need to create an organization from your root account. This will be the basis for everything else.

resource "aws_organizations_organization" "this" {
  aws_service_access_principals = [
    "sso.amazonaws.com",
  ]

  enabled_policy_types = [
    "SERVICE_CONTROL_POLICY"
  ]

  feature_set = "ALL"
}

Separate Things Into Organizational Units Link to heading

Next you should create some organizational units so that accounts are separated and service control policies can be applied to the correct accounts. In this example I’m creating two units:

  • Infrastructure: shared resources like networking, databases, and queues
  • Workloads: Actual services that are deployed
resource "aws_organizations_organizational_unit" "infrastructure" {
  name      = "Infrastructure"
  parent_id = aws_organizations_organization.this.roots[0].id
}

resource "aws_organizations_organizational_unit" "workloads" {
  name      = "Workloads"
  parent_id = aws_organizations_organization.this.roots[0].id
}

Treat Production as Special Link to heading

Under your top level OUs you should also split things into production and software development life-cycle units. This allows for much more secure practices in production accounts then other accounts that are used for development and you likely have good reasons to be more permissive.

resource "aws_organizations_organizational_unit" "infrastructure-prod" {
  name      = "Infrastructure Prod"
  parent_id = aws_organizations_organizational_unit.infrastructure.id
}

resource "aws_organizations_organizational_unit" "infrastructure-sdlc" {
  name      = "Infrastructure SDLC"
  parent_id = aws_organizations_organizational_unit.infrastructure.id
}

resource "aws_organizations_organizational_unit" "workloads-prod" {
  name      = "Workloads Prod"
  parent_id = aws_organizations_organizational_unit.workloads.id
}

resource "aws_organizations_organizational_unit" "workloads-sdlc" {
  name      = "Workloads SDLC"
  parent_id = aws_organizations_organizational_unit.workloads.id
}

Create Accounts Link to heading

Finally we can create an account. All accounts created within an organization create a role with admin permissions that is trusted by the root account. In this case I’m calling that account BreakGlass as it should only be used in emergencies. We are also creating a policy in the root account that will allow that role to be assumed.

resource "aws_organizations_account" "network_sdlc" {
  name      = "Network SDLC"
  email     = "[email protected]"
  parent_id = aws_organizations_organizational_unit.infrastructure-sdlc.id
  role_name = "BreakGlass"
  # There is no AWS Organizations API for reading role_name
  lifecycle {
    ignore_changes = [role_name]
  }
  close_on_deletion = true
}

data "aws_iam_policy_document" "network_sdlc_breakglass" {
  statement {
    actions = [
      "sts:AssumeRole",
    ]

    resources = ["arn:aws:iam::${aws_organizations_account.network_sdlc.id}:role/BreakGlass"]
  }
}

resource "aws_iam_policy" "network_sdlc_breakglass" {
  name        = "Network-SDLC-Break-Glass"
  description = "Allows the BreakGlass role to be assumed in the ${aws_organizations_account.network_sdlc.name} account"
  policy      = data.aws_iam_policy_document.network_sdlc_breakglass.json
}

Is That All? Link to heading

Obviously there is a lot more that needs to be done to make this useful. My goal is to write more posts covering various fundamental that I learn about setting up AWS with best practices as I develop Sprout.

Further Reading Link to heading