AWS → STACKIT · ARCHITECTURE

From an AWS landing zone to a STACKIT organisation

An AWS landing zone is accounts, organizational units, service control policies, a shared network and an audit trail. STACKIT offers an organisation, folders, projects and a network area, but no policy-attachment layer. StackToIT assessed a compact landing zone: half of it mapped to Terraform, the other half came back as design work for your architects.

Three ways to lay out the organisation

The question is how much of the account boundary you need to keep. The run treats the AWS organisation, its units and its accounts as design work, not as resources to copy.

LayoutHow it mapsTrade-off
One organisation, folders, one project per accountAn organizational unit becomes a folder; an account becomes a project. This is the comparable grouping the run proposes.Closest to the AWS shape. The run notes that a project is not a separate account, billing and IAM boundary, so the access policy is re-created, not copied.
A separate organisation for accounts that need hard isolationThe run names this for accounts that need real isolation from the rest.Strongest separation, but a second organisation means its own roles, its own network area and a second place to administer.
One project with several networksEach subnet becomes a network in one project; the project is the boundary of the VPC.Simplest for a single-VPC estate. Security groups are project-wide, so separation between tiers is kept with groups, not with accounts.

Resource Manager roles manage the hierarchy and, per STACKIT’s documentation, do not grant access to cloud resources inside a project. Plan the role bindings per project explicitly.

What the run made of each landing-zone resource

Eleven units: 1 DIRECT, 5 RECONFIGURE, 5 REDESIGN. Five were drafted in Terraform, one needs no resource and five are left to an architect.

AWS resourceSTACKIT targetClass and confidence
aws_organizations_organizationNone: the organisation is not created by the provider; roles are designed on organisation, folders and projectsREDESIGN, MEDIUM
aws_organizations_organizational_unitResource Manager folder, a grouping level with no policy attachmentREDESIGN, LOW
aws_organizations_accountResource Manager project, a comparable container levelREDESIGN, LOW
aws_organizations_policy (service control)None managed: policy as code in the delivery pipelineREDESIGN, LOW
aws_vpc and aws_subnetNetworks in the projectRECONFIGURE, MEDIUM
aws_ec2_transit_gatewayNetwork area and network area regionRECONFIGURE, MEDIUM
aws_kms_keyKMS key ring and keyRECONFIGURE, MEDIUM
aws_s3_bucket (trail)Object Storage bucketDIRECT, MEDIUM
aws_cloudtrail (organisation trail)Telemetry Router instance with an Object Storage destinationRECONFIGURE, MEDIUM
aws_guardduty_detectorNone: detection is redesigned on Audit Log and Logs with a SIEM or third-party toolREDESIGN, MEDIUM

The mapping names Resource Manager folders and projects, but the draft writes no block for them. The landing-zone requirements file of the run lists both as mapped and not created.

Hub network and transit gateway

Input written for this page and assessed on 4 November 2026. Left: a VPC, a subnet and a transit gateway. Right: the network area, its region settings and the network the engine drafted, without the review comments above each block.

Example inputmain.tf
resource "aws_vpc" "hub" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "hub_a" {
  vpc_id            = aws_vpc.hub.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "eu-central-1a"
}

resource "aws_ec2_transit_gateway" "hub" {
  description = "shared hub"
}

resource "aws_ec2_transit_gateway_vpc_attachment" "hub" {
  transit_gateway_id = aws_ec2_transit_gateway.hub.id
  vpc_id             = aws_vpc.hub.id
  subnet_ids         = [aws_subnet.hub_a.id]
}
STACKIT Terraform by StackToITterraform/network.tf
resource "stackit_network" "hub_a" {
  project_id       = var.project_id
  name             = "hub-a"
  ipv4_prefix      = "10.0.1.0/24"
  ipv4_nameservers = var.default_ipv4_nameservers
}

resource "stackit_network_area" "migrated" {
  organization_id = var.organization_id
  name            = "migrated-network-area"
}

resource "stackit_network_area_region" "migrated" {
  organization_id = var.organization_id
  network_area_id = stackit_network_area.migrated.network_area_id
  ipv4 = {
    network_ranges   = [for p in var.network_area_ranges : { prefix = p }]
    transfer_network = var.network_area_transfer_network
  }
}
  • The project is the VPC boundary

    There is no VPC container. The subnet became a network in the project, and the transit gateway became a network area that routes privately between networks of the organisation.

  • Separation needs security groups

    Networks in one area reach each other, and the area has no per-attachment object. A VPC that was not connected in AWS can reach its neighbour once both are in the area.

  • Values you supply

    The area’s address ranges and the transfer network are variables, because the source does not state them.

Encryption key and organisation trail

The key and the trail are drafted; the connection of the audit stream is not. First pane: the key, the trail bucket and the trail. Then the key block from terraform/security.tf and the router blocks from terraform/observability.tf, without the review comments above each block. The S3 destination takes its endpoint and access key as variables.

Example inputmain.tf
resource "aws_kms_key" "logs" {
  description         = "log archive key"
  enable_key_rotation = true
}

resource "aws_s3_bucket" "trail" {
  bucket = "acme-org-trail"
}

resource "aws_cloudtrail" "org" {
  name                  = "org-trail"
  s3_bucket_name        = aws_s3_bucket.trail.id
  is_organization_trail = true
  kms_key_id            = aws_kms_key.logs.arn
}
STACKIT Terraform by StackToITterraform/security.tf
resource "stackit_kms_key" "logs" {
  project_id   = var.project_id
  keyring_id   = stackit_kms_keyring.migrated.keyring_id
  display_name = "log-archive-key"
  algorithm    = "aes_256_gcm"
  purpose      = "symmetric_encrypt_decrypt"
  protection   = var.kms_protection
  access_scope = var.kms_access_scope == "SNA" ? "SNA" : null
}
STACKIT Terraform by StackToITterraform/observability.tf
resource "stackit_telemetryrouter_instance" "migrated" {
  project_id   = var.project_id
  display_name = "migrated-telemetry-router"
}

resource "stackit_telemetryrouter_destination" "org" {
  project_id   = var.project_id
  instance_id  = stackit_telemetryrouter_instance.migrated.instance_id
  display_name = "org-trail"
  config = {
    config_type = "S3"
    s3 = {
      bucket   = stackit_objectstorage_bucket.trail.name
      endpoint = var.trail_org_s3_endpoint
      access_key = {
        id     = var.trail_org_s3_access_key_id
        secret = var.trail_org_s3_secret_access_key
      }
    }
  }
}
  • Rotation is not carried

    The provider documentation lists no rotation attribute for stackit_kms_key, so rotation becomes an operational process.

  • The trail’s scope is a link, not a flag

    is_organization_trail has no counterpart. A person links the STACKIT organisation, folder or project to the router with a separate resource that the draft does not write.

  • The router handles STACKIT’s Audit Log

    It delivers the audit events of STACKIT products. Per STACKIT’s documentation, admin activity is recorded by default and visible in the portal for 90 days; routing is how you keep it longer.

  • Key scope is your choice

    access_scope comes from a variable with no default. The provider documentation gives PUBLIC as the default of the attribute, which makes the key reachable on the public KMS endpoint; SNA keeps it reachable over the network area only.

The structure the draft leaves to you

These blocks produced no Terraform. The organizational unit, the account and the policy each came back as REDESIGN with a reason, because their effect lives in policy and people, not in a resource. The policy attachment is assessed as part of the policy unit.

Example inputmain.tf
resource "aws_organizations_organizational_unit" "workloads" {
  name      = "workloads"
  parent_id = aws_organizations_organization.main.roots[0].id
}

resource "aws_organizations_account" "prod" {
  name      = "acme-prod"
  email     = "aws-prod@example.com"
  parent_id = aws_organizations_organizational_unit.workloads.id
}

resource "aws_organizations_policy" "deny_regions" {
  name = "deny-non-eu-regions"
  type = "SERVICE_CONTROL_POLICY"
  content = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Deny"
      Action    = "*"
      Resource  = "*"
      Condition = { StringNotEquals = { "aws:RequestedRegion" = ["eu-central-1", "eu-west-1"] } }
    }]
  })
}

resource "aws_organizations_policy_attachment" "workloads" {
  policy_id = aws_organizations_policy.deny_regions.id
  target_id = aws_organizations_organizational_unit.workloads.id
}

How to decide

  1. Count the real boundaries

    List the accounts and ask what each one separates: billing, identity, blast radius, a regulator. A project covers some of these; a separate organisation covers more.

  2. Move guardrails into the pipeline

    An AWS service control policy limits what roles can do and grants nothing. On STACKIT, the same intent becomes checks in the pipeline that applies Terraform plus careful role design.

  3. Decide the network area early

    Ranges and the transfer network cannot be an afterthought: the STACKIT provider documentation states that a project is created in a network area by setting the label networkArea, and that this cannot be changed after the project is created.

  4. Keep the audit scope explicit

    Write down whether the trail covered one account, all regions or the whole organisation, then link the matching STACKIT scope to the router.

  5. Plan detection separately

    GuardDuty has no managed counterpart in the provider the run used. Decide which tool reads Audit Log and Logs.

Questions architects ask

Does a STACKIT project equal an AWS account?

Not exactly. The run maps an account to a project as a comparable container level and states that a project is not an equivalent account, billing and IAM boundary. Access is bound per project.

Can I keep my service control policies?

There is no managed policy layer in the provider version used. The run proposes policy as code in the pipeline and role design on the organisation, folders and projects.

What replaces the transit gateway?

A network area. Projects in the area connect through an internal transfer network, and routing tables update automatically. Traffic between regions currently goes over the public internet, according to STACKIT’s documentation.

Does StackToIT create the folders and projects?

No. The run lists them as mapped targets without a block in the draft. Your team creates them and sets the area label on each project.

Sources

Your next move: STACKIT.

Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.