From b17c1a5d43c5a1445b87e725b120c83766874d46 Mon Sep 17 00:00:00 2001 From: Alex English Date: Sun, 30 Aug 2026 18:35:09 -0700 Subject: [PATCH 1/4] Import legacy IAM users and the project-leads group Drop the chelseyb module: the user was deleted in the account, so main planned to recreate it on the next apply. Add fangyiliu, jack.pashayan and the project-leads group as bare resources in legacy files rather than through the modules, which create a login profile that cannot be imported without planning a change against a live console password. Refs #176 --- terraform/aws-groups-legacy.tf | 20 ++++++++++++++++++++ terraform/aws-users-legacy.tf | 33 +++++++++++++++++++++++++++++++++ terraform/aws-users.tf | 12 ------------ 3 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 terraform/aws-groups-legacy.tf create mode 100644 terraform/aws-users-legacy.tf diff --git a/terraform/aws-groups-legacy.tf b/terraform/aws-groups-legacy.tf new file mode 100644 index 0000000..e2c54a3 --- /dev/null +++ b/terraform/aws-groups-legacy.tf @@ -0,0 +1,20 @@ +// Legacy IAM groups. +// +// Declared as bare resources rather than through ./modules/aws-groups for the +// same reason as aws-users-legacy.tf: the module attaches a fixed policy set to +// a group it creates, and these groups predate it with policies attached by +// hand. Bringing one under the module would plan those attachments away. +// +// Note IAM groups cannot carry tags at all - AWS exposes no group tagging API - +// so a group never reports as managed in the coverage report no matter what is +// done here. Declaring it is still worth doing: it is what stops the group and +// its memberships drifting further. + +resource "aws_iam_group" "project_leads" { + name = "project-leads" +} + +import { + to = aws_iam_group.project_leads + id = "project-leads" +} diff --git a/terraform/aws-users-legacy.tf b/terraform/aws-users-legacy.tf new file mode 100644 index 0000000..9c44499 --- /dev/null +++ b/terraform/aws-users-legacy.tf @@ -0,0 +1,33 @@ +// Legacy IAM users. +// +// These accounts predate this Terraform and were created by hand, so they are +// declared as bare resources rather than through ./modules/aws-users. Two +// reasons, both deliberate: +// +// 1. The module also creates an aws_iam_user_login_profile. A login profile's +// password cannot be read back from the IAM API, so importing one plans a +// change against a live console password. These users keep their existing +// login profiles, unmanaged. +// 2. The module assumes a user the module itself created, with a uniform tag +// set and group membership. These accounts have neither. +// +// New users still go in aws-users.tf through the module. Nothing should be +// added here that is not already live in the account. + +resource "aws_iam_user" "fangyiliu" { + name = "fangyiliu" +} + +resource "aws_iam_user" "jack_pashayan" { + name = "jack.pashayan" +} + +import { + to = aws_iam_user.fangyiliu + id = "fangyiliu" +} + +import { + to = aws_iam_user.jack_pashayan + id = "jack.pashayan" +} diff --git a/terraform/aws-users.tf b/terraform/aws-users.tf index 3aa3514..de3f005 100644 --- a/terraform/aws-users.tf +++ b/terraform/aws-users.tf @@ -21,18 +21,6 @@ module "iam_user_testiamuser" { user_groups = ["read-only-group"] } -module "iam_user_chelseyb" { - source = "./modules/aws-users" - - user_name = "chelseyb" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - - module "iam_user_alexe" { source = "./modules/aws-users" From 9da5dac3023b9cd88324ed3ccbf42e68236277f0 Mon Sep 17 00:00:00 2001 From: Alex English Date: Sun, 30 Aug 2026 18:36:00 -0700 Subject: [PATCH 2/4] remove offboarded users --- terraform/aws-users.tf | 109 ----------------------------------------- 1 file changed, 109 deletions(-) diff --git a/terraform/aws-users.tf b/terraform/aws-users.tf index de3f005..c15f79b 100644 --- a/terraform/aws-users.tf +++ b/terraform/aws-users.tf @@ -43,115 +43,6 @@ module "iam_user_rsakuma" { user_groups = ["ops-leads"] } -module "iam_user_npang4" { - source = "./modules/aws-users" - - user_name = "npang4" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_drakeredwind01" { - source = "./modules/aws-users" - - user_name = "drakeredwind01" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_srinipandiyan" { - source = "./modules/aws-users" - - user_name = "srinipandiyan" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_bltomlin" { - source = "./modules/aws-users" - - user_name = "bltomlin" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_gmgonzal" { - source = "./modules/aws-users" - - user_name = "gmgonzal" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_Bwoltz" { - source = "./modules/aws-users" - - user_name = "Bwoltz" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_raibarra" { - source = "./modules/aws-users" - - user_name = "raibarra04" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_ezesalvatore4" { - source = "./modules/aws-users" - - user_name = "ezesalvatore" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_myronchen-git" { - source = "./modules/aws-users" - - user_name = "myronchen-git" - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} - -module "iam_user_here" { - source = "./modules/aws-users" - - user_name = "here" # Replace with GitHub handle - user_tags = { - "Project" = "devops-security" - "Access Level" = "1" - } - user_groups = ["read-only-group"] -} module "iam_user_benettonkkb" { source = "./modules/aws-users" From af2a29be23ff0145d0e8078ee7438e59a2b7171f Mon Sep 17 00:00:00 2001 From: Alex English Date: Sun, 30 Aug 2026 18:43:27 -0700 Subject: [PATCH 3/4] Import the ManageAccessKeys policy as legacy Uses the document already committed under aws-custom-policies/existing-policies/, verified identical to the live policy. Declares description explicitly: it is ForceNew, so omitting it plans a replacement that would detach the policy from ops-leads. Refs #176 --- terraform/aws-custom-policies-legacy.tf | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 terraform/aws-custom-policies-legacy.tf diff --git a/terraform/aws-custom-policies-legacy.tf b/terraform/aws-custom-policies-legacy.tf new file mode 100644 index 0000000..8188f37 --- /dev/null +++ b/terraform/aws-custom-policies-legacy.tf @@ -0,0 +1,26 @@ +// Legacy IAM customer-managed policies. +// +// Declared as bare resources rather than through ./modules/aws-policies for the +// same reason as aws-users-legacy.tf and aws-groups-legacy.tf: the module owns +// the name, description and document of a policy it created, and these predate +// it. The document below is the one already committed under +// aws-custom-policies/existing-policies/, which was verified byte-for-byte +// against the live policy before importing. +// +// ManageAccessKeys is attached to the ops-leads group by +// aws_iam_group_policy_attachment.manageAccessKeys in aws-groups.tf, which +// still refers to it by literal ARN. That attachment is unchanged here. + +resource "aws_iam_policy" "manage_access_keys" { + name = "ManageAccessKeys" + // description is ForceNew on aws_iam_policy. Omitting it plans a replacement + // of the live policy, which detaches it from ops-leads. This is the string + // already on the policy in the account. + description = "Policy for creating, listing, and updating Access Keys" + policy = file("${path.module}/aws-custom-policies/existing-policies/manage-access-keys-policy.json") +} + +import { + to = aws_iam_policy.manage_access_keys + id = "arn:aws:iam::035866691871:policy/ManageAccessKeys" +} From 9999d1894bb890c8cb7f2e6312cf8534efdde2ee Mon Sep 17 00:00:00 2001 From: Alex English Date: Sun, 30 Aug 2026 18:45:41 -0700 Subject: [PATCH 4/4] Set force_destroy on the aws-users module Offboarding is done by deleting a module block, so DeleteUser has to be able to finish without a hand cleanup of MFA devices, access keys and directly attached policies. Refs #176 --- terraform/modules/aws-users/main.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terraform/modules/aws-users/main.tf b/terraform/modules/aws-users/main.tf index ba35f14..9b05c2f 100644 --- a/terraform/modules/aws-users/main.tf +++ b/terraform/modules/aws-users/main.tf @@ -4,6 +4,12 @@ resource "aws_iam_user" "user" { name = var.user_name path = var.user_path + // Offboarding is done by deleting the module block, so a destroy has to be + // able to finish on its own. Without this, DeleteUser returns DeleteConflict + // for any user who still has an MFA device, an access key or a directly + // attached policy, and the apply fails partway through the batch. + force_destroy = true + tags = var.user_tags }