Skip to main content

OceanSoft IAM Identity Center — From-Scratch Enablement

Scope: TI-2a — minimum clickops fast-track. Every terraform apply step is marked [HITL]. Agents prepare; HITL executes all mutations. Read fully before opening the AWS console.

Minimum clickops: 1 Enable-SSO click + 1 S3 bucket. Everything else is Terraform.

Operating model

This guide is the fast-track implementation of the reusable terraform-aws/modules/sso module. The 1st pilot consumer is b2b-commerce/infra/terraform/aws/identity/ (profile-only, ADR-021) — all root-level paths below refer to that pilot root. The generic public reference root is terraform-aws/accounts/management-account/.


Section 1 — Sign In / Create the Management Account

Only if no management account exists for [email protected]. If an account already exists, skip to Section 2.

  1. Go to https://aws.amazon.comCreate an AWS Account.
  2. Use email [email protected] (or a sub-addressed variant such as [email protected] to preserve the human inbox — the email must be globally unique across all of AWS).
  3. Complete phone verification and billing details.
  4. Once the account is active, sign in to the AWS Management Console as the root user.

Branch check:

# READONLY verify — run after sign-in to confirm you are in the right account
aws sts get-caller-identity --profile <YOUR_MANAGEMENT_PROFILE>
# Expected: Account matches your management account ID; no cross-account confusion

If the account already exists and you are signed in, proceed directly to Section 2.


5W1H — Management Account

QuestionAnswer
WhyIAM Identity Center must be enabled from a management account; a workload account cannot host an org-level instance.
What if missingCannot enable an organization instance of Identity Center; multi-account permission sets are unavailable.
Business valueA dedicated sso@ management account is the trust anchor for the entire OceanSoft landing zone.
PurposeEstablishes the root account from which the AWS Organization and Identity Center are governed.
Critical thinkingIf an account already exists but you are unsure whether it is the management account, run aws organizations describe-organization — the MasterAccountId must match the account you are signed into.

Section 2 — Enable IAM Identity Center [HITL — the one click]

As the HITL operator, I want to enable IAM Identity Center as an ORGANIZATION instance in my chosen home region so that modules/sso can discover the instance via data.aws_ssoadmin_instances and Terraform manages groups, permission sets, and assignments.

Select ORGANIZATION INSTANCE — account-instance trap

When the Identity Center console asks which instance type to create, you MUST choose "Enable with AWS Organizations" (the organization instance).

Do NOT choose the account-level "Enable" button if a separate organization option is shown. An account instance cannot:

  • provision permission sets to member accounts
  • resolve account names via enable_organizations_lookup
  • scale to a multi-account landing zone

If you accidentally enable an account instance, you must delete it and re-enable — there is no migration path. Confirm the console shows organization instance before clicking Enable.

Enabling the org instance AUTO-CREATES the AWS Organization — do NOT create one manually first

Enabling IAM Identity Center as an organization instance automatically creates an AWS Organization with all features enabled for your management account. You do NOT need to navigate to AWS Organizations and create the organization manually. Doing so first is redundant and adds unnecessary clickops with no correctness benefit.

The old pattern (create Organization → enable Identity Center) is wrong. The correct sequence is: Enable Identity Center (org instance) → Organization is created for you.

Step 2.1 — Choose your home region

The home region is permanent for the lifetime of the instance.

Recommendation: Use <HOME_REGION> (default: ap-southeast-2, the Sydney region, matching var.sso_region in infra/terraform/aws/identity/variables.tf).

The home region must match the aws.identity_center provider alias in infra/terraform/aws/identity/providers.tf.

Step 2.2 — Enable Identity Center [HITL]

  1. Sign in to the AWS Management Console with the management account administrator or root user.
  2. Navigate to IAM Identity Center (search "Identity Center" in the top bar).
  3. Click Enable with AWS Organizations — this is the organization instance option.
  4. Confirm the selected region matches your chosen <HOME_REGION>.
  5. Click Enable.

Post-enable verify:

# READONLY verify
aws sso-admin list-instances \
--profile <YOUR_MANAGEMENT_PROFILE> \
--region <HOME_REGION>
# Expected: Instances array with exactly 1 entry; IdentityStoreId = d-<YOUR_OWN_ID>

GUARD BOX — Foreign Identity Center Instances

NEVER authenticate against these start URLs. They belong to foreign organizations.

Instance IDForeign ownerAction
d-9767913734Foreign org (bound to terraform-aws-sso profile)BLOCKED
d-976752e8d5Employer landing zone (READONLY-only, foreign)BLOCKED

If your aws sso-admin list-instances output contains either of these IDs, STOP. You are querying the wrong account or the wrong region. Sign out and sign back in to the correct management account before continuing.

Your OceanSoft start URL will be of the form: https://d-<YOUR_OWN_ID>.awsapps.com/start

The instance ID in YOUR start URL will NOT match either value above.


5W1H — Identity Center Enablement

QuestionAnswer
WhyIAM Identity Center is a free, org-level, single-pane-of-glass credential broker; enabling it once in the management account gives every member account access via permission-set assignments — no per-account configuration needed.
What if missingdata.aws_ssoadmin_instances in modules/sso fails at plan time; all downstream group/assignment resources cannot be created.
Business valueEliminates per-account IAM user maintenance; all access is managed from one YAML file; audit trail is centralised in CloudTrail under the management account.
PurposeCreates the one immutable anchor (the SSO instance ARN) that every Terraform resource in modules/sso references via a data source.
Critical thinkingYou CANNOT move an Identity Center instance to another region after creation. If you select the wrong home region, you must delete the instance and lose all permission sets and assignments — there is no migration path. Confirm the region matches var.sso_region before clicking Enable.

Section 3 — Bootstrap Credential

As the HITL operator, I want a short-lived credential to run the first terraform apply without leaving a long-lived IAM or root access key in place.

Credential ranking

OptionMethodTeardown requiredRecommendation
A — AWS CloudShellOpen CloudShell in the management-account consoleNone — session credential expires automaticallyPREFERRED
B — Temporary IAM user access keyCreate an IAM user, generate an access key, delete post-bootstrapYes — delete the key and user after applyACCEPTABLE
C — Root access keyREJECT — see danger box
NEVER create a root access key

Root access keys cannot be scoped to least-privilege — they carry full account power. AWS explicitly advises against creating them. Any root access key left in place is a standing CPS 234 (APRA) least-privilege finding and an immediate security incident.

If you are tempted to use a root access key for convenience, use CloudShell instead — it is ephemeral, IAM-role-backed, and requires zero credential management.

Option A — AWS CloudShell [PREFERRED]

  1. In the management-account console, click the CloudShell icon (top navigation bar).
  2. CloudShell opens a browser-based terminal with your console session credentials.
  3. Clone the repo or upload your Terraform files; proceed directly to Section 4.

No access key is created. No teardown step is needed. The credential expires with your console session.

Option B — Temporary IAM user access key [ACCEPTABLE]

# [HITL] — Create a bootstrap user with minimum permissions for the identity Terraform root
aws iam create-user --user-name tf-bootstrap-identity \
--profile <YOUR_MANAGEMENT_PROFILE>

aws iam attach-user-policy \
--user-name tf-bootstrap-identity \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--profile <YOUR_MANAGEMENT_PROFILE>

aws iam create-access-key --user-name tf-bootstrap-identity \
--profile <YOUR_MANAGEMENT_PROFILE>
# Save AccessKeyId and SecretAccessKey — export as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY

Teardown (MANDATORY — run immediately after terraform apply in Section 5 succeeds):

# [HITL] — Delete the temporary credential; admin access via SSO replaces it
aws iam delete-access-key \
--user-name tf-bootstrap-identity \
--access-key-id <ACCESS_KEY_ID> \
--profile <YOUR_MANAGEMENT_PROFILE>

aws iam detach-user-policy \
--user-name tf-bootstrap-identity \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--profile <YOUR_MANAGEMENT_PROFILE>

aws iam delete-user --user-name tf-bootstrap-identity \
--profile <YOUR_MANAGEMENT_PROFILE>

5W1H — Bootstrap Credential

QuestionAnswer
WhyA credential is needed for the first terraform apply before SSO permission sets exist; CloudShell is the least-privilege option because it is ephemeral and IAM-role-backed.
What if missingCannot run terraform apply; no Terraform-managed resources can be created.
Business valueEphemeral credentials leave zero standing access after bootstrap; CPS 234 least-privilege requirement is satisfied by design.
PurposeProvides the one-time Terraform executor credential that is replaced by SSO-managed access after the first apply.
Critical thinkingCloudShell is preferred over a temporary IAM user because it requires zero key management; the session credential is tied to your console MFA and expires automatically.

Section 4 — S3 State Bucket [HITL — the second and last clickops]

As the HITL operator, I want to create the S3 state bucket before running terraform init so that the backend exists independently of the Terraform root that will use it — preventing the SELF_REFERENTIAL_TFSTATE bootstrap deadlock.

SELF_REFERENTIAL_TFSTATE — Do not skip this section

infra/terraform/aws/identity/backend.tf uses bucket: ${ACCOUNT_ID}-tfstate-<HOME_REGION>

If you run terraform init before this bucket exists, Terraform fails to initialise the backend. If you attempt to create the bucket WITH Terraform inside the same root that uses it, you create a bootstrap deadlock — the state backend cannot be written because the bucket does not exist yet.

Always create the state bucket BEFORE running terraform init. Always.

Step 4.1 — Check whether the state bucket already exists

# READONLY verify
aws s3api head-bucket \
--bucket ${ACCOUNT_ID}-tfstate-<HOME_REGION> \
--profile <YOUR_MANAGEMENT_PROFILE> 2>&1
# 200 OK (no error) → bucket exists → skip to Step 4.3
# 404 / NoSuchBucket → continue to Step 4.2

Step 4.2 — Create the state bucket with versioning + SSE [HITL]

# Run inside CloudShell (management account) — [HITL]
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=<HOME_REGION>
BUCKET="${ACCOUNT_ID}-tfstate-${REGION}"

aws s3api create-bucket --bucket "$BUCKET" --region "$REGION" \
--create-bucket-configuration LocationConstraint="$REGION"

aws s3api put-bucket-versioning --bucket "$BUCKET" \
--versioning-configuration Status=Enabled

aws s3api put-bucket-encryption --bucket "$BUCKET" \
--server-side-encryption-configuration \
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

aws s3api put-public-access-block --bucket "$BUCKET" \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

Note: for us-east-1 omit --create-bucket-configuration (it is the default region).

Step 4.3 — Verify bucket is ready

# READONLY verify
aws s3api get-bucket-versioning \
--bucket ${ACCOUNT_ID}-tfstate-<HOME_REGION> \
--profile <YOUR_MANAGEMENT_PROFILE>
# Expected: {"Status": "Enabled"}
ADR-006 — No DynamoDB

backend.tf sets use_lockfile = true. Terraform native S3 locking is used — no DynamoDB table is needed. Do NOT create a lock table.


5W1H — S3 State Backend Bootstrap

QuestionAnswer
WhyTerraform's S3 backend must be initialised before any terraform init is run; the bucket cannot be a resource in the same root that depends on it (anti-pattern: SELF_REFERENTIAL_TFSTATE).
What if missingterraform init fails with NoSuchBucket; the entire Terraform chain is blocked.
Business valueState in S3 with versioning enables point-in-time recovery; use_lockfile = true prevents concurrent apply races without a DynamoDB dependency.
PurposeCreates the durable state store that Terraform uses to track all Identity Center resources; this bucket outlives any single terraform apply.
Critical thinkingThe bucket name convention ${ACCOUNT_ID}-tfstate-<HOME_REGION> is portable. If you rename the bucket later, all engineers must run terraform init -reconfigure. Do not rename after the first terraform apply.

Section 5 — Everything-as-Code: One Terraform Run

As the HITL operator, I want a single terraform apply in infra/terraform/aws/identity/ to create the B2B member account, SSO admin user, groups, permission sets, and assignments — with no further console clickops.

This single Terraform run covers:

ResourceTerraformNotes
B2B member accountaws_organizations_accountemail = [email protected] (variable)
Bootstrap admin usermodules/sso sso_usersPassword set via email OTP after apply
Admin groupmodules/sso sso_groups
Permission setsmodules/ssoAdministratorAccess, PowerUserAccess, ReadOnly, SecurityAudit
Account assignmentsmodules/ssoGroup-to-account mapping from YAML
aws_organizations_account caveats — read before apply
  • Email must be globally unique: [email protected] cannot be reused across any AWS account, ever. Use sub-addressing (e.g., [email protected]) to preserve the human inbox.
  • terraform destroy does NOT close the account: it removes/suspends the account in Organizations with a 90-day close window. The root email stays locked during that period. Treat member-account creation as near-irreversible. The terraform apply below is [HITL]-gated for this reason.

Step 5.1 — Verify SSO instance is live

# READONLY verify — confirm the instance from Section 2 is active
aws sso-admin list-instances \
--profile <YOUR_MANAGEMENT_PROFILE> \
--region <HOME_REGION>
# Expected: 1 instance with your OceanSoft Identity Store ID (not d-9767913734 or d-976752e8d5)

Step 5.2 — Terraform init

cd infra/terraform/aws/identity/

terraform init \
-backend-config="bucket=${ACCOUNT_ID}-tfstate-<HOME_REGION>" \
-backend-config="region=<HOME_REGION>"
# Expected: "Terraform has been successfully initialized!"

Step 5.3 — Tier 1 snapshot tests

# Run inside the devcontainer (nnthanh101/terraform:2.6.0)
task test:tier1 MODULE=sso
# Expected: 10/10 pass, ~2-3 seconds

Step 5.4 — Terraform plan

terraform plan -var-file=terraform.tfvars
# Review output: confirm aws_organizations_account, sso_users, sso_groups, permission sets
# Expected: X to add, 0 to change, 0 to destroy

Step 5.5 — Apply [HITL]

# [HITL] — member-account creation is near-irreversible; review plan output first
terraform apply -var-file=terraform.tfvars
# Type: yes
# Expected: Apply complete! Resources: X added, 0 changed, 0 destroyed.

After apply, the bootstrap admin user receives a password-setup email (OTP). Set a password and MFA before continuing. The bootstrap admin user will receive a password-setup email within 1–5 minutes; check spam if delayed. Do not proceed to Section 6 until the password and MFA are configured.

Step 5.6 — Capture your start URL

# READONLY — retrieve YOUR OceanSoft start URL from the console or CLI
aws sso-admin list-instances \
--profile <YOUR_MANAGEMENT_PROFILE> \
--region <HOME_REGION> \
--query 'Instances[0].IdentityStoreId' \
--output text
# Build your start URL: https://d-<output>.awsapps.com/start

Alternatively: IAM Identity Center console → Settings → Identity source tab → AWS access portal URL.

Save this URL. You will use it in [sso-session oceansoft] in Section 6.

Step 5.7 — Teardown bootstrap credential (if Option B was used)

If you used a temporary IAM user access key (Section 3 Option B), delete it now using the teardown commands listed in that section.


5W1H — Terraform IaC Run

QuestionAnswer
WhyAll post-enablement objects (member accounts, users, groups, permission sets, assignments) must be code-managed; console creation is invisible to version control and unrecoverable from state.
What if missingIdentity Center exists but has no groups or assignments; engineers have no permission sets to assume; the B2B account is unreachable via SSO.
Business valueTerraform-managed access changes are pull-request-reviewed, audit-trailed, and recoverable from state; APRA CPS 234 requires evidence of controlled access changes.
PurposeConverts the manually-enabled Identity Center instance into a fully code-managed access control layer that scales across additional accounts without console work.
Critical thinkingenable_organizations_lookup = true in main.tf requires a live AWS Organization at plan time; the Organization is created automatically when Identity Center is enabled (Section 2) — no manual setup is needed before this step.

Section 6 — Profiles + Login + Verify

As the HITL operator, I want ready-to-paste ~/.aws/config blocks so that I can authenticate with aws sso login and run Terraform without hardcoded credentials.

Step 6.1 — Check whether SSO profiles already exist

# READONLY verify
grep -A 6 '\[profile sso\]' ~/.aws/config 2>/dev/null || echo "profile sso NOT FOUND"
grep -A 6 '\[profile b2b\]' ~/.aws/config 2>/dev/null || echo "profile b2b NOT FOUND"

If both profiles are found and sso_start_url matches your OceanSoft start URL → skip to Step 6.3.

If either profile points to a foreign instance (d-9767913734 or d-976752e8d5) → replace as shown.

Step 6.2 — Add SSO profiles to ~/.aws/config [HITL]

[sso-session oceansoft]
sso_start_url = https://d-<YOUR_OWN_ID>.awsapps.com/start
sso_region = <HOME_REGION>
sso_registration_scopes = sso:account:access

[profile sso]
sso_session = oceansoft
sso_account_id = ${MANAGEMENT_ACCOUNT_ID}
sso_role_name = AdministratorAccess
region = <HOME_REGION>
output = json

[profile b2b]
sso_session = oceansoft
sso_account_id = ${B2B_ACCOUNT_ID}
sso_role_name = PowerUserAccess
region = <HOME_REGION>
output = json

Replace:

  • <YOUR_OWN_ID> → the Identity Store ID from Step 5.6
  • <HOME_REGION> → your chosen home region
  • ${MANAGEMENT_ACCOUNT_ID} → 12-digit management account ID
  • ${B2B_ACCOUNT_ID} → 12-digit B2B workload account ID

Step 6.3 — Test SSO login [HITL]

# [HITL] — opens browser for consent
aws sso login --profile sso

Step 6.4 — Final verify chain

# READONLY verify — confirm SSO instance
aws sso-admin list-instances \
--profile sso --region <HOME_REGION>
# Expected: 1 instance, YOUR Identity Store ID (not d-9767913734 or d-976752e8d5)

# READONLY verify — list groups created by Terraform
aws identitystore list-groups \
--identity-store-id <YOUR_IDENTITY_STORE_ID> \
--region <HOME_REGION> --profile sso \
--query 'Groups[*].DisplayName'
# Expected: ["PlatformTeam", "PowerUsers", "AuditTeam", "SecurityTeam"]

Step 6.5 — Deploy chain (subsequent applies)

cd infra/terraform/aws/identity/

terraform init \
-backend-config="bucket=${ACCOUNT_ID}-tfstate-<HOME_REGION>" \
-backend-config="region=<HOME_REGION>"

terraform plan -var-file=terraform.tfvars

task test:tier1 MODULE=sso

# [HITL] — review plan before applying
terraform apply -var-file=terraform.tfvars

5W1H — AWS CLI Profile Wiring

QuestionAnswer
WhyTerraform and AWS CLI commands require named profiles; static credentials in environment variables are an IAM anti-pattern blocked by the org SCP.
What if missingterraform init -backend-config="..." fails with NoCredentialProviders; the entire deploy chain is blocked.
Business valueSSO profiles use short-lived tokens (max 8 hours); no long-lived credentials in CI or local environments; OIDC handles machine identity (TI-2c).
PurposeMaps the two accounts (management, b2b) to their Terraform provider aliases so aws.identity_center and the default provider resolve correctly without any hardcoded secrets.
Critical thinkingThe sso_role_name in the profile must match a permission set that exists in Identity Center AND has been assigned to the account in terraform.tfvars. If you run Terraform before the assignment was applied, the profile authenticates but Terraform gets access-denied on SSO admin APIs.

References

  • State migration guide: terraform-aws/accounts/management-account/README.md
  • Entra ID federation (SAML/SCIM): identity-center-enablement.mdx sibling → entra-federation.mdx
  • ADR-006 (S3 locking, no DynamoDB): terraform-aws/.adlc/adrs/
  • ADR-021 (parallel 2-account topology): docs/content/architecture/adrs/ADR-021-parallel-2-account-topology.md
  • modules/sso v1.3.0: terraform-aws/modules/sso/