This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Getting Started

Prerequisites, bootstrapping, and first deployment.

This section walks you through getting Org Kickstart deployed into a new AWS account.

Prerequisites

Before running Org Kickstart you need:

  • Terraform >= 1.0 (< 2.0)
  • AWS CLI configured with credentials for the Management (Payer) account
  • An S3 bucket for Terraform remote state
  • A few manual “artisanal” steps completed in the AWS console (see Bootstrap)

Steps

  1. Complete the Bootstrap steps in the AWS Console
  2. Copy examples/pipeline to your own private repo — it includes the Makefile, backend config, and directory layout
  3. Create your-org.tfvars and your-org.tfbackend for your organization (see the Reference for all variables; name them to match your env value)
  4. Initialize Terraform:
    make env=your-org tf-init
    
  5. Create the Security Account first (required before full apply):
    terraform apply -var-file="your-org.tfvars" -target module.security_account
    
  6. Deploy everything:
    make env=your-org tf-execute
    
    This runs tf-plan followed by tf-apply — saving the plan, applying it, and writing output-your-org.json to your state bucket.

For subsequent updates, use:

make env=your-org update

Using with an Existing Organization

If you already have an AWS Organization, see Importing an Existing Org for guidance on importing existing resources into Terraform state.

Example tfvars

See the Reference page for a full annotated example. The examples/pipeline directory in the repository contains a sample private-repo layout with a Makefile, backend config template, and scripts for CI/CD deployments.

1 - Bootstrap a New Account

Manual steps required in the AWS Console before running Terraform.

Before Org Kickstart can be deployed, a few steps must be completed via ClickOps in your new AWS Management (Payer) account. Terraform cannot perform these actions automatically.

Root Account Tasks

Log into the root user of your new AWS “payer” account and complete the following:

  1. Add MFA to root
  2. Enable IAM access to billing
  3. Go to Organizations and create an Organization
  4. Go to AWS IAM Identity Center (SSO) and enable it
  5. Add yourself as a user in Identity Center
  6. Create a Permission Set named TempAdministratorAccess (4-hour session recommended)
  7. Assign the Permission Set to the Payer/Management Account for your user
  8. Activate trusted access for CloudFormation StackSets — click “Activate trusted access with AWS Organizations to use service-managed permissions” (must be done via console)

Log out of root and never use it again.

Note: As of January 2026, Terraform does not support the aws login capability. An IAM Identity Center or IAM User must be created to run Terraform.

On Your Machine

  1. Check email and activate your IAM Identity Center account
  2. Add MFA to your Identity Center account
  3. Configure AWS credentials in your environment:
    aws configure sso
    # or
    export AWS_PROFILE=your-sso-profile
    

You are now ready to deploy Org Kickstart.

Next Steps

  • Create your tfvars file — see the Reference for all variables and a full example
  • Run terraform init and your first apply — see Getting Started

2 - Importing an Existing Organization

How to adopt Org Kickstart into an existing AWS Organization.

Org Kickstart can manage an existing AWS Organization. Several resources must be imported into Terraform state before running terraform apply.

The minimum required imports are the organization, the management account, and the Security Account (if it already exists). You may also want to import existing accounts, CloudTrail buckets, billing buckets, and SSO configuration.

Automated Import

The scripts/import_org.sh script generates an import-org.tf file and a TFVars snippet for your existing accounts.

# From the org-kickstart directory
./scripts/import_org.sh

Review the generated import-org.tf carefully before running Terraform.

Steps

  1. Run import_org.sh and review import-org.tf
  2. Add your CloudTrail or billing bucket to the import file if applicable
  3. Review SCPs you want to import:
    aws organizations list-policies --filter SERVICE_CONTROL_POLICY \
      --query 'Policies[].[Id,Name]' --output text
    
  4. Iterate with tf-plan until no unwanted changes appear:
    make env=your-org tf-plan
    make env=your-org tf-show    # review the plan output
    
  5. Once satisfied, apply:
    make env=your-org tf-apply
    
  6. Incrementally enable additional features

Manual Import Examples

Organizational Units

ROOT_OU=$(aws organizations list-roots --query Roots[0].Id --output text)
aws organizations list-organizational-units-for-parent \
  --parent-id $ROOT_OU \
  --query 'OrganizationalUnits[].[Id,Name]' --output text
import {
  to = module.organization.aws_organizations_organizational_unit.TF_VARS_KEY
  id = "ou-xxxx-xxxxxxxx"
}

IAM Identity Center (SSO)

To opt out of managing an existing Identity Center, set disable_sso_management = true in your tfvars. This is recommended for existing orgs with complex SSO configurations.

IDENTITY_STORE_ID=$(aws sso-admin list-instances \
  --query Instances[0].IdentityStoreId --output text)
SSO_INSTANCE_ARN=$(aws sso-admin list-instances \
  --query Instances[0].InstanceArn --output text)
import {
  to = module.organization.aws_identitystore_group.admin_group
  id = "$IDENTITY_STORE_ID/$GROUP_ID"
}
import {
  to = module.organization.aws_ssoadmin_permission_set.admin_permission_set
  id = "$PERMISSION_SET_ARN,$SSO_INSTANCE_ARN"
}
import {
  to = module.organization.aws_ssoadmin_managed_policy_attachment.admin_policy_attachments
  id = "arn:aws:iam::aws:policy/AdministratorAccess,$PERMISSION_SET_ARN,$SSO_INSTANCE_ARN"
}

CloudTrail

aws cloudtrail list-trails --query Trails[].TrailARN --output text
import {
  to = module.organization.aws_s3_bucket.cloudtrail_bucket[0]
  id = "YOUR_EXISTING_BUCKET_NAME"
}
import {
  to = module.organization.aws_cloudtrail.org_cloudtrail[0]
  id = "TRAIL_ARN"
}

Service Control Policies

aws organizations list-policies --filter SERVICE_CONTROL_POLICY \
  --query 'Policies[].[Id,Name]' --output text

# Get OUs targeted by a policy
aws organizations list-targets-for-policy --policy-id p-xxxxxx
import {
  to = module.organization.module.scp["POLICY_BLOCK_IDENTIFIER_FROM_TFVARS"].aws_organizations_policy.org_policy
  id = "p-xxxxxx"
}

Disabling Features for Initial Import

When importing an existing org, you may want to temporarily disable features to prevent unintended changes on the first apply:

  • CloudTrail: cloudtrail_bucket_name = null
  • SSO: disable_sso_management = true
  • Audit Role StackSet: deploy_audit_role = false
  • Security Services: use the security_services block with disable_* flags

3 - Example tfvars

A complete annotated example tfvars file.

The following is a complete example tfvars file showing all major configuration options. Copy this as a starting point and customize for your organization.

organization = {
  organization_name                 = "my-org"
  payer_name                        = "My Org Management Account"
  payer_email                       = "aws+payer@example.com"
  security_account_name             = "my-org-security"
  security_account_root_email       = "aws+security@example.com"
  cloudtrail_bucket_name            = "my-org-cloudtrail"
  cloudtrail_loggroup_name          = "CloudTrail/DefaultLogGroup"
  billing_data_bucket_name          = "my-org-cur"
  cur_report_frequency              = "DAILY"   # DAILY, HOURLY, or MONTHLY
  session_duration                  = "PT8H"
  admin_permission_set_name         = "AdministratorAccess"
  admin_group_name                  = "AllAdmins"
  disable_sso_management            = false
  deploy_audit_role                 = true
  audit_role_name                   = "security-audit"
  audit_role_stack_set_template_url = "https://s3.amazonaws.com/pht-cloudformation/aws-account-automation/AuditRole-Template.yaml"
  declarative_policy_bucket_name    = "my-org-account-status"
  vpc_flowlogs_bucket_name          = "my-org-flowlogs"
  macie_bucket_name                 = "my-org-macie-findings"

  # Custom OUs (in addition to the four required OUs)
  organization_units = {
    "Platform" = {
      name             = "Platform"
      is_child_of_root = true
    }
  }

  # AWS Accounts
  accounts = {
    dev = {
      account_name          = "my-org-dev"
      account_email         = "aws+dev@example.com"
      monthly_budget_amount = 500
    }

    prod = {
      account_name   = "my-org-prod"
      account_email  = "aws+prod@example.com"
      parent_ou_name = "Workloads"
    }

    sandbox = {
      account_name   = "my-org-sandbox"
      account_email  = "aws+sandbox@example.com"
      parent_ou_name = "Sandbox"
    }

    sso = {
      account_name    = "my-org-sso"
      account_email   = "aws+sso@example.com"
      parent_ou_name  = "Governance"
      delegated_admin = ["sso.amazonaws.com"]
    }
  }

  # Alternate contacts applied to all accounts (can be overridden per account)
  global_billing_contact = {
    name          = "Finance Team"
    title         = "CFO"
    email_address = "billing@example.com"
    phone_number  = "+14041234567"
  }

  global_security_contact = {
    name          = "Security Team"
    title         = "CISO"
    email_address = "security@example.com"
    phone_number  = "+14041234567"
  }

  global_primary_contact = {
    full_name       = "IT Operations"
    company_name    = "My Org, Inc."
    address_line_1  = "123 Main Street"
    city            = "Atlanta"
    state_or_region = "GA"
    postal_code     = "30332"
    country_code    = "US"
    email_address   = "aws@example.com"
    phone_number    = "+14041234567"
  }

  # Service Control Policies
  service_control_policies = {
    deny_root = {
      policy_name        = "DenyRoot"
      policy_description = "Denies use of root user"
      policy_json_file   = "policies/DenyRootSCP.json"
    }

    security_controls = {
      policy_name        = "DefaultSecurityControls"
      policy_description = "Base security controls for all accounts"
      policy_json_file   = "policies/SecurityControlsSCP.json.tftpl"
      policy_vars = {
        audit_role_name = "security-audit"
      }
    }
  }

  # Resource Control Policies
  resource_control_policies = {
    s3_data_perimeter = {
      policy_name        = "S3DataPerimeter"
      policy_description = "Restricts S3 to principals inside the org"
      policy_json_file   = "policies/RCP_S3DataPerimeter.json.tftpl"
      policy_vars = {
        org_id = "o-xxxxxxxxxxxx"
      }
    }
  }

  # Declarative Policies
  declarative_policies = {
    deny_public_ami = {
      policy_name        = "Block_Public_AMIs"
      policy_description = "Deny public sharing of all AMIs"
      policy_type        = "DECLARATIVE_POLICY_EC2"
      policy_json_file   = "policies/EC2ImageBPA_DCP.json"
      policy_targets     = ["Workloads", "Governance", "Sandbox"]
    }
  }

  # Security Services
  security_services = {
    disable_guardduty   = false
    disable_securityhub = false
    disable_macie       = false
  }

  # Billing Alerts
  billing_alerts = {
    levels = {
      level1  = 100
      level2  = 500
      oh_shit = 1000
    }
    subscriptions = ["billing-alerts@example.com"]
  }

  budget_defaults = {
    alert_recipients      = ["finance@example.com"]
    currency              = "USD"
    warning_percentage    = 80
    organizational_budget = 1000
  }
}

backend_bucket = "my-org-terraform-state-123456789012"

Backend Config File

Create a my-org.tfbackend file alongside your tfvars:

bucket = "my-org-terraform-state-123456789012"
key    = "org-kickstart.tfstate"
region = "us-east-1"

Then initialize with:

terraform init -backend-config="my-org.tfbackend"