Skip to main content

NTC Account Baseline Templates

Release Notes Implementation Blueprint 🔒   Source Code

Description​

NTC Account Baseline Templates provide a standardized framework for configuring AWS accounts with essential guardrails, security controls, and operational settings. These templates automate the deployment of account-specific configurations, including IAM policies, logging, security, and baseline compliance checks. By applying consistent baselines across accounts, this building block ensures governance, security, and operational efficiency at scale.

NTC Account Baseline Templates are highly customizable, enabling you to tailor configurations to meet the specific needs of different environments or workloads.

Unified Multi-Region Baseline

Starting with NTC Account Baseline Templates v4, the unified multi-region baseline is the only supported mode. The legacy per-region baseline templates have been removed.

See Account Baseline for full documentation and examples.

info

NTC Account Baseline Templates can be easily customized and then applied via NTC Account Factory.

Per-Account Overrides​

Templates are configured once per baseline scope. Individual accounts can be given a different value, or exempted from a control entirely, through customer_values.account_baseline_overrides on the account's account_factory_list entry. The aws_config and account_hardening templates support this today.

Overrides are validated inside the generated baseline, not in the Account Factory stack. An unknown control, a wrong type or a value AWS would reject therefore fails the account's baseline plan rather than the management stack plan.

See Account Factory List for the full list of overridable values, their defaults and the governance model.

Custom Baseline Templates​

In addition to the pre-built templates, you can create custom baseline Terraform files that are deployed alongside the standard templates. Custom templates use the same injected variables and multi-region pattern.

Available injected variables:

VariableTypeDescription
var.aws_partitionstringAWS partition (e.g., aws, aws-cn, aws-us-gov)
var.aws_partition_dns_suffixstringAWS partition DNS suffix (e.g., amazonaws.com)
var.main_regionstringPrimary region for the account baseline
var.baseline_regionslist(string)All regions where the baseline is deployed
var.current_account_idstringAWS account ID
var.current_account_namestringAccount name
var.current_account_emailstringAccount email address
var.current_account_ou_pathstringOrganizational unit path
var.current_account_tagsmapAccount tags
var.current_account_alternate_contactslistAlternate contacts
var.current_account_customer_valuesanyCustom values from account creation
var.baseline_scope_namestringName of the baseline scope
var.baseline_parametersanyScope-specific parameters
var.baseline_terraform_versionstringTerraform/OpenTofu version
var.baseline_terraform_binarystringterraform or opentofu
var.baseline_aws_provider_versionstringAWS provider version
var.baseline_execution_role_namestringIAM role name for baseline execution

Example custom unified baseline template:

# files/custom_baseline.tf

# Global resource (no region argument — deployed once per account)
resource "aws_iam_account_password_policy" "strict" {
minimum_password_length = 14
require_lowercase_characters = true
require_uppercase_characters = true
require_numbers = true
require_symbols = true
allow_users_to_change_password = true
max_password_age = 90
password_reuse_prevention = 24
}

# Regional resource (deployed in each baseline region)
resource "aws_ebs_encryption_by_default" "enabled" {
for_each = toset(var.baseline_regions)
region = each.value
enabled = true
}

# Regional resource with account-aware naming
resource "aws_sns_topic" "baseline_alerts" {
for_each = toset(var.baseline_regions)
region = each.value
name = "${var.current_account_name}-baseline-alerts"
}

Reference custom files in your Account Factory configuration alongside the pre-built templates:

baseline_terraform_files = [
# Pre-built templates
module.account_baseline_templates.account_baseline_terraform_files["iam_monitoring_reader"],
module.account_baseline_templates.account_baseline_terraform_files["aws_config"],
# Custom baseline file
{
file_name = "custom_baseline.tf"
content = file("${path.module}/files/custom_baseline.tf")
}
]

Usage​

Latest Release4.6.1
Loading usage example from blueprint...

Requirements​

The following requirements are needed by this module:

  • terraform (>= 1.10.0)

  • aws (>= 6.0)

  • local (>= 2.4.0)

Providers​

The following providers are used by this module:

  • aws (>= 6.0)

  • local (>= 2.4.0)

Modules​

No modules.

Resources​

The following resources are used by this module:

Required Inputs​

The following input variables are required:

account_baseline_templates​

Description: List of baseline file templates which should be used to generate a Terraform Account Baseline.

Type:

list(object({
file_name = string
template_name = string
# NOTE: Legacy per-region baseline templates have been removed.
# 'unified_multi_region_baseline' must be true (default). Set to false is no longer supported.
unified_multi_region_baseline = optional(bool, true)
iam_role_inputs = optional(object({
role_name = optional(string, "")
role_path = optional(string, "/")
policy_name = optional(string, "")
policy_json = optional(string, "")
policy_arn = optional(string, "")
role_principal_type = optional(string, "AWS")
role_principal_identifiers = optional(list(string), [])
role_add_current_account_to_principals = optional(bool, false)
role_is_instance_profile = optional(bool, false)
}), {})
openid_connect_inputs = optional(object({
provider = optional(string, "")
audience = optional(string, "")
subject_list = optional(list(string), [])
subject_list_encoded = optional(string, "")
role_name = optional(string, "ntc-oidc-role")
role_path = optional(string, "/")
role_max_session_in_hours = optional(number, 1)
permission_boundary_arn = optional(string, "")
permission_policy_arn = optional(string, "arn:$${var.aws_partition}:iam::aws:policy/AdministratorAccess") # $${var.aws_partition} is injected by ntc-account-factory
}), {})
tfstate_backend_inputs = optional(object({
config_principal_name = optional(string, "config.amazonaws.com")
s3_bucket_name = optional(string, "")
s3_bucket_force_destroy = optional(bool, false)
s3_regional_namespace = optional(bool, false)
s3_regional_buckets = optional(bool, false)
s3_abac_enabled = optional(bool, true) # ABAC is required for tag conditions (aws:ResourceTag) on the bucket
kms_deletion_window_in_days = optional(number, 30)
kms_key_rotation_enabled = optional(bool, true)
kms_key_owners = optional(list(string), [])
# DEPRECATED - no longer used. AWS Config now inspects this bucket/key with its service-linked role
# (AWSServiceRoleForConfig) instead of a custom IAM role (see DEPRECATION WARNINGS in main.tf). Setting this has no effect.
config_iam_role_name = optional(string, "ntc-config-role")
# opt the state bucket(s) into aws backup: adds the backup tags and kms access for the roles of the 'backup' template
backup_support_enabled = optional(bool, true)
backup_tags = optional(map(string), { "ntc:backup" = "true", "ntc:backup-scope" = "local-and-central" })
backup_operator_iam_role_name = optional(string, "ntc-local-backup-operator-role")
backup_restorer_iam_role_name = optional(string, "ntc-local-backup-restorer-role")
access_rules = optional(list(object({
name = string
description = optional(string, "Access rule")
role_arns = optional(list(string), [])
allowed_prefixes = optional(list(string), ["*"])
# "read_write" allows plan and apply, "read_only" allows plan (read state and manage lock files only)
access_level = optional(string, "read_write")
})), [])
}), {})
aws_config_inputs = optional(object({
config_principal_name = optional(string, "config.amazonaws.com")
config_recorder_name = optional(string, "ntc-config-recorder")
config_delivery_channel_name = optional(string, "ntc-config-delivery")
config_log_archive_bucket_arn = optional(string, "")
config_log_archive_kms_key_arn = optional(string, "")
config_delivery_frequency = optional(string, "One_Hour")
config_security_main_region = optional(string, "")
config_recording_mode = optional(object({
frequency = optional(string, "CONTINUOUS")
resource_types = optional(list(string), [])
exclusion_by_resource_types = optional(list(string), [])
recording_mode_override = optional(object({
frequency = optional(string, "DAILY")
resource_types = optional(list(string), [])
}), {})
}), {
frequency = "CONTINUOUS"
resource_types = []
})
# DEPRECATED - will be removed in the next major version. Override the recording mode per account in the
# account map of ntc-account-factory instead:
# customer_values.account_baseline_overrides.aws_config.config_recording_mode
# The account map value takes precedence over this map when both are set. Using this map emits a warning
# in the plan of the account factory stack (see DEPRECATION WARNINGS in main.tf).
config_recording_mode_account_override = optional(map(object({
frequency = optional(string)
resource_types = optional(list(string))
exclusion_by_resource_types = optional(list(string))
recording_mode_override = optional(object({
frequency = optional(string)
resource_types = optional(list(string))
}), {})
})), {})
# DEPRECATED - no longer used. AWS Config now uses the AWS-managed service-linked role (AWSServiceRoleForConfig)
# for the configuration recorder, to satisfy Security Hub control Config.1. Setting these has no effect and
# emits a warning in the plan of the account factory stack (see DEPRECATION WARNINGS in main.tf).
config_iam_role_name = optional(string, "ntc-config-role")
config_iam_path = optional(string, "/")
}), {})
backup_inputs = optional(object({
# Must match the central NTC backup module's expectations (member_account_backup_role_name, vault
# name) or cross-account copy fails with AccessDenied.
backup_operator_iam_role_name = optional(string, "ntc-local-backup-operator-role")
# Restore-only role, kept separate from the operator role for least privilege.
backup_restorer_iam_role_name = optional(string, "ntc-local-backup-restorer-role")
# Scanner role GuardDuty Malware Protection assumes to read recovery points during a scan
malware_scan_scanner_iam_role_name = optional(string, "ntc-local-backup-malware-scanner-role")
# Region is always appended: "<vault_prefix_name>-<region>".
vault_prefix_name = optional(string, "ntc-local-backup-vault")
kms_deletion_window_in_days = optional(number, 30)
kms_key_rotation_enabled = optional(bool, true)
# Additional admin principals for the vault's KMS key
kms_key_owners = optional(list(string), [])
# Central backup account for the reverse restore-copy path and to copy resources to central account for not fully supported resource types (e.g., RDS)
central_backup_account_id = optional(string, "")
vault_lock_config = optional(object({
enabled = optional(bool, false)
min_retention_days = optional(number, 7)
max_retention_days = optional(number, 30)
changeable_for_days = optional(number, 30)
}), {
enabled = false
min_retention_days = 7
max_retention_days = 30
changeable_for_days = 30
})
# Per-region override of aws_backup_region_settings - AWS Backup rejects an unrecognized resource
# type per region outright. No entry for a region = module defaults apply unchanged.
region_overrides = optional(map(object({
# Each REPLACES (not adds to) the module default for that region - repeat every type it should
# still have. Both map directly to the two aws_backup_region_settings arguments of the same
# name, see:
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_region_settings
resource_types_opt_in_preference = optional(list(string))
resource_type_management_preference = optional(list(string))
})), {})
}), {})
# Remediates a set of common AWS Security Hub / Foundational Security Best Practices findings once per
# account. Every control can be turned off for the whole baseline scope with its own "enabled" flag.
account_hardening_inputs = optional(object({
# SSM.7 - block public sharing of SSM documents owned by the account. The setting is always written
# as "Disable", the only value which satisfies the control, so "enabled" is the whole knob: on means
# the account blocks public sharing, off means this baseline does not manage the setting at all.
ssm_document_public_sharing = optional(object({
enabled = optional(bool, true)
}), {})
# SSM.6 - send the output of SSM Automation script actions to CloudWatch Logs. The setting is always
# written as "CloudWatch", the only value the SSM API accepts here. Sending automation output to S3
# is configured per execution on the automation itself, not through this account-level setting.
ssm_automation_log_destination = optional(object({
enabled = optional(bool, true)
}), {})
# EC2.8 - account-level instance metadata defaults. An instance can still override these at launch,
# and EC2.8 is evaluated per instance, so this makes new instances compliant but does not remediate
# existing ones.
ec2_imdsv2 = optional(object({
enabled = optional(bool, true)
http_tokens = optional(string, "required")
# "no-preference" leaves the per-instance default untouched.
http_endpoint = optional(string, "no-preference")
# -1 means no account-level preference.
http_put_response_hop_limit = optional(number, -1)
instance_metadata_tags = optional(string, "no-preference")
}), {})
# EC2.7 - encrypt newly created EBS volumes and snapshots with the default EBS KMS key.
ebs_encryption_by_default = optional(object({
enabled = optional(bool, true)
}), {})
# EC2.182 - "block-all-sharing" also revokes already public snapshots, "block-new-sharing" does not.
ebs_snapshot_block_public_access = optional(object({
enabled = optional(bool, true)
state = optional(string, "block-all-sharing")
}), {})
# IAM.7 / IAM.16 / IAM.17 - applies to IAM users only, not to identities federated through Identity
# Center. Defaults also satisfy Si001 IT-Grundschutz 5.0 (chapter 4 - T7) and CIS AWS Foundations.
# IAM.16 requires password_reuse_prevention to be exactly 24 and IAM.17 requires max_password_age
# to be 90 or less - loosening either still applies, but no longer passes the control.
iam_password_policy = optional(object({
enabled = optional(bool, true)
# 18 is the minimum requirement for privileged users.
minimum_password_length = optional(number, 18)
require_lowercase_characters = optional(bool, true)
require_numbers = optional(bool, true)
require_uppercase_characters = optional(bool, true)
require_symbols = optional(bool, true)
# last 24 passwords may not be reused (required by CIS controls)
allow_users_to_change_password = optional(bool, true)
password_reuse_prevention = optional(number, 24)
max_password_age = optional(number, 90)
}), {})
# S3.1 - account-level block which overrides any per-bucket public access setting. S3.1 requires all
# four flags to be true, so setting any of them to false fails the control.
s3_account_public_access_block = optional(object({
enabled = optional(bool, true)
block_public_acls = optional(bool, true)
block_public_policy = optional(bool, true)
ignore_public_acls = optional(bool, true)
restrict_public_buckets = optional(bool, true)
}), {})
}), {})
}))

Optional Inputs​

No optional inputs.

Outputs​

The following outputs are exported:

account_baseline_terraform_files​

Description: Account Baseline Terraform files grouped by template name.