Reviewing all your granted IAM permissions in your Google Cloud organization can become a quite cumbersome task and can be very time-consuming. As your organization grows, more teams will be involved, and more projects will be created over time. Some organizations have hundreds or even thousands of bindings. Permissions can pile up, making it tough to keep track of who has access to what. To help a hand, we at Xebia created a convenience script to export all the granted IAM permissions to all principals for organizations, folders, and projects in human-readable format.
Gain visibility and insights into your current IAM permissions with this script.
To simplify permission reviews, we created a bash script to generate a quick and thorough overview of IAM bindings/roles across the entire Google Cloud Platform.
Keep in mind that in order to run this script in Google Cloud Shell, your account must have the roles/iam.securityReviewer
permission at the organizational level.
Please note: Resource-specific bindings are not included in this script, but can, of course, be included in this script. (e.g. gcloud storage buckets get-iam-policy gs://BUCKET –project=PROJECT_ID).
Steps:
- Open Google Cloud Shell and create a new
.sh
file (e.g.,nano export_permissions.sh
). - Edit the file and paste the code, replacing the placeholder with your actual Organization ID.
- We’ve also included a function to exclude specific folder(s), replace the placeholder with the actual folder ID you want to exclude.
- Make the script executable by running:
chmod +x export_permissions.sh
in the terminal. - Execute the script with
./export_permissions.sh
and watch the magic unfold. - The resulting
.csv
file will be saved directly to your Cloud Shell.
#!/bin/bash
# Set the organization ID for the specific customer
ORGANIZATION_ID="123456789" # Replace with your actual organization ID
# Define folders to exclude (comma-separated list of folder IDs to ignore)
FOLDER_EXCLUSIONS="12345678910" # Replace with actual folder IDs
# Output CSV file
OUTPUT_FILE="gcp_permissions.csv"
# Initialize CSV with headers
echo "Resource Type,Resource ID,Resource Name,Member,Role" > "$OUTPUT_FILE"
# Function to check if a folder should be excluded
is_excluded_folder() {
local folder_id="$1"
[[ ",$FOLDER_EXCLUSIONS," == *",$folder_id,"* ]]
}
# Function to handle IAM policy processing and append results to CSV
# Function to handle IAM policy processing and append results to CSV
process_policy() {
local resource_type="$1"
local resource_id="$2"
local resource_name="$3"
local policy_json="$4"
echo "Processing IAM policy for $resource_type: $resource_id ($resource_name)"
# Check if .bindings exists and is not null
if jq -e '.bindings' <<< "$policy_json" >/dev/null; then
jq -c '.bindings[]?' <<< "$policy_json" | while read -r binding; do
# Check if role exists in binding, default to "UNKNOWN_ROLE" if null
role=$(jq -r '.role // "UNKNOWN_ROLE"' <<< "$binding")
# Check if members array exists and is not null
if jq -e '.members' <<< "$binding" >/dev/null; then
members=$(jq -r '.members[]?' <<< "$binding")
# Process each member, defaulting to "UNKNOWN_MEMBER" if null
for member in $members; do
if [ -z "$member" ] || [ "$member" == "null" ]; then
member="UNKNOWN_MEMBER"
echo "Warning: Null or empty member found for role $role in $resource_type $resource_id"
fi
echo "$resource_type,$resource_id,$resource_name,$member,$role" >> "$OUTPUT_FILE"
done
else
echo "Warning: No members found for role $role in $resource_type $resource_id"
fi
done
else
echo "Warning: No bindings found for $resource_type $resource_id ($resource_name)"
fi
}
# Recursive function to process folders and their projects
process_folder() {
local folder_id="$1"
if is_excluded_folder "$folder_id"; then
echo "Excluding Folder: $folder_id" >&2
return
fi
# Fetch the folder name for clarity in output
folder_name=$(gcloud resource-manager folders describe "$folder_id" --format="value(displayName)")
echo "Processing Folder: $folder_id ($folder_name)"
# Fetch and process folder IAM policy
folder_policy=$(gcloud resource-manager folders get-iam-policy "$folder_id" --format=json)
process_policy "Folder" "$folder_id" "$folder_name" "$folder_policy"
# List and process all projects within the folder
for project in $(gcloud projects list --filter="parent.id=$folder_id" --format="value(projectId)"); do
project_name=$(gcloud projects describe "$project" --format="value(name)")
echo "Processing Project: $project ($project_name)"
project_policy=$(gcloud projects get-iam-policy "$project" --format=json)
process_policy "Project" "$project" "$project_name" "$project_policy"
done
# Recursively process any subfolders
for subfolder in $(gcloud resource-manager folders list --folder="$folder_id" --format="value(name)"); do
process_folder "$subfolder"
done
}
# Process organization-level IAM policy
echo "Processing Organization IAM policy for Org ID: $ORGANIZATION_ID"
org_name=$(gcloud organizations describe "$ORGANIZATION_ID" --format="value(displayName)")
org_policy=$(gcloud organizations get-iam-policy "$ORGANIZATION_ID" --format=json)
process_policy "Organization" "$ORGANIZATION_ID" "$org_name" "$org_policy"
# Process all top-level folders in the organization
echo "Listing top-level folders for Organization ID: $ORGANIZATION_ID"
for folder in $(gcloud resource-manager folders list --organization="$ORGANIZATION_ID" --format="value(name)"); do
process_folder "$folder"
done
echo "Permissions export completed. Results saved to $OUTPUT_FILE"
Key Takeaway:
We concluded that listing all the granted IAM permissions across your google cloud organization wasn’t as straightforward as we thought. In the cloud console, the IAM service only lists a selective resource(s). Alternatively, you can use the Cloud Asset Inventory to do this, and in some cases, this will better suit your needs. However, take into account that you’ll receive another output where the policy is in JSON format. This will require post-processing to human-readable format(s). Take into account that if you use Infrastructure as Code for assigning IAM binding, this will already gain more visibility. However, even with Infrastructure as Code (e.g. Terraform), in some cases, IAM bindings are also located in different files and locations. This script significantly reduced our time to analyse IAM permissions and created a good initial overview of the permissions for a IAM review in a single file.
Apart from the blog, you can find the full example of our exercise and source code here: https://github.com/xebia/gcp-iam-bindings-export
Disclaimer:
Permissions are sensitive data. Always ensure you align with relevant stakeholders to determine who should have access to this information and where it should be saved and shared with whom.