|
| 1 | +--- |
| 2 | +title: "Migrating to Next-Gen Components with Atmos Auth" |
| 3 | +slug: nextgen-component-migration |
| 4 | +authors: [Benbentwo] |
| 5 | +tags: [reference-architecture, identity, migration, atmos-auth, components] |
| 6 | +date: 2026-02-25 |
| 7 | +--- |
| 8 | +import Intro from '@site/src/components/Intro'; |
| 9 | +import Steps from '@site/src/components/Steps'; |
| 10 | + |
| 11 | +<Intro> |
| 12 | +Over the past couple of months, we've shipped two changes that fundamentally simplify how components authenticate with AWS: **Atmos Auth** and the **deprecation of `account-map`**. This post is a practical migration guide for upgrading any component — old or new — to work with the next generation of the Cloud Posse Reference Architecture. |
| 13 | +</Intro> |
| 14 | + |
| 15 | +<!--truncate--> |
| 16 | + |
| 17 | +## What Changed |
| 18 | + |
| 19 | +Two major improvements landed recently: |
| 20 | + |
| 21 | +| Change | What It Does | |
| 22 | +|--------|--------------| |
| 23 | +| **Atmos Auth** | Handles AWS authentication before Terraform runs — no more dynamic role assumption in `providers.tf` | |
| 24 | +| **Account-Map Deprecation** | Replaces the `account-map` Terraform component with a static YAML variable, eliminating a critical deploy-time dependency | |
| 25 | + |
| 26 | +Together, these remove the need for `account-map`, `aws-teams`, and `aws-team-roles`. If you missed the announcement, see [Reference Architecture v2: Deprecating Account-Map](/blog/deprecate-account-map/). |
| 27 | + |
| 28 | +## How to Tell Which Generation You're On |
| 29 | + |
| 30 | +The quickest way to tell is to look at your component's `providers.tf`. |
| 31 | + |
| 32 | +### Legacy (Account-Map + Team Roles) |
| 33 | + |
| 34 | +If your `providers.tf` looks like this, you're on the older generation: |
| 35 | + |
| 36 | +```hcl |
| 37 | +provider "aws" { |
| 38 | + region = var.region |
| 39 | + |
| 40 | + # Profile is deprecated in favor of terraform_role_arn. When profiles are not in use, terraform_profile_name is null. |
| 41 | + profile = module.iam_roles.terraform_profile_name |
| 42 | + |
| 43 | + dynamic "assume_role" { |
| 44 | + # module.iam_roles.terraform_role_arn may be null, in which case do not assume a role. |
| 45 | + for_each = compact([module.iam_roles.terraform_role_arn]) |
| 46 | + content { |
| 47 | + role_arn = assume_role.value |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +module "iam_roles" { |
| 53 | + source = "../account-map/modules/iam-roles" |
| 54 | + context = module.this.context |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +This pattern depends on `account-map`, `aws-teams`, and `aws-team-roles` being deployed. The `iam_roles` module reaches into the `account-map` remote state to figure out which role to assume. |
| 59 | + |
| 60 | +### Next-Gen (Atmos Auth) |
| 61 | + |
| 62 | +With Atmos Auth, authentication happens _before_ Terraform runs. The provider is simply: |
| 63 | + |
| 64 | +```hcl |
| 65 | +provider "aws" { |
| 66 | + region = var.region |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +That's it. Whatever AWS credentials Atmos Auth has configured for the target account are already active. No dynamic role assumptions, no remote state lookups, no `account-map` dependency. |
| 71 | + |
| 72 | +The full next-gen `providers.tf` also includes the `account_map` variable (a static map of account names to IDs) and a dummy `iam_roles` module for backward compatibility: |
| 73 | + |
| 74 | +```hcl |
| 75 | +variable "account_map_enabled" { |
| 76 | + type = bool |
| 77 | + description = "Enable the account map component" |
| 78 | + default = false |
| 79 | +} |
| 80 | + |
| 81 | +variable "account_map" { |
| 82 | + type = object({ |
| 83 | + full_account_map = map(string) |
| 84 | + audit_account_account_name = optional(string, "") |
| 85 | + root_account_account_name = optional(string, "") |
| 86 | + identity_account_account_name = optional(string, "") |
| 87 | + aws_partition = optional(string, "aws") |
| 88 | + iam_role_arn_templates = optional(map(string), {}) |
| 89 | + }) |
| 90 | + description = "Map of account names to account IDs." |
| 91 | + default = { |
| 92 | + full_account_map = {} |
| 93 | + audit_account_account_name = "" |
| 94 | + root_account_account_name = "" |
| 95 | + identity_account_account_name = "" |
| 96 | + aws_partition = "aws" |
| 97 | + iam_role_arn_templates = {} |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +provider "aws" { |
| 102 | + region = var.region |
| 103 | +} |
| 104 | + |
| 105 | +# Dummy module to satisfy legacy references to module.iam_roles |
| 106 | +module "iam_roles" { |
| 107 | + source = "cloudposse/label/null" |
| 108 | + context = module.this.context |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +## Migrating Any Component with Mixins |
| 113 | + |
| 114 | +Here's the key insight: **you don't need to manually rewrite `providers.tf`**. We publish Atmos mixins that handle this for you. Just add two lines to your component's `component.yaml`. |
| 115 | + |
| 116 | +### The Two Mixins |
| 117 | + |
| 118 | +Add the following to the `mixins` section of your `component.yaml`: |
| 119 | + |
| 120 | +```yaml |
| 121 | +# components/terraform/<component-name>/component.yaml |
| 122 | +apiVersion: atmos/v1 |
| 123 | +kind: ComponentVendorConfig |
| 124 | +spec: |
| 125 | + source: |
| 126 | + uri: github.com/cloudposse-terraform-components/aws-<component>.git//src?ref={{ .Version }} |
| 127 | + version: v1.x.x |
| 128 | + included_paths: |
| 129 | + - "**/**" |
| 130 | + excluded_paths: |
| 131 | + - "providers.tf" # Exclude the upstream providers.tf |
| 132 | + mixins: |
| 133 | + # Use upstream mixin for providers.tf without account-map dependency |
| 134 | + - uri: https://raw.githubusercontent.com/cloudposse-terraform-components/mixins/{{ .Version }}/src/mixins/provider-without-account-map.tf |
| 135 | + version: v0.3.2 |
| 136 | + filename: providers.tf |
| 137 | + # Use upstream mixin for account verification |
| 138 | + - uri: https://raw.githubusercontent.com/cloudposse-terraform-components/mixins/{{ .Version }}/src/mixins/account-verification.mixin.tf |
| 139 | + version: v0.3.2 |
| 140 | + filename: account-verification.mixin.tf |
| 141 | +``` |
| 142 | + |
| 143 | +Then vendor (or re-vendor) the component: |
| 144 | + |
| 145 | +```bash |
| 146 | +atmos vendor pull -c <component-name> |
| 147 | +``` |
| 148 | + |
| 149 | +### What Each Mixin Does |
| 150 | + |
| 151 | +**`provider-without-account-map.tf`** (vendored as `providers.tf`) |
| 152 | + |
| 153 | +This replaces the legacy `providers.tf`. It: |
| 154 | + |
| 155 | +<Steps> |
| 156 | +1. Defines the `account_map_enabled` and `account_map` variables so your component can receive the static account map from stack configuration |
| 157 | +1. Configures the AWS provider with just `region = var.region` — Atmos Auth handles the credentials |
| 158 | +1. Includes a dummy `iam_roles` module so existing code that references `module.iam_roles` doesn't break |
| 159 | +</Steps> |
| 160 | + |
| 161 | +**`account-verification.mixin.tf`** |
| 162 | + |
| 163 | +This is a safety net. It verifies that the AWS account you're deploying to is the one you _intend_ to deploy to. If Atmos Auth is misconfigured and you're authenticated to the wrong account, this mixin will catch it before Terraform makes any changes. |
| 164 | + |
| 165 | +## Step-by-Step Migration |
| 166 | + |
| 167 | +### 1. Set Up Atmos Auth |
| 168 | + |
| 169 | +Configure Atmos Auth profiles in your `atmos.yaml`. This tells Atmos how to authenticate to each account before running Terraform: |
| 170 | + |
| 171 | +```bash |
| 172 | +# Authenticate with your profile |
| 173 | +atmos auth login |
| 174 | +``` |
| 175 | + |
| 176 | +See [Atmos Auth](https://atmos.tools/cli/auth) for configuration details. |
| 177 | + |
| 178 | +### 2. Add the Static Account Map |
| 179 | + |
| 180 | +Define account IDs in your stack defaults so components can look up accounts without remote state: |
| 181 | + |
| 182 | +```yaml |
| 183 | +# stacks/orgs/acme/_defaults.yaml |
| 184 | +vars: |
| 185 | + account_map_enabled: false |
| 186 | + account_map: |
| 187 | + full_account_map: |
| 188 | + core-root: "123456789012" |
| 189 | + core-artifacts: "234567890123" |
| 190 | + core-audit: "345678901234" |
| 191 | + core-auto: "456789012345" |
| 192 | + plat-dev: "567890123456" |
| 193 | + plat-staging: "678901234567" |
| 194 | + plat-prod: "789012345678" |
| 195 | + root_account_account_name: core-root |
| 196 | + audit_account_account_name: core-audit |
| 197 | +``` |
| 198 | + |
| 199 | +### 3. Update component.yaml |
| 200 | + |
| 201 | +For each component, add the mixins and exclude the upstream `providers.tf` as shown above. Then re-vendor: |
| 202 | + |
| 203 | +```bash |
| 204 | +atmos vendor pull -c <component-name> |
| 205 | +``` |
| 206 | + |
| 207 | +### 4. Verify |
| 208 | + |
| 209 | +Run a plan against a non-production environment to confirm everything works: |
| 210 | + |
| 211 | +```bash |
| 212 | +atmos terraform plan <component-name> -s plat-ue1-dev |
| 213 | +``` |
| 214 | + |
| 215 | +The plan should succeed without any `account-map` remote state lookups. |
| 216 | + |
| 217 | +## Works Regardless of Component Version |
| 218 | + |
| 219 | +The mixins approach works whether you're running the latest component versions or older ones. The key is the `excluded_paths` and `mixins` configuration in `component.yaml`: |
| 220 | + |
| 221 | +<Steps> |
| 222 | +1. `excluded_paths: ["providers.tf"]` — Throws away whatever `providers.tf` ships with the component |
| 223 | +1. The `provider-without-account-map.tf` mixin replaces it with the next-gen version |
| 224 | +1. The `account-verification.mixin.tf` mixin adds the safety check |
| 225 | +</Steps> |
| 226 | + |
| 227 | +This means you can migrate components incrementally — update one at a time, test, and move on — without needing to upgrade the component source code itself. |
| 228 | + |
| 229 | +## Summary |
| 230 | + |
| 231 | +| Before | After | |
| 232 | +|--------|-------| |
| 233 | +| `account-map` component deployed first | Static `account_map` variable in stack config | |
| 234 | +| `aws-teams` + `aws-team-roles` for IAM | AWS SSO Permission Sets + Atmos Auth | |
| 235 | +| Dynamic role assumption in `providers.tf` | Atmos Auth handles credentials before Terraform | |
| 236 | +| Complex `iam_roles` module in every component | Simple `region = var.region` provider | |
| 237 | +| Tight coupling between components via remote state | Components are independent | |
| 238 | + |
| 239 | +The net result: fewer components to manage, simpler authentication, no deploy ordering dependencies, and a provider configuration you can actually read at a glance. |
| 240 | + |
| 241 | +## Learn More |
| 242 | + |
| 243 | +<Steps> |
| 244 | +1. [Deprecating Account-Map](/blog/deprecate-account-map/) — The full deprecation announcement |
| 245 | +1. [Migrate from Account-Map](/layers/project/tutorials/migrate-from-account-map/) — Detailed step-by-step migration guide |
| 246 | +1. [Atmos Auth](https://atmos.tools/cli/auth) — Authentication configuration reference |
| 247 | +1. [How to Log into AWS](/layers/identity/how-to-log-into-aws/) — Authentication workflows for human users |
| 248 | +</Steps> |
| 249 | + |
| 250 | +:::tip Need Help? |
| 251 | +Migrating core authentication infrastructure is a significant change. If you need assistance, reach out in the [SweetOps Slack](https://cloudposse.com/slack) or contact [Cloud Posse support](https://cloudposse.com/support). |
| 252 | +::: |
0 commit comments