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
- Complete the Bootstrap steps in the AWS Console
- Set up your local repository — copy the
local-deploy sample, pin the
module version, and create your <env>.tfvars / <env>.tfbackend
- Initialize Terraform:
make env=your-org tf-init
- Create the Security Account first (required before full apply):
terraform apply -var-file="your-org.tfvars" -target module.security_account
- 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:
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/local-deploy
directory in the repository contains a sample layout with a Makefile, backend config template,
and helper scripts for running Org Kickstart from your workstation. To pin to a specific release,
see Releases.
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:
- Add MFA to root
- Enable IAM access to billing
- Go to Organizations and create an Organization
- Go to AWS IAM Identity Center (SSO) and enable it
- Add yourself as a user in Identity Center
- Create a Permission Set named
TempAdministratorAccess (4-hour session recommended)
- Assign the Permission Set to the Payer/Management Account for your user
- 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 June 2026, aws login works for the root user with Terraform — but the
root user cannot assume IAM roles, and Org Kickstart assumes roles (for example
OrganizationAccountAccessRole into the security account). That is why you must run Terraform as
an IAM Identity Center user or an IAM User rather than as root.
On Your Machine
- Check email and activate your IAM Identity Center account
- Add MFA to your Identity Center account
- Configure AWS credentials in your environment:
aws configure sso
# or
export AWS_PROFILE=your-sso-profile
- Create the S3 bucket that will hold the Terraform state. It must exist before the first
terraform init, since the S3 backend lives in it:
aws s3 mb s3://org-kickstart-fooli
This is the bucket you set as backend_bucket. By default (manage_state_bucket = true) Org
Kickstart then adopts it into Terraform and enforces versioning, public-access-block, and
encryption — see the Parameter Reference.
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 - Set Up Your Local Repository
Create your private deployment repo from the local-deploy sample and learn the Makefile workflow.
Once the Bootstrap steps are complete, set up a private repository to hold your
organization’s configuration and deploy Org Kickstart from your workstation.
1. Copy the sample
Copy the examples/local-deploy
directory into your own private git repository. It provides:
main.tf — calls the Org Kickstart module
import.tf — adopts the AWS Organization, the payer account, and the state bucket
Makefile — the env-based workflow (below)
sample.tfvars / sample.tfbackend — templates to copy
scripts/ and granted.yml — helper scripts and the Granted registry manifest
2. Pin the module version
main.tf pins the module to a tagged release:
source = "github.com/primeharbor/org-kickstart?ref=0.3.1"
See Releases for tracking the rolling latest branch versus pinning to a tag.
3. Create your environment files
Pick a short environment name ($env) for your org — for example fooli. Copy the samples so their
names match it:
cp sample.tfvars fooli.tfvars
cp sample.tfbackend fooli.tfbackend
- Edit
fooli.tfvars — see the Parameter Reference and the
Example tfvars for all options.
- Set the state bucket name in
fooli.tfbackend — the bucket you created with aws s3 mb during
Bootstrap. By default (manage_state_bucket = true) Org Kickstart adopts and
manages it.
4. The Makefile workflow
Every target takes env=<your env>:
make env=fooli tf-init # terraform init with your backend config
make env=fooli tf-plan # write a saved plan
make env=fooli tf-apply # apply the saved plan and write outputs
make env=fooli tf-execute # tf-plan followed by tf-apply
make env=fooli update # tf-init + tf-execute — your day-to-day command
The first apply creates the organization structure, OUs, and the security account.
Next steps
3 - Importing an Existing Organization
How to adopt Org Kickstart into an existing AWS Organization.
Experimental & incomplete
Importing an existing organization is **experimental and far from complete**. It does not cover
every resource Org Kickstart manages, so a first `terraform plan` against an adopted org will often
show a mix of imports, in-place changes, and **new resources**. Review that plan extremely
closely — confirm exactly what is being imported versus created (and especially that nothing is
being **destroyed**) before you apply.
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
- Run
import_org.sh and review import-org.tf
- Add your CloudTrail or billing bucket to the import file if applicable
- Review SCPs you want to import:
aws organizations list-policies --filter SERVICE_CONTROL_POLICY \
--query 'Policies[].[Id,Name]' --output text
- 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
- Once satisfied, apply:
make env=your-org tf-apply
- 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
4 - 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
}
# CloudFormation stacks to deploy into the payer account.
# Use this for billing notifications, cost alarms, or other payer-scoped
# automation. Exactly one of template_file or template_url is required.
payer_cloudformation_stacks = {
billing_alerts = {
stack_name = "slack_billing_alerts"
template_file = "cloudformation/slack-Template.yaml"
regions = ["us-east-1"]
parameters = {
pExecutionRate = "cron(0 09 * * ? *)"
# JSON parameter values can use a heredoc; the module canonicalizes
# JSON-valued parameters so whitespace differences don't cause drift.
pEventInput = <<-EOT
{
"threshold": "10",
"alert_percent": "20"
}
EOT
pSlackWebhookSecret = "SlackWebHook"
pRuleState = "ENABLED"
pAccountDescription = "My-Payer"
}
}
}
# CloudFormation stacks to deploy into the security account via the
# OrganizationAccountAccessRole. Same schema as payer_cloudformation_stacks.
security_account_stacks = {
findings_processor = {
stack_name = "security-findings-processor"
template_file = "cloudformation/findings-processor-Template.yaml"
regions = ["us-east-1"]
parameters = {
pSlackWebhookSecret = "SlackWebHook"
pSeverityThreshold = "MEDIUM"
}
}
}
# 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"