Skip to main content

NTC Security Tooling Migration (v1 → v2)

This guide covers the migration of terraform-aws-ntc-security-tooling from v1.x to v2.x.

Overview of Changes

v2.0.0 introduces significant architectural improvements to simplify multi-region security service configurations:

Key Changes

Featurev1.xv2.x
Provider ArchitectureAWS Provider v5 with multiple aliasesAWS Provider v6 with region attribute
Regional ConfigurationSeparate regional-security-config submodule per regionIntegrated into root module with regions arrays
Multi-Region SupportMultiple submodule calls + provider aliasesSingle module with regional configuration arrays
Module Complexity1 root + N regional modulesSingle root module
Home RegionImplicit (default provider)Explicit region parameter

Before & After Comparison

Previous approach required separate module configurations per region:

# Main Security Hub module (home region)
module "ntc_security_tooling" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling?ref=1.8.0"

# Security Hub, enrichment, notifications, etc.
# ...
}

# Separate regional module for Frankfurt (eu-central-1)
module "ntc_regional_security_config_euc1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling//modules/regional-security-config?ref=1.8.0"

guardduty_config = {
enabled = true
# ... configuration
}

inspector_config = {
enabled = true
# ... configuration
}

iam_access_analyzer_config = [
# ... configuration
]

providers = {
aws = aws.euc1
}
}

# Separate regional module for Virginia (us-east-1)
module "ntc_regional_security_config_use1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling//modules/regional-security-config?ref=1.8.0"

guardduty_config = {
enabled = true
# ... configuration
}

inspector_config = {
enabled = true
# ... configuration
}

iam_access_analyzer_config = [
# ... configuration
]

providers = {
aws = aws.use1
}
}

Breaking Changes

Breaking Changes

The following changes require a migration to avoid resource recreation:

  1. Module Structure: Regional security services moved from submodules to root module
  2. Resource Paths: All regional resources now under module.ntc_security_tooling.module.regional_security_config[region].*
  3. Configuration Format: guardduty_config, inspector_config, iam_access_analyzer_config renamed to guardduty_configuration, inspector_configuration, iam_access_analyzers_configuration
  4. Home Region: Now explicitly set via region parameter (must be a standard AWS region, not opt-in)

Migration Steps

Step 1: Update Provider Configuration

Update the terraform.required_providers to use AWS Provider v6:

main.tf
terraform {
required_version = ">= 1.3.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

provider "aws" {
region = "eu-central-1"
}

provider "aws" {
alias = "euc1"
region = "eu-central-1"
}

provider "aws" {
alias = "use1"
region = "us-east-1"
}

Step 2: Consolidate Module Configuration

Extract regional security configurations from submodules and consolidate into the root module:

# ---------------------------------------------------------------------------------------------------------------------
# ¦ NTC SECURITY TOOLING - MAIN MODULE
# ---------------------------------------------------------------------------------------------------------------------
module "ntc_security_tooling" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling?ref=1.8.0"

# Security Hub, enrichment, notifications
# (no region specification - uses default provider)
# ...
}

# ---------------------------------------------------------------------------------------------------------------------
# ¦ NTC SECURITY TOOLING - REGIONAL CONFIGURATION - FRANKFURT
# ---------------------------------------------------------------------------------------------------------------------
module "ntc_regional_security_config_euc1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling//modules/regional-security-config?ref=1.8.0"

guardduty_config = {
enabled = true
finding_publishing_frequency = "SIX_HOURS"

organization_config = {
auto_enable = "ALL"
features = [
{
auto_enable = "ALL"
name = "S3_DATA_EVENTS"
},
# ... more features
]
}

export_findings = true
log_archive_bucket_arn = "arn:aws:s3:::log-bucket"
log_archive_kms_key_arn = "arn:aws:kms:..."
}

inspector_config = {
enabled = true

organization_config = {
auto_enable = true
features = [
{
auto_enable = "NEW"
name = "EC2"
},
# ... more features
]
}
}

iam_access_analyzer_config = [
{
analyzer_name = "ntc-external-access-analysis"
findings_type = "external_access_analysis"
findings_scope = "organization"
rules = [
# ... rules
]
},
{
analyzer_name = "ntc-unused-access-analysis"
findings_type = "unused_access_analysis"
findings_scope = "organization"
unused_access_age = 90
rules = [
# ... rules
]
}
]

providers = {
aws = aws.euc1
}
}

# ---------------------------------------------------------------------------------------------------------------------
# ¦ NTC SECURITY TOOLING - REGIONAL CONFIGURATION - VIRGINIA
# ---------------------------------------------------------------------------------------------------------------------
module "ntc_regional_security_config_use1" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling//modules/regional-security-config?ref=1.8.0"

guardduty_config = {
enabled = true
finding_publishing_frequency = "ONE_HOUR" # Regional override
# ... same configuration
}

inspector_config = {
enabled = true
# ... same configuration
}

iam_access_analyzer_config = [
{
analyzer_name = "ntc-external-access-analysis-use1" # Regional override
findings_type = "external_access_analysis"
findings_scope = "organization"
rules = [
# ... rules
]
}
# Note: unused access analyzer only in one region
]

providers = {
aws = aws.use1
}
}

Step 3: Add State Migration Blocks

Create a state_migrations.tf file to migrate resources without recreating them.

Region-Specific State Migrations

You'll need to create moved blocks for each region you're migrating. The example below shows Frankfurt (eu-central-1). Repeat these blocks for each of your regions (e.g., us-east-1, eu-central-2).

state_migrations.tf
# =====================================================================================================================
# STATE MIGRATIONS - v1.x to v2.x
# =====================================================================================================================
# These moved blocks ensure Terraform state is updated without recreating resources
# Run: terraform plan - should show only "moved" operations (no creates/destroys)
# After successful migration, this file can be deleted

# -------------------------------------------------------------------------------------------------------------------
# GUARDDUTY - FRANKFURT (eu-central-1)
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector.ntc_guardduty[0]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector.ntc_guardduty[0]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector_feature.ntc_guardduty["S3_DATA_EVENTS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector_feature.ntc_guardduty["S3_DATA_EVENTS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector_feature.ntc_guardduty["EBS_MALWARE_PROTECTION"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector_feature.ntc_guardduty["EBS_MALWARE_PROTECTION"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector_feature.ntc_guardduty["RDS_LOGIN_EVENTS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector_feature.ntc_guardduty["RDS_LOGIN_EVENTS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector_feature.ntc_guardduty["LAMBDA_NETWORK_LOGS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector_feature.ntc_guardduty["LAMBDA_NETWORK_LOGS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector_feature.ntc_guardduty["EKS_AUDIT_LOGS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector_feature.ntc_guardduty["EKS_AUDIT_LOGS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_detector_feature.ntc_guardduty["RUNTIME_MONITORING"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_detector_feature.ntc_guardduty["RUNTIME_MONITORING"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration.ntc_guardduty[0]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration.ntc_guardduty[0]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration_feature.ntc_guardduty["S3_DATA_EVENTS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration_feature.ntc_guardduty["S3_DATA_EVENTS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration_feature.ntc_guardduty["EBS_MALWARE_PROTECTION"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration_feature.ntc_guardduty["EBS_MALWARE_PROTECTION"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration_feature.ntc_guardduty["RDS_LOGIN_EVENTS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration_feature.ntc_guardduty["RDS_LOGIN_EVENTS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration_feature.ntc_guardduty["LAMBDA_NETWORK_LOGS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration_feature.ntc_guardduty["LAMBDA_NETWORK_LOGS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration_feature.ntc_guardduty["EKS_AUDIT_LOGS"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration_feature.ntc_guardduty["EKS_AUDIT_LOGS"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_organization_configuration_feature.ntc_guardduty["RUNTIME_MONITORING"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_organization_configuration_feature.ntc_guardduty["RUNTIME_MONITORING"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_guardduty_publishing_destination.ntc_guardduty[0]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_guardduty_publishing_destination.ntc_guardduty[0]
}

# -------------------------------------------------------------------------------------------------------------------
# INSPECTOR - FRANKFURT (eu-central-1)
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_regional_security_config_euc1.aws_inspector2_enabler.ntc_inspector[0]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_inspector2_enabler.ntc_inspector[0]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_inspector2_organization_configuration.ntc_inspector[0]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_inspector2_organization_configuration.ntc_inspector[0]
}

# -------------------------------------------------------------------------------------------------------------------
# IAM ACCESS ANALYZER - FRANKFURT (eu-central-1)
# -------------------------------------------------------------------------------------------------------------------
moved {
from = module.ntc_regional_security_config_euc1.aws_accessanalyzer_analyzer.ntc_analyzer["ntc-external-access-analysis"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_accessanalyzer_analyzer.ntc_analyzer["ntc-external-access-analysis"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_accessanalyzer_analyzer.ntc_analyzer["ntc-unused-access-analysis"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_accessanalyzer_analyzer.ntc_analyzer["ntc-unused-access-analysis"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_accessanalyzer_archive_rule.ntc_analyzer["ntc-external-access-analysis/archive-all-not-public"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_accessanalyzer_archive_rule.ntc_analyzer["ntc-external-access-analysis/archive-all-not-public"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_accessanalyzer_archive_rule.ntc_analyzer["ntc-external-access-analysis/archive-all-ntc-userids"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_accessanalyzer_archive_rule.ntc_analyzer["ntc-external-access-analysis/archive-all-ntc-userids"]
}

moved {
from = module.ntc_regional_security_config_euc1.aws_accessanalyzer_archive_rule.ntc_analyzer["ntc-unused-access-analysis/archive-all-aws-sso-roles"]
to = module.ntc_security_tooling.module.regional_security_config["eu-central-1"].aws_accessanalyzer_archive_rule.ntc_analyzer["ntc-unused-access-analysis/archive-all-aws-sso-roles"]
}

# -------------------------------------------------------------------------------------------------------------------
# REPEAT FOR ADDITIONAL REGIONS (us-east-1, eu-central-2, etc.)
# -------------------------------------------------------------------------------------------------------------------
# Copy the blocks above and replace:
# - module.ntc_regional_security_config_euc1 → module.ntc_regional_security_config_use1
# - ["eu-central-1"] → ["us-east-1"]
# - Adjust analyzer names if using regional overrides (e.g., "ntc-external-access-analysis-use1")
Multiple Regions

If you have multiple regional modules (e.g., ntc_regional_security_config_use1 for us-east-1), you'll need to create similar moved blocks for each region. Simply duplicate the blocks and update:

  • The from module name (e.g., module.ntc_regional_security_config_use1)
  • The region in the to path (e.g., ["us-east-1"])
  • Any regional-specific resource names (e.g., analyzer names with regional suffixes)

Step 4: Remove Old Regional Module Definitions

After consolidating the configuration, remove the regional-security-config module calls:

# DELETE THESE:
# module "ntc_regional_security_config_euc1" { ... }
# module "ntc_regional_security_config_use1" { ... }
# module "ntc_regional_security_config_euc2" { ... }

Step 5: Handle GuardDuty Import (if needed)

If you see errors about GuardDuty detectors already existing, you may need to import them:

data "aws_guardduty_detector" "euc1" {
provider = aws.euc1
}

import {
to = module.ntc_regional_security_config_euc1.aws_guardduty_detector.ntc_guardduty[0]
id = data.aws_guardduty_detector.euc1.id
}

Step 6: Initialize and Plan

# Upgrade to AWS Provider v6
terraform init -upgrade

# Review the plan
terraform plan

Expected plan output:

  • ✅ Many moved operations (state migrations)
  • ✅ Some in-place updates (configuration changes)
  • ✅ Possible additions for new region parameter metadata
  • NO resource deletions or recreations of security services

If you see deletions or recreations, do NOT apply! Review your configuration and state migration blocks carefully.

Step 7: Apply Migration

terraform apply

Monitor the output. The migration should complete without errors.

Step 8: Clean Up

After successful migration:

# Remove the state migration file (optional, but recommended)
rm state_migrations.tf

# Remove old provider aliases if no longer needed
# (Only remove if other modules don't use them)

# Commit your changes
git add .
git commit -m "Migrate ntc-security-tooling from v1.x to v2.x"

Configuration Changes Summary

Parameter Renames

v1.x Parameterv2.x ParameterNotes
guardduty_configguardduty_configurationNow includes regions array
inspector_configinspector_configurationNow includes regions array
iam_access_analyzer_configiam_access_analyzers_configurationNow includes regions array
(implicit)regionNew: Explicit home region parameter

New Features in v2.x

  • Explicit Home Region: Set via region parameter (must be standard AWS region)
  • Regional Overrides: Override specific settings per region using region_overrides maps
  • Simplified Configuration: Single module instead of multiple submodules
  • Better Documentation: Comprehensive inline comments in module source

Complete Example

See a complete before/after example in customer repositories:

Troubleshooting

Issue: Resources Being Recreated

Symptom: terraform plan shows deletions and recreations of GuardDuty detectors, Inspector enablers, or Access Analyzers

Solution:

  1. Check your moved blocks match your existing state exactly
  2. Use terraform state list to see current resource addresses
  3. Verify you've created moved blocks for all regions (not just one)
  4. Ensure module names in from match your actual v1.x module names

Issue: Provider Configuration Errors

Symptom: "provider configuration not present" errors or "region not supported" errors

Solution:

  1. Ensure AWS Provider v6 is installed (terraform init -upgrade)
  2. Verify your required_providers block has version = "~> 6.0"
  3. Check that your region parameter uses a standard AWS region (not opt-in like eu-central-2)

Issue: GuardDuty Already Exists Error

Symptom: Error creating GuardDuty detector because it already exists

Solution:

  1. Use the import blocks shown in Step 5
  2. Import the existing GuardDuty detector before applying
  3. Alternatively, add moved blocks to migrate from v1.x state

Issue: Missing Archive Rules for IAM Access Analyzer

Symptom: Archive rules not showing up or being deleted/recreated

Solution:

  1. Check the archive rule key format in moved blocks: "analyzer-name/rule-name"
  2. Use terraform state list to find the exact resource addresses
  3. Ensure rule names match exactly between v1.x and v2.x configurations

Issue: Regional Overrides Not Working

Symptom: Regional-specific settings (like analyzer names or finding frequencies) not applying

Solution:

  1. Verify region_overrides map keys match the regions in your regions array
  2. Check that override values match the expected data type (string, number, map)
  3. Review the module documentation for supported override fields

Need Help?