Maintaining a secure and manageable access control policy in Google Cloud requires minimizing overly broad or unnecessary direct IAM role assignments. As organizations grow, it’s common for user-specific IAM bindings to accumulate, often leading to misconfigurations or excessive access permissions.
To help address this, I’ve developed a script that identifies unnecessary direct IAM role assignments across all projects within a GCP organization. This can be a powerful tool for auditing and cleaning up direct bindings in favor of group-based or managed identities.
🎯 Purpose Link to heading
The script analyzes IAM policies across an entire GCP organization and searches for explicit user-based role assignments, then cross-references them against a list of users you’re interested in auditing.
# by pduchnovsky, https://duchnovsky.com/2025/07/gcp-iam-audit-direct-roles
# Find unnecessary direct roles assigned
org=1234567890
USERS=(
"xxxxx" "xxxxx"
)
USERS_JSON=$(printf '%s\n' "${USERS[@]}" | jq -R . | jq -s .)
QUERY=$(printf "policy:\"user:%s\" OR " "${USERS[@]}") && QUERY="${QUERY% OR }"
gcloud asset search-all-iam-policies \
--scope="organizations/$org" \
--query="${QUERY}" \
--format=json | jq --argjson users "$USERS_JSON" -r '
group_by(.project) |
map({
projectId: .[0].project,
projectName: (.[0].resource | capture("projects/(?<name>[^/]+)").name // .[0].project),
userRoles: (
[.[]
| .resource as $res
| .policy.bindings[]
| {role, members: [.members[]
| select(startswith("user:"))
| sub("^user:"; "")
], resource: $res}
]
| map({role, resource, member: .members[]})
| map(select((.member | split("@")[0]) as $id | $users | index($id)))
| group_by(.member)
| map({
member: .[0].member,
rolesByResource: (
group_by(.resource)
| map({
resource: .[0].resource,
roles: map(.role) | unique
})
)
})
)
}) |
.[] |
"--- Project: \(.projectName) (\(.projectId)) ---",
"Google Cloud Console IAM Link: https://console.cloud.google.com/iam-admin/iam?project=\(.projectName)",
(.userRoles[]? |
" Member: \(.member)",
(.rolesByResource[] |
" Resource: \(.resource)",
(.roles[] | " Role: " + .)
)
),
""
'