Bring the remaining IAM users under Terraform - #180
Merged
Conversation
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
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
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 plan in terraform Plan: 4 to import, 0 to add, 9 to change, 30 to destroy.Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
!~ update in-place
- destroy
Terraform will perform the following actions:
# aws_iam_group.project_leads will be imported
resource "aws_iam_group" "project_leads" {
arn = "arn:aws:iam::035866691871:group/project-leads"
id = "project-leads"
name = "project-leads"
path = "/"
unique_id = "*********************"
}
# aws_iam_policy.manage_access_keys will be updated in-place
# (imported from "arn:aws:iam::035866691871:policy/ManageAccessKeys")
!~ resource "aws_iam_policy" "manage_access_keys" {
arn = "arn:aws:iam::035866691871:policy/ManageAccessKeys"
attachment_count = 1
description = "Policy for creating, listing, and updating Access Keys"
id = "arn:aws:iam::035866691871:policy/ManageAccessKeys"
name = "ManageAccessKeys"
name_prefix = null
path = "/"
policy = jsonencode(
{
Statement = [
{
Action = "iam:ListUsers"
Effect = "Allow"
Resource = "arn:aws:iam::*:*"
Sid = "ListUsersForConsole"
},
{
Action = [
"iam:UpdateAccessKey",
"iam:CreateAccessKey",
"iam:ListAccessKeys",
]
Effect = "Allow"
Resource = "arn:aws:iam::*:user/*"
Sid = "ViewAndUpdateAccessKeys"
},
]
Version = "2012-10-17"
}
)
policy_id = "*********************"
tags = {}
!~ tags_all = {
+ "managed-by" = "terraform-devops-security"
}
}
# aws_iam_user.fangyiliu will be updated in-place
# (imported from "fangyiliu")
!~ resource "aws_iam_user" "fangyiliu" {
arn = "arn:aws:iam::035866691871:user/fangyiliu"
+ force_destroy = false
id = "fangyiliu"
name = "fangyiliu"
path = "/"
permissions_boundary = null
tags = {}
!~ tags_all = {
+ "managed-by" = "terraform-devops-security"
}
unique_id = "*********************"
}
# aws_iam_user.jack_pashayan will be updated in-place
# (imported from "jack.pashayan")
!~ resource "aws_iam_user" "jack_pashayan" {
arn = "arn:aws:iam::035866691871:user/jack.pashayan"
+ force_destroy = false
id = "jack.pashayan"
name = "jack.pashayan"
path = "/"
permissions_boundary = null
tags = {}
!~ tags_all = {
+ "managed-by" = "terraform-devops-security"
}
unique_id = "*********************"
}
# module.iam_user_Bwoltz.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/Bwoltz" -> null
- force_destroy = false -> null
- id = "Bwoltz" -> null
- name = "Bwoltz" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPTNBOGTM25" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_Bwoltz.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20250313015346634000000001" -> null
- user = "Bwoltz" -> null
}
# module.iam_user_Bwoltz.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "Bwoltz" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = false -> null
- user = "Bwoltz" -> null
}
# module.iam_user_Ganeshswaminathan1912.aws_iam_user.user will be updated in-place
!~ resource "aws_iam_user" "user" {
!~ force_destroy = false -> true
id = "Ganeshswaminathan1912"
name = "Ganeshswaminathan1912"
tags = {
"Access Level" = "1"
"Project" = "vrms"
}
# (5 unchanged attributes hidden)
}
# module.iam_user_alexe.aws_iam_user.user will be updated in-place
!~ resource "aws_iam_user" "user" {
!~ force_destroy = false -> true
id = "alexe"
name = "alexe"
tags = {
"Access Level" = "1"
"Project" = "devops-security"
}
# (5 unchanged attributes hidden)
}
# module.iam_user_benettonkkb.aws_iam_user.user will be updated in-place
!~ resource "aws_iam_user" "user" {
!~ force_destroy = false -> true
id = "benettonkkb"
name = "benettonkkb"
tags = {
"Access Level" = "1"
"Project" = "devops-security"
}
# (5 unchanged attributes hidden)
}
# module.iam_user_bltomlin.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/bltomlin" -> null
- force_destroy = false -> null
- id = "bltomlin" -> null
- name = "bltomlin" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEP65FRWD6EO" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_bltomlin.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [] -> null
- id = "terraform-20250205014939130500000001" -> null
- user = "bltomlin" -> null
}
# module.iam_user_bltomlin.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "bltomlin" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = false -> null
- user = "bltomlin" -> null
}
# module.iam_user_drakeredwind01.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/drakeredwind01" -> null
- force_destroy = false -> null
- id = "drakeredwind01" -> null
- name = "drakeredwind01" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPVDEIVODGP" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_drakeredwind01.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20241017012315444900000001" -> null
- user = "drakeredwind01" -> null
}
# module.iam_user_drakeredwind01.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "drakeredwind01" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "drakeredwind01" -> null
}
# module.iam_user_ezesalvatore4.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/ezesalvatore" -> null
- force_destroy = false -> null
- id = "ezesalvatore" -> null
- name = "ezesalvatore" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPXHV2OY2NX" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_ezesalvatore4.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20250821011530624400000001" -> null
- user = "ezesalvatore" -> null
}
# module.iam_user_ezesalvatore4.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "ezesalvatore" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "ezesalvatore" -> null
}
# module.iam_user_gmgonzal.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/gmgonzal" -> null
- force_destroy = false -> null
- id = "gmgonzal" -> null
- name = "gmgonzal" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPVHWKTBXAK" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_gmgonzal.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20250220022612487300000001" -> null
- user = "gmgonzal" -> null
}
# module.iam_user_gmgonzal.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "gmgonzal" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "gmgonzal" -> null
}
# module.iam_user_here.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/here" -> null
- force_destroy = false -> null
- id = "here" -> null
- name = "here" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPZ4PXSYNFN" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_here.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20250515012156916800000001" -> null
- user = "here" -> null
}
# module.iam_user_here.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "here" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "here" -> null
}
# module.iam_user_myronchen-git.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/myronchen-git" -> null
- force_destroy = false -> null
- id = "myronchen-git" -> null
- name = "myronchen-git" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPWYWGVDRW3" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_myronchen-git.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [] -> null
- id = "terraform-20251009012251192400000001" -> null
- user = "myronchen-git" -> null
}
# module.iam_user_myronchen-git.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "myronchen-git" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "myronchen-git" -> null
}
# module.iam_user_npang4.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/npang4" -> null
- force_destroy = false -> null
- id = "npang4" -> null
- name = "npang4" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEP2DV4JB6PH" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_npang4.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20240822014538538500000001" -> null
- user = "npang4" -> null
}
# module.iam_user_npang4.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "npang4" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "npang4" -> null
}
# module.iam_user_raibarra.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/raibarra04" -> null
- force_destroy = false -> null
- id = "raibarra04" -> null
- name = "raibarra04" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPVRG7XJJJB" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_raibarra.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20250501013210101600000001" -> null
- user = "raibarra04" -> null
}
# module.iam_user_raibarra.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "raibarra04" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "raibarra04" -> null
}
# module.iam_user_rsakuma.aws_iam_user.user will be updated in-place
!~ resource "aws_iam_user" "user" {
!~ force_destroy = false -> true
id = "rsakuma"
name = "rsakuma"
tags = {
"Access Level" = "1"
"Project" = "devops-security"
}
# (5 unchanged attributes hidden)
}
# module.iam_user_srinipandiyan.aws_iam_user.user will be destroyed
# (because aws_iam_user.user is not in configuration)
- resource "aws_iam_user" "user" {
- arn = "arn:aws:iam::035866691871:user/srinipandiyan" -> null
- force_destroy = false -> null
- id = "srinipandiyan" -> null
- name = "srinipandiyan" -> null
- path = "/" -> null
- tags = {
- "Access Level" = "1"
- "Project" = "devops-security"
} -> null
- tags_all = {
- "Access Level" = "1"
- "Project" = "devops-security"
- "managed-by" = "terraform-devops-security"
} -> null
- unique_id = "AIDAQQWOSJEPYI4MH34R6" -> null
# (1 unchanged attribute hidden)
}
# module.iam_user_srinipandiyan.aws_iam_user_group_membership.user_group_membership will be destroyed
# (because aws_iam_user_group_membership.user_group_membership is not in configuration)
- resource "aws_iam_user_group_membership" "user_group_membership" {
- groups = [
- "read-only-group",
] -> null
- id = "terraform-20250130022147796000000001" -> null
- user = "srinipandiyan" -> null
}
# module.iam_user_srinipandiyan.aws_iam_user_login_profile.user_login will be destroyed
# (because aws_iam_user_login_profile.user_login is not in configuration)
- resource "aws_iam_user_login_profile" "user_login" {
- id = "srinipandiyan" -> null
- password = (sensitive value) -> null
- password_length = 20 -> null
- password_reset_required = true -> null
- user = "srinipandiyan" -> null
}
# module.iam_user_testiamuser.aws_iam_user.user will be updated in-place
!~ resource "aws_iam_user" "user" {
!~ force_destroy = false -> true
id = "testiamuser"
name = "testiamuser"
tags = {
"Access Level" = "1"
"Project" = "devops-security"
}
# (5 unchanged attributes hidden)
}
# module.iam_user_tylerthome.aws_iam_user.user will be updated in-place
!~ resource "aws_iam_user" "user" {
!~ force_destroy = false -> true
id = "tyler.thome"
name = "tyler.thome"
tags = {
"Access Level" = "1"
"Project" = "home-unite-us"
}
# (5 unchanged attributes hidden)
}
Plan: 4 to import, 0 to add, 9 to change, 30 to destroy.❌ Error applying plan in Apply Terraform changes on merge #42 |
12 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #176.
Brings the remaining IAM users under Terraform, offboards ten users, and adopts the
ManageAccessKeyspolicy and theproject-leadsgroup.The account changed since #176 was written — three of the eight users it names (
bonnie_ops,github-action-incubator,homeuniteus-app) have since been deleted, which also makes both of its access-key rotation items moot: deleting a user deletes its keys, and those were the two whose secrets sat in abandoned state files.What this does
Removes the
chelseybmodule. The user was deleted in the account, somainplanned to recreate it —Plan: 3 to addon an untouched checkout. Any.tfmerge would have resurrected the account with a fresh login profile.Offboards ten users (
9da5dac), and setsforce_destroy = trueon theaws-usersmodule so a future offboarding does not stall on an MFA device or a directly attached policy. Note this does not apply retroactively to the ten in this PR: their module blocks are gone, so Terraform plans their destroy from state, whereforce_destroyis stillfalse. The three that would have hitDeleteConflict(bltomlin,myronchen-git,here) were cleared by hand before merge.Adds legacy files for resources that predate this Terraform. These use bare resources rather than the modules, because the modules create an
aws_iam_user_login_profilewhose password cannot be read back from the IAM API — importing one plans a change against a live console password.aws-users-legacy.tf—fangyiliu,jack.pashayanaws-groups-legacy.tf—project-leadsaws-custom-policies-legacy.tf—ManageAccessKeysLogin profiles, access keys and direct policy attachments are deliberately left undeclared on the legacy users. That is what keeps the destroy count at zero.
Plan
Nothing is replaced. The 30 destroys are the ten offboarded users (user, login profile, group membership each). Every one of the 9 in-place changes is either a
managed-bytag landing fromdefault_tagsorforce_destroygoingfalse -> true; there is no other attribute change in the plan.Expect the tag additions — the provider sets
default_tags, so an imported resource gainsmanaged-by = terraform-devops-security. That diff is the mechanism by which these start reporting as managed in the coverage report.Two traps worth recording
descriptionis ForceNew onaws_iam_policy. Omitting it planned a replacement of the liveManageAccessKeyspolicy, which would have detached it fromops-leadsand minted a new ARN thataws-groups.tfstill references literally. It is now declared explicitly, with the string already on the policy. The document itself comes fromaws-custom-policies/existing-policies/manage-access-keys-policy.json, which was already committed and verified identical to live before importing.IAM groups cannot be tagged at all, so
project-leadsimports with zero changes and will keep reporting as unmanaged in the coverage report no matter what. Declaring it is still worth doing — it is what stops the group drifting further.Exempt, not imported
Some IAM is deliberately outside Terraform and now carries
managed-by = exempt, applied by hand since no Terraform run will ever touch it:devops-iam-github-actionand the policiesTerraformIAM,TerraformDynamoDBAccess,DevopsSecurityTerraformBucketAccess, plus thehfla-ops-terraform-statebucket andhfla_ops_terraform_tabletable — this repo's own CI identity and state backend. Terraform managing the credentials and the bucket it unlocks is a lockout risk.bonnie_hflaandchelseybeck— human accounts kept outside Terraform.hackforla/devops#208teaches the coverage report about that tag so these stop counting as unmanaged.