Blog

Review your IAM permissions across your Google Cloud Platform (GCP) with a convenient script

07 Nov, 2024
Xebia Background Header Wave

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:

  1. Open Google Cloud Shell and create a new .sh file (e.g., nano export_permissions.sh).
  2. Edit the file and paste the code, replacing the placeholder with your actual Organization ID.
  3. We’ve also included a function to exclude specific folder(s), replace the placeholder with the actual folder ID you want to exclude.
  4. Make the script executable by running: chmod +x export_permissions.sh in the terminal.
  5. Execute the script with ./export_permissions.sh and watch the magic unfold.
  6. 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.

Lars Prosec
I work as a Cloud Platform Engineer with Xebia, contributing to the GCP Cloud Control Team. Our focus is providing tailored-made assessments, reviews, scans, and assistance for your Google Cloud environment, offering various services including Managed Services. In addition to working with the cloud, I prioritize my health by regularly attending my local gym. I also enjoy spending my free time on music, particularly DJing and mixing a variety of tracks.
Daan Heikens
I work as a Google Cloud Consultant at Xebia. Working as a consultant, I help clients design and implement secure cloud architectures. I am passionate about technology, craft beers and Formula 1. A curious, enthusiastic and a constant tinkerer. I’m eager to learn new things and have a passion for cloud native security.​ You’ll find me listening to podcasts, reading books and blogs in my spare time.
Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts