Introducing Gruntwork’s AWS Landing Zone Solution
Create and manage AWS accounts with a customizable security baseline defined in Terraform.
Create and manage AWS accounts with a customizable security baseline defined in Terraform.
Setting up AWS accounts for production is hard. You need to create multiple accounts and configure each one with a variety of authentication, access controls and security features by using AWS Organizations, IAM Roles, IAM Users, IAM Groups, IAM Password Policies, Amazon GuardyDuty, AWS CloudTrail, AWS Config, and a whole lot more. Doing this from scratch is time-consuming and complicated, and all the pre-built “Landing Zone” solutions we tried were expensive, low-quality, UI-driven, and/or didn’t allow customization. That’s why today, I’m excited to announce Gruntwork’s AWS Landing Zone solution, which allows you to set up production-grade AWS accounts out of the box in minutes, but it’s also 100% defined as code, so you can fully customize it to your needs.
In addition, we are able to leverage the work we’ve invested in developing our compliance offering meaning new accounts can have the added benefit of complying with the CIS AWS Foundations Benchmark. Let me show you exactly how it works!
How to get started
Here’s the AWS account structure we’re going to create:

Check out our AWS landing zone deployment guide for why this is the recommended account structure.
To create this account structure, we are going to use the following new modules in the IaC Library:
account-baseline-root
: A security baseline for configuring the root account (also known as the master account) of an AWS Organization including setting up child accounts.account-baseline-security
: A security baseline for configuring the security account where all of your IAM users and IAM groups are defined.account-baseline-app
: A security baseline for configuring accounts designed to run your Apps and store logs.
Under the hood, these use a variety of other modules such as our new GuardDuty module, AWS Config, IAM roles, and so on. The best news is all of these modules are available to our customers at no extra cost as part of the Gruntwork for AWS subscription. If you’re not already a subscriber then check out our pricing page for more information. With the new modules, we’ll set up a production-grade account structure using Terragrunt and Terraform.
Let’s Deploy the Root Account
The first AWS account you create is the root account (sometimes also called the master account). This will be the parent account for your organization and won’t be used to run any infrastructure; instead, it is used solely to create child accounts and manage billing. This account has powerful permissions over all child accounts, so you should strictly limit access to this account to a small number of trusted admins — essentially if you apply a policy to the root account, it applies to all accounts in the organization!

To show how easy it is to set up a security baseline in the root account and create all of the necessary child accounts, let’s create a terragrunt.hcl
file to deploy the resources using the account-baseline-root
module:
Note: We are using Terragrunt to deploy these examples, but they work just fine with pure Terraform too!
terraform { source = "git::git@github.com:gruntwork-io/terraform-aws-service-catalog.git//modules/landingzone/account-baseline-root?ref=v0.36.11" }
include { path = find_in_parent_folders() }
inputs = { # Prefix all resources with this name name_prefix = "ref-arch-acme-root" # The child AWS accounts to create in this AWS organization child_accounts = { logs = { email = "root-accounts+logs@acme.com" is_logs_account = true } security = { email = "root-accounts+security@acme.com" } shared-services = { email = "root-accounts+shared-services@acme.com" } dev = { email = "root-accounts+dev@acme.com" } stage = { email = "root-accounts+stage@acme.com" } prod = { email = "root-accounts+prod@acme.com" }
} # The IAM users to create in this account. Since this is the root # account, we should only create IAM users for a small handful of # trusted admins. users = { alice = { groups = ["full-access"] pgp_key = "keybase:alice_on_keybase" create_login_profile = true create_access_keys = false } } }
After running terragrunt apply
we should now have successfully configured the root account of an AWS Organization, including setting up child accounts, AWS Config, AWS CloudTrail, Amazon Guard Duty, IAM users, IAM groups, IAM password policy, and more. The baseline will also authenticate to the logs account via an IAM role and create S3 buckets for storing AWS Config and CloudTrail data. Next, we’ll deploy the security baseline in the security account.
Let’s Deploy the Logs Account
The logs account is used for aggregating log data. All of the other accounts will send their AWS Config and CloudTrail data to this account so that you have a single, central place where all logs are stored and can be viewed.

We’ll deploy a security baseline to the logs account using the account-baseline-app
module, including setting up AWS Config, AWS CloudTrail, Amazon Guard Duty and the respective S3 buckets to store the logs.
terraform { source = "git::git@github.com:gruntwork-io/terraform-aws-service-catalog.git//modules/landingzone/account-baseline-app?ref=v0.36.11" }
include { path = find_in_parent_folders() }
inputs = { # Prefix all resources with this name name_prefix = "ref-arch-acme-logs"
# Use the S3 bucket and KMS key that were already created in this # logs account by account-baseline-root cloudtrail_s3_bucket_name = "<CLOUDTRAIL_BUCKET_NAME>" cloudtrail_kms_key_arn = "<CLOUDTRAIL_KMS_KEY_ARN>" # Use the S3 bucket that was already created in this logs account # by account-baseline-root config_s3_bucket_name = "<CONFIG_BUCKET_NAME>" config_central_account_id = null config_aggregate_config_data_in_external_account = false # Allow access from other AWS accounts allow_read_only_access_from_other_account_arns = "01234567890" allow_full_access_from_other_account_arns = "01234567890" allow_logs_access_from_other_account_arns = "01234567890" }
After running terragrunt apply
we should now have successfully configured the logs account of an AWS Organization, including creating the S3 buckets to aggregate logs from CloudTrail and AWS Config.
Let’s Deploy the Security Account
The security account is where all of your IAM users and IAM groups are defined and also where other “security” activities will be organized.

We’ll deploy a security baseline to the security account using the account-baseline-security
module, including setting up AWS Config, AWS CloudTrail, Amazon Guard Duty, IAM users, IAM groups, IAM password policy, and more. By default, we only create the admin and cross-account IAM groups in the security account.
terraform { source = "git::git@github.com:gruntwork-io/terraform-aws-service-catalog.git//modules/landingzone/account-baseline-security?ref=v0.36.11" }
include { path = find_in_parent_folders() }
inputs = { # Prefix all resources with this name name_prefix = "ref-arch-acme-security"
# Send CloudTrail logs to this bucket cloudtrail_s3_bucket_name = "ref-arch-security-logs"
# We assume the S3 bucket for AWS Config has already been created # by account-baseline-root config_should_create_s3_bucket = false config_s3_bucket_name = "<CONFIG_BUCKET_NAME>" config_central_account_id = "<LOGS_ACCOUNT_ID>" config_aggregate_config_data_in_external_account = true
# Enable the IAM groups you want should_create_iam_group_full_access = true should_create_iam_group_read_only = true should_create_iam_group_user_self_mgmt = true # Configure the names for IAM groups iam_group_name_full_access = "full-access" iam_group_name_read_only = "read-only" iam_group_name_iam_user_self_mgmt = "self-mgmt" iam_group_names_ssh_grunt_sudo_users = "ssh-grunt-sudo-users" iam_group_names_ssh_grunt_users = "ssh-grunt-users"
# This IAM group gives access to other AWS accounts iam_groups_for_cross_account_access = [ { group_name = "_account.stage-full-access" iam_role_arn = "arn:aws:iam::012345678901:role/full-access" }, { group_name = "_account.stage-read-only" iam_role_arn = "arn:aws:iam::012345678901:role/read-only" }, { group_name = "_account.prod-full-access" iam_role_arn = "arn:aws:iam::012345678901:role/full-access" }, { group_name = "_account.prod-read-only" iam_role_arn = "arn:aws:iam::012345678901:role/read-only" }, ]
# The IAM users to create in this account. Since this is the # security account, this is where we create all of our IAM users # and add them to IAM groups. users = { Alice = { groups = ["full-access", "access-all-external-accounts", "ssh-grunt-sudo-users"] pgp_key = "keybase:alice_on_keybase" create_login_profile = true create_access_keys = false } Bob = { groups = ["_account.stage-full-access", "_account.prod-read-only", "ssh-grunt-sudo-users"] pgp_key = "keybase:bob_on_keybase" create_login_profile = true create_access_keys = false } }
After running terragrunt apply
we should now have successfully configured the security account of an AWS Organization, including setting up the IAM users and groups. In the final step, we’ll deploy the app account.
Let’s Deploy the App Account
You can have one or more application accounts for running your software. At a bare minimum, most companies will have a production account (“prod”), for running user-facing software, and a staging account (“stage”) which is a replica of production (albeit with smaller or fewer servers to save money) used for internal testing.

For the purposes of this example, we’ll deploy a single account using the account-baseline-app
module. Let’s create another terragrunt.hcl
file to deploy a security baseline into the app account:
terraform { source = "git::git@github.com:gruntwork-io/terraform-aws-service-catalog.git//modules/landingzone/account-baseline-app?ref=v0.36.11" }
include { path = find_in_parent_folders() }
inputs = { # Prefix all resources with this name name_prefix = "ref-arch-acme-prod"
# Use the S3 bucket and KMS key that were already created in this # logs account by account-baseline-root cloudtrail_s3_bucket_name = "<CLOUDTRAIL_BUCKET_NAME>" cloudtrail_kms_key_arn = "<CLOUDTRAIL_KMS_KEY_ARN>"
# Use the S3 bucket that was already created in this logs account # by account-baseline-root config_s3_bucket_name = "<CONFIG_BUCKET_NAME>" config_central_account_id = "<LOGS_ACCOUNT_ID>" config_aggregate_config_data_in_external_account = true
# Allow access from other AWS accounts # IAM permissions omitted for brevity. <OMITTED_FOR_BREVITY> }
After running terragrunt apply
we should now have successfully configured the app account of an AWS Organization, including setting up CloudTrail, AWS Config, and any IAM roles for debugging and deployment.
Summary
That’s all it takes to configure the AWS root, logs, security and app accounts with AWS Config, CloudTrail, GuardDuty, IAM users, IAM groups, IAM password policies and more! The best part is that because we are using Terraform, under the hood, everything is 100% managed using code which means you can easily configure and extend all of these modules to add any custom requirements for your organization. For more information on the concepts and details refer to our “Configure Your Accounts with Landing Zone” production deployment guide.
In this post, we introduced our new Gruntwork for AWS Landing Zone solution. We hope that it addresses the needs of many of our existing SME and larger enterprise customers. To get access to these modules for easily creating production-grade account structures, check out our pricing page or contact us for more information.