AikidoSec/aws-native-terraform-module: Terraform That Deploys Like an AWS Control Plane

It uses Terraform where orchestration belongs, StackSets where AWS can fan out cleanly, and `moved` blocks to refactor live security roles without a destructive rewrite.

9 min read · AikidoSec/aws-native-terraform-module

A control room with a Terraform console on the left feeding a central switchboard, then a StackSet relay pushing identical security packages into a wall of AWS account cabinets on the right. The scene explains how one management plane can coordinate many member accounts without duplicating the rollout logic.
One orchestrator, many targets. Terraform stays in the hub, StackSets do the fan-out.
Key Takeaways

The odd thing about this repo is not that it uses Terraform. It is that Terraform is not doing the part AWS already does best. AikidoSec/aws-native-terraform-module uses Terraform as the control plane, then hands distribution to CloudFormation StackSets so the same security posture can land across member accounts without a custom rollout system.

Terraform in the hub, StackSets in the spokes

That split is the whole point. The root module handles orchestration in the management account, while StackSets push the member-account side of the setup through AWS-native fan-out. For an AWS Organization, that is a cleaner boundary than trying to make Terraform impersonate a distribution service across every account.

A visual map of the repo’s two-plane design. Terraform manages the hub, StackSets handle the spokes, and feature toggles decide how far the rollout travels.

The security model is shaped, not broad

The IAM layer is not trying to be generous. It is trying to be precise. The module enforces an external ID for role assumption, layers a custom `aikido_security_audit` policy on top of AWS-managed permissions, and only creates scanning resources when the relevant feature flags are on.

variable "external_id" {
  type = string
}

resource "aws_iam_role_policy" "ebs_scan" {
  name = "aikido-ebs-scan"
  role = aws_iam_role.aikido.name

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["ec2:DescribeSnapshots"]
        Resource = "*"
      },
      {
        Effect   = "Allow"
        Action   = ["ec2:DeleteSnapshot"]
        Resource = "*"
        Condition = {
          StringEquals = {
            "ec2:ResourceTag/aikido_snapshot" = "true"
          }
        }
      }
    ]
  })
}
A close-up of a vault of EBS snapshots where one snapshot card carries a visible tag and another does not. Only the tagged card can pass through a narrow lock, illustrating how deletion is allowed only when a resource matches an explicit condition.
Least privilege becomes concrete when deletion is gated by a tag, not a blanket permission.

Least privilege, but with deliberate tradeoffs

The cleanest detail in the repo is also the most opinionated. The `enable_comprehensive_permissions` toggle admits that full scan coverage and quiet CloudTrail logs are not the same goal. Turn it off, and the module stays leaner, but Aikido will hit more `Access Denied` responses while probing the account. That is not a bug. It is an explicit tradeoff.

A split editorial scene shows manual account-by-account duplication on one side and organization-aware rollout on the other. The left side is cluttered with repeated isolated roles and brittle update notes, while the right side has one central definition distributing consistent security controls to many accounts.
The hybrid pattern is more complex, but it replaces repetitive rollout work with one managed path.

How to refactor a live security module without breaking it

This is where the repo stops looking like a module and starts looking like an operating discipline. Version 2.0.0 split the logic into submodules, the repository keeps linting and release automation in place, and `moved` blocks preserve resource identity while the file structure changes. That means the maintainers can reshape the code without forcing Terraform to tear down live IAM roles and rebuild them from scratch.

moved {
  from = aws_iam_role.aikido
  to   = module.iam_roles.aws_iam_role.aikido
}

moved {
  from = aws_iam_role_policy.aikido_security_audit
  to   = module.iam_roles.aws_iam_role_policy.aikido_security_audit
}

resource "aws_cloudformation_stack_set" "member_accounts" {
  name = "aikido-security-rollout"
}
A modular filing cabinet is being split into smaller drawers, but a gold thread stays attached to the same live AWS role badge. The visual explains that the code can move between submodules while the actual infrastructure identity remains stable.
The `moved` block is the smallest possible refactor story: change the code, keep the live resource.

That is the important part. Terraform can rename resources. What matters here is that the repo treats refactoring as a rollout constraint, not a cleanup task.

What this beats, and what it costs

ModelRollout modelOperational complexityOrg-scale fitRefactor safetyLeast-privilege controlBest use case
Pure Terraform per accountRepeat the same module in each accountLow at first, then duplication and driftWeak unless you add more toolingDepends on manual disciplineStrong in theory, noisy in practiceSmall estates or one-off setups
AWS-native StackSets onlyPush a template from AWS into member accountsModerate, with less Terraform glueStrong for fan-outMedium, but code-level refactors are less expressiveGood, but the authoring model is narrowerStandardized AWS-only rollouts
Aikido hybrid moduleTerraform orchestrates, StackSets distributeHigher upfront, lower rollout friction laterStrong, because the org boundary is explicitStrong, thanks to `moved` blocks and submodulesStrongest balance, because policy shape stays localizedSecurity controls in AWS Organizations
Two deployment philosophies are shown side by side. One side is a tangle of duplicated account-specific scripts and brittle permissions, while the other is a single controlled path branching cleanly into an AWS Organization.
The hybrid approach is not simpler. It is more disciplined about where complexity belongs.

The hybrid wins when rollout safety matters more than simplicity. If you only need one account, it is probably overbuilt. If you need to touch many accounts without rewriting live IAM roles, the extra moving parts are the point.