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
| Feature | v1.x | v2.x |
|---|---|---|
| Provider Architecture | AWS Provider v5 with multiple aliases | AWS Provider v6 with region attribute |
| Regional Configuration | Separate regional-security-config submodule per region | Integrated into root module with regions arrays |
| Multi-Region Support | Multiple submodule calls + provider aliases | Single module with regional configuration arrays |
| Module Complexity | 1 root + N regional modules | Single root module |
| Home Region | Implicit (default provider) | Explicit region parameter |
Before & After Comparison
- v1.x Structure
- v2.x Structure
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
}
}
New consolidated approach with a single module:
# All-in-one configuration
module "ntc_security_tooling" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling?ref=2.0.0"
# Explicit home region (required)
region = "eu-central-1"
# GuardDuty configuration with multi-region support
guardduty_configuration = {
regions = ["eu-central-1", "us-east-1"]
# ... configuration
}
# Inspector configuration with multi-region support
inspector_configuration = {
regions = ["eu-central-1", "us-east-1"]
# ... configuration
}
# IAM Access Analyzer with multi-region support
iam_access_analyzers_configuration = [
{
regions = ["eu-central-1", "us-east-1"]
# ... configuration
}
]
}
Breaking Changes
The following changes require a migration to avoid resource recreation:
- Module Structure: Regional security services moved from submodules to root module
- Resource Paths: All regional resources now under
module.ntc_security_tooling.module.regional_security_config[region].* - Configuration Format:
guardduty_config,inspector_config,iam_access_analyzer_configrenamed toguardduty_configuration,inspector_configuration,iam_access_analyzers_configuration - Home Region: Now explicitly set via
regionparameter (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:
- Before (v1.x)
- After (v2.x)
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"
}
terraform {
required_version = ">= 1.5.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "eu-central-1"
}
# Regional provider aliases no longer needed for ntc-security-tooling
# The module handles multi-region internally using the 'region' attribute
Step 2: Consolidate Module Configuration
Extract regional security configurations from submodules and consolidate into the root module:
- Old Configuration
- New Configuration
# ---------------------------------------------------------------------------------------------------------------------
# ¦ 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
}
}
# ---------------------------------------------------------------------------------------------------------------------
# ¦ NTC SECURITY TOOLING - ALL-IN-ONE CONFIGURATION
# ---------------------------------------------------------------------------------------------------------------------
module "ntc_security_tooling" {
source = "github.com/nuvibit-terraform-collection/terraform-aws-ntc-security-tooling?ref=2.0.0"
# Explicit home region (required)
region = "eu-central-1"
# Security Hub, enrichment, notifications
# ... (unchanged from v1.x)
# GuardDuty configuration (consolidated)
guardduty_configuration = {
regions = ["eu-central-1", "us-east-1"]
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:..."
# Regional overrides
region_overrides = {
"us-east-1" = {
finding_publishing_frequency = "ONE_HOUR"
}
}
}
# Inspector configuration (consolidated)
inspector_configuration = {
regions = ["eu-central-1", "us-east-1"]
organization_config = {
auto_enable = true
features = [
{
auto_enable = "NEW"
name = "EC2"
},
# ... more features
]
}
# Regional overrides (if needed)
region_overrides = {}
}
# IAM Access Analyzer configuration (consolidated)
iam_access_analyzers_configuration = [
{
regions = ["eu-central-1", "us-east-1"]
analyzer_name = "ntc-external-access-analysis"
findings_type = "external_access_analysis"
findings_scope = "organization"
rules = [
# ... rules
]
# Regional overrides
region_overrides = {
"us-east-1" = {
analyzer_name = "ntc-external-access-analysis-use1"
}
}
},
{
regions = ["eu-central-1"] # Single region only
analyzer_name = "ntc-unused-access-analysis"
findings_type = "unused_access_analysis"
findings_scope = "organization"
unused_access_age = 90
rules = [
# ... rules
]
region_overrides = {}
}
]
}
Step 3: Add State Migration Blocks
Create a state_migrations.tf file to migrate resources without recreating them.
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 - 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")
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
frommodule name (e.g.,module.ntc_regional_security_config_use1) - The region in the
topath (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:
- v1.x Import (Old)
- v2.x Import (New)
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
}
data "aws_guardduty_detector" "existing" {
for_each = toset(["eu-central-1", "us-east-1"]) # replace with regions where guardduty will be configured
region = each.key
}
import {
for_each = toset(["eu-central-1", "us-east-1"]) # replace with regions where guardduty will be configured
to = module.ntc_security_tooling.module.regional_security_config[each.key].aws_guardduty_detector.ntc_guardduty[0]
id = "${data.aws_guardduty_detector.existing[each.key].id}@${each.key}"
}
Step 6: Initialize and Plan
# Upgrade to AWS Provider v6
terraform init -upgrade
# Review the plan
terraform plan
Expected plan output:
- ✅ Many
movedoperations (state migrations) - ✅ Some in-place updates (configuration changes)
- ✅ Possible additions for new
regionparameter 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 Parameter | v2.x Parameter | Notes |
|---|---|---|
guardduty_config | guardduty_configuration | Now includes regions array |
inspector_config | inspector_configuration | Now includes regions array |
iam_access_analyzer_config | iam_access_analyzers_configuration | Now includes regions array |
| (implicit) | region | New: Explicit home region parameter |
New Features in v2.x
- Explicit Home Region: Set via
regionparameter (must be standard AWS region) - Regional Overrides: Override specific settings per region using
region_overridesmaps - 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:
- Check your
movedblocks match your existing state exactly - Use
terraform state listto see current resource addresses - Verify you've created
movedblocks for all regions (not just one) - Ensure module names in
frommatch your actual v1.x module names
Issue: Provider Configuration Errors
Symptom: "provider configuration not present" errors or "region not supported" errors
Solution:
- Ensure AWS Provider v6 is installed (
terraform init -upgrade) - Verify your
required_providersblock hasversion = "~> 6.0" - Check that your
regionparameter 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:
- Use the import blocks shown in Step 5
- Import the existing GuardDuty detector before applying
- Alternatively, add
movedblocks 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:
- Check the archive rule key format in
movedblocks:"analyzer-name/rule-name" - Use
terraform state listto find the exact resource addresses - 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:
- Verify
region_overridesmap keys match the regions in yourregionsarray - Check that override values match the expected data type (string, number, map)
- Review the module documentation for supported override fields
Need Help?
- 📚 Review the main migration guide
- 🐛 Check common issues
- 📖 Read the NTC Security Tooling documentation
- 💬 Contact NTC Support