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: Upgrade AWS Provider to v6 (Keep NTC at v1.x)
Upgrade the AWS provider to v6 before upgrading NTC modules. This writes the new region attribute into the Terraform state for all existing resources. Without this intermediate step, resources managed through non-default (aliased) providers may be flagged for recreation during the NTC v2 upgrade.
Update only the provider version constraint. Keep all existing provider aliases and NTC module versions unchanged.
terraform {
required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # ← Updated from ~> 5.0
}
}
}
# Keep all existing provider aliases unchanged
provider "aws" {
region = "eu-central-1"
}
provider "aws" {
alias = "euc1"
region = "eu-central-1"
}
provider "aws" {
alias = "use1"
region = "us-east-1"
}
Initialize and apply:
terraform init -upgrade
terraform plan
# Expected: in-place updates adding the region attribute — NO recreations
terraform apply
Step 2: Simplify Provider Configuration
After the provider upgrade is applied, simplify the provider configuration for NTC v2:
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 3: 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 4: 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 5: 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 6: 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 7: 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 8: Apply Migration
terraform apply
Monitor the output. The migration should complete without errors.
Step 9: 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