Skip to main content

Cloud

Permissions for onboarding GCP

This section describes the set of permissions that you grant to Illumio Cloud.

By participating in the BETA program for GCP features you agree that your company’s use of the BETA version of GCP features will be governed by Illumio’s Beta Terms and Conditions.

Permission Type

Permission Name

Notes

Read

roles/iam.securityReviewer (link lists all resources)

This role gives Illumio Cloud the permissions to read data or resources from your organization or project. This role allows the viewing of all resources, but does not allow modification. This role can be truncated to the following minimum resource permissions. (Limiting permissions to cloud services currently supported by Illumio Cloud):

Although Illumio Cloud does not currently support every resource listed in the full securityReviewer role, if you do not truncate the role, Illumio Cloud assigns all the resource permissions so that you don't have to update permissions each time Illumio supports additional resources.

Read

roles/compute.viewer (link lists all resources)

This role gives Illumio Cloud the permissions to read compute resources from your organization or project. This role allows the viewing of all resources, but does not allow modification. The full role is required, so truncation is not supported.

Read

roles/browser (link lists all resources)

Required for organization and folder onboarding. This role gives Illumio Cloud the permissions to read organization projects and folders. The full role is required, so truncation is not supported.

Read

roles/cloudasset.viewer (link lists all resources)

This role gives Illumio Cloud the permissions to read cloud asset resources from your organization or project. This role allows the viewing of all resources, but does not allow modification. The full role is required, so truncation is not supported.

Read Flows

IllumioPubSubFlowLogAccess

This custom role gives Illumio Cloud the permissions to read flows by creating and attaching subscriptions. The permission is also sought for offboarding flow access workflows. The full role is required, so truncation is not supported. This role assigns the following resource permissions:

  • pubsub.topics.attachSubscription

  • pubsub.subscriptions.consume

  • pubsub.subscriptions.create

  • pubsub.subscriptions.delete

Read Flows

IllumioPubSubAccessRole

This custom role gives Illumio Cloud the permissions to read flows by creating and attaching subscriptions. The permission is also sought for offboarding flow access workflows. This role assigns the following resource permissions:

  • pubsub.topics.attachSubscription

Write

illumio_api_enable_role

This custom role gives Illumio Cloud the permissions to enable APIs. The full role is required, so truncation is not supported. This role assigns the following resource permissions:

  • serviceusage.services.enable

  • serviceusage.services.list

  • serviceusage.services.get

Write

illumio_write_role

This custom role gives Illumio Cloud the permissions to modify data or resources in your organization or project. This role allows the modification of resources. Enable read-write mode from in the onboarding wizard to create the role. The full role is required, so truncation is not supported. This role assigns the following resource permissions:

  • compute.firewalls.create

  • compute.networks.updatePolicy

  • compute.firewalls.update

  • compute.firewalls.get

  • compute.firewalls.delete

Permissions script example

When you grant read and write permissions to Illumio Cloud, the gcp_onboarding_prod.sh script below creates roles and sets permissions. The script prompts you for permission to enable APIs. Enabling APIs is optional, but Illumio Cloud functionality is affected if you don't enable APIs.

Note

If billing is not enabled for your projects, Illumio Cloud cannot enable APIs.

#!/bin/bash

#Constants
RESOURCE_TYPE_PROJECT="project"
RESOURCE_TYPE_ORGANIZATION="organization"
RESOURCE_TYPE_FOLDER="folder"
DEFAULT_ROLE_NAME="illumio_role_$(date +%s)"
DEFAULT_SA_NAME="illumio-sa-$(date +%s)"
DEFAULT_SA_DISPLAY_NAME="Illumio Service Account"
DEFAULT_POST_URL="https://cloud.illum.io"
PREDEFINED_ROLES="roles/iam.securityReviewer,roles/compute.viewer,roles/cloudasset.viewer"
DEFAULT_WRITE_ROLE_NAME="illumio_write_role_$(date +%s)"
WRITE_PERMISSIONS="compute.firewalls.create,compute.firewalls.delete,compute.firewalls.get,compute.firewalls.update,compute.networks.updatePolicy"
DEFAULT_API_ENABLE_ROLE_NAME="illumio_api_enable_role_$(date +%s)"
API_ENABLE_PERMISSION="serviceusage.services.enable,serviceusage.services.list,serviceusage.services.get"

ILLUMIO_SA_EMAIL="[email protected]"

GREEN='\033[32m'
RESET='\033[0m'
RED='\033[31m'

# Resource tracking
created_resources__service_accounts=""
created_resources__roles=""
RESOURCE_TYPE="$RESOURCE_TYPE_PROJECT" # using default resource type as object, based on if organization id is passed in arguments, it changes to "$RESOURCE_TYPE_ORGANIZATION"

# Function to add a resource to the tracking list - these lists will be used to clean up resources in case of an error
add_resource() {
    local resource_type=$1
    local resource_value=$2
    local var_name="created_resources__${resource_type}"
    if [[ -z "${!var_name}" ]]; then
        eval "$var_name=\"$resource_value\""
    else
        eval "$var_name=\"${!var_name},$resource_value\""
    fi
}

# Cleanup function for errors (triggered by trap)
cleanup() {
    if [ "$1" == "0" ]; then
        return
    fi
    echo -e "${RED}Cleaning up resources...${RESET}"

    # Delete service accounts
    IFS=',' read -ra SAS <<< "${created_resources__service_accounts}"
    for sa in "${SAS[@]}"; do
        echo -e "${RED}Deleting service account: $sa${RESET}"
        if ! gcloud iam service-accounts delete "$sa" --quiet --project="$PROJECT_ID"; then
            echo -e "${RED}Failed to delete service account: $sa${RESET}"
        fi
    done

    # Delete roles
    IFS=',' read -ra ROLES <<< "${created_resources__roles}"
    for role in "${ROLES[@]}"; do
        echo -e "${RED}Deleting IAM role: $role${RESET}"
        if [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
            if ! gcloud iam roles delete "$role" --organization="$ORGANIZATION_ID" --quiet; then
                echo -e "${RED}Failed to delete IAM role: $role${RESET}"
            fi
        else
            if ! gcloud iam roles delete "$role" --project="$PROJECT_ID" --quiet; then
                echo -e "${RED}Failed to delete IAM role: $role${RESET}"
            fi
        fi
    done
    echo -e "${GREEN}DONE${RESET}"
}

# Error handling setup
set -e
trap 'cleanup $?' EXIT

print_usage() {
    echo "Usage: $0 --project-id PROJECT_ID --auth-key AUTH_KEY --auth-secret AUTH_SECRET --tenant-id CS_TENANT_ID  [--organization-id ORGANIZATION_ID] [--read-write] [--role-name ROLE_NAME] [--sa-name SA_NAME] [--sa-display-name SA_DISPLAY_NAME] [--illumio-sa-email ILLUMIO_SA_EMAIL] [--post-url POST_URL]"
    echo "  --project-id PROJECT_ID             Specify the GCP project ID (mandatory)"
    echo "  --post-url POST_URL                 Specify the POST URL (optional)"
    echo "  --role-name ROLE_NAME               Specify the role name (optional)"
    echo "  --sa-name SA_NAME                   Specify the service account name (optional)"
    echo "  --sa-display-name SA_DISPLAY_NAME   Specify the service account display name (optional)"
    echo "  --illumio-sa-email ILLUMIO_SA_EMAIL Specify the Illumio service account email (optional)"
    echo "  --auth-key AUTH_KEY                 Specify the authentication key for basic auth to Illumio endpoint (mandatory)"
    echo "  --auth-secret AUTH_SECRET           Specify the authentication secret for basic auth Illumio endpoint (mandatory)"
    echo "  --tenant-id CS_TENANT_ID            Specify the tenant ID for HTTP requests (mandatory)"
    echo "  --organization-id ORGANIZATION_ID   Specify the GCP organization ID (optional)"
    echo "  --folder-id FOLDER_ID               Specify the GCP folder ID (optional)"
    echo "  --read-write                        Enable read-write mode (optional)"
    exit 1
}


# Parse arguments
READ_WRITE_MODE=false
while [[ $# -gt 0 ]]; do
    case $1 in
        --project-id) PROJECT_ID="$2"; shift 2 ;;
        --post-url) POST_URL="$2"; shift 2 ;;
        --role-name) ROLE_NAME="$2"; shift 2 ;;
        --sa-name) SA_NAME="$2"; shift 2 ;;
        --sa-display-name) SA_DISPLAY_NAME="$2"; shift 2 ;;
        --illumio-sa-email) ILLUMIO_SA_EMAIL="$2"; shift 2 ;;
        --organization-id) ORGANIZATION_ID="$2"; shift 2 ;;
        --folder-id) FOLDER_ID="$2"; shift 2 ;;
        --read-write) READ_WRITE_MODE=true; shift ;;
        --auth-key) AUTH_KEY="$2"; shift 2 ;;
        --auth-secret) AUTH_SECRET="$2"; shift 2 ;;
        --tenant-id) CS_TENANT_ID="$2"; shift 2 ;;
        *) echo "Unknown option: $1"; print_usage ;;
    esac
done


# Validate mandatory parameters
if [ -z "$PROJECT_ID" ] || [ -z "$AUTH_KEY" ] || [ -z "$AUTH_SECRET" ] || [ -z "$CS_TENANT_ID" ]; then
#    echo "$PROJECT_ID $AUTH_KEY $AUTH_SECRET $CS_TENANT_ID"
    echo "Error: Project id, auth key, secret and tenant id are mandatory."
    print_usage
fi

# Set default values if not provided
ROLE_NAME="${ROLE_NAME:-$DEFAULT_ROLE_NAME}"
SA_NAME="${SA_NAME:-$DEFAULT_SA_NAME}"
SA_DISPLAY_NAME="${SA_DISPLAY_NAME:-$DEFAULT_SA_DISPLAY_NAME}"
POST_URL="${POST_URL:-$DEFAULT_POST_URL}"

# Validate PROJECT_ID
if ! [[ "$PROJECT_ID" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
    echo -e "${RED}Error: Invalid PROJECT_ID. It must start with a letter and can only contain alphanumeric characters, hyphens, or underscores.${RESET}"
    exit 1
fi

# Validate POST_URL
if ! [[ "$POST_URL" =~ ^https?:// ]]; then
    echo -e "${RED}Error: Invalid POST_URL. It must start with http:// or https://.${RESET}"
    exit 1
fi

# Validate ROLE_NAME
if [ ${#ROLE_NAME} -gt 64 ]; then
    echo -e "${RED}Error: ROLE_NAME must not exceed 64 characters.${RESET}"
    exit 1
fi

# Validate SA_NAME
if [ ${#SA_NAME} -gt 30 ]; then
    echo -e "${RED}Error: SA_NAME must not exceed 30 characters.${RESET}"
    exit 1
fi

# Validate SA_DISPLAY_NAME
if [ ${#SA_DISPLAY_NAME} -gt 100 ]; then
    echo -e "${RED}Error: SA_DISPLAY_NAME must not exceed 100 characters.${RESET}"
    exit 1
fi

# Validate ILLUMIO_SA_EMAIL
if ! [[ "$ILLUMIO_SA_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9_-]+\.iam\.gserviceaccount\.com$ ]]; then
    echo -e "${RED}Error: Invalid ILLUMIO_SA_EMAIL. It must be a valid email address.${RESET}"
    exit 1
fi

# Validate PROJECT_ID
if ! [[ "$PROJECT_ID" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
    echo -e "${RED}Error: Invalid PROJECT_ID '$PROJECT_ID'. It must start with a letter and can only contain alphanumeric characters, hyphens, or underscores.${RESET}"
    exit 1
fi

# Determine resource type based on folder ID or organization ID presence
if [ -n "$FOLDER_ID" ] && [ -n "$ORGANIZATION_ID" ]; then
    RESOURCE_TYPE="$RESOURCE_TYPE_FOLDER"
    RESOURCE_ID="$FOLDER_ID"
    echo "Operating at folder level: $FOLDER_ID"

    # Validate folder ID format (typically numeric)
    if ! [[ "$FOLDER_ID" =~ ^[0-9]+$ ]]; then
        echo -e "${RED}Error: Invalid FOLDER_ID. It must be numeric.${RESET}"
        exit 1
    fi

    # Validate organization ID format (typically numeric)
    # This is required for folder onboarding to create custom IAM role
    if ! [[ "$ORGANIZATION_ID" =~ ^[0-9]+$ ]]; then
        echo -e "${RED}Error: Invalid ORGANIZATION_ID. It must be numeric. Organization id is required to create custom IAM role for folder onboarding${RESET}"
        exit 1
    fi

elif [ -n "$ORGANIZATION_ID" ]; then
    RESOURCE_TYPE="$RESOURCE_TYPE_ORGANIZATION"
    RESOURCE_ID="$ORGANIZATION_ID"
    echo "Operating at organization level: $ORGANIZATION_ID"

    # Validate organization ID format (typically numeric)
    if ! [[ "$ORGANIZATION_ID" =~ ^[0-9]+$ ]]; then
        echo -e "${RED}Error: Invalid ORGANIZATION_ID. It must be numeric.${RESET}"
        exit 1
    fi
else
    RESOURCE_TYPE="$RESOURCE_TYPE_PROJECT"
    RESOURCE_ID="$PROJECT_ID"
    echo "Operating at project level: $PROJECT_ID"
fi


# READ_WRITE_MODE
if $READ_WRITE_MODE; then
    echo "Read-write mode enabled."
else
    echo "Read-only mode."
fi

# Function to set the active project
set_project() {
    local project_id=$1
    echo "Setting active project to $project_id..."
    gcloud config set project "$project_id"
    echo -e "${GREEN}DONE${RESET}"
}

# Function to create service account
create_service_account() {
    local project_id=$1
    local sa_name=$2
    local sa_display_name=$3

    echo "Creating service account..."
    gcloud iam service-accounts create "$sa_name" \
        --display-name="$sa_display_name" \
        --project="$project_id" \
        --quiet
    #creating service account email using the service account name and project id
    local sa_email="${sa_name}@${project_id}.iam.gserviceaccount.com"
    echo "$sa_email"
}

# Function to create IAM role
create_iam_role() {
    local resource_type=$1
    local resource_id=$2
    local role_name=$3
    local permissions=$4
    local organization_id=$5

    echo "Creating custom IAM role..."
    if [ "$resource_type" == "$RESOURCE_TYPE_PROJECT" ]; then
        gcloud iam roles create "$role_name" \
            --project="$resource_id" \
            --title="$role_name" \
            --description="Custom role for listing and getting storage and VPCs" \
            --permissions="$permissions" \
            --stage="GA" \
            --quiet > /dev/null
    elif [ "$resource_type" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
         gcloud iam roles create "$role_name" \
             --organization="$resource_id" \
             --title="$role_name" \
             --description="Custom role for organization-level permissions" \
             --permissions="$permissions" \
             --stage="GA" \
             --quiet > /dev/null
    elif [ "$resource_type" == "$RESOURCE_TYPE_FOLDER" ]; then
#      iam role could only be created at project/organization level, for folder onboarding creating it at organization level
       gcloud iam roles create "$role_name" \
           --organization="$organization_id" \
           --title="$role_name" \
           --description="Custom role for folder-level permissions" \
           --permissions="$permissions" \
           --stage="GA" \
           --quiet > /dev/null
    fi
    add_resource "roles" "$role_name"
    echo -e "${GREEN}DONE${RESET}"
}

# Function to bind IAM role to service account
bind_role_to_service_account() {
    local resource_type=$1
    local resource_id=$2
    local sa_email=$3
    local role_name=$4
    local organization_id=$5

    echo "Binding custom IAM role to service account..."
    if [ "$resource_type" == "$RESOURCE_TYPE_PROJECT" ]; then
        gcloud projects add-iam-policy-binding "$resource_id" \
            --member="serviceAccount:$sa_email" \
            --role="projects/$resource_id/roles/$role_name" \
            --condition=None \
            --quiet > /dev/null
    elif [ "$resource_type" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
         gcloud organizations add-iam-policy-binding "$resource_id" \
             --member="serviceAccount:$sa_email" \
             --role="organizations/$resource_id/roles/$role_name" \
             --condition=None \
             --quiet > /dev/null
    elif [ "$resource_type" == "$RESOURCE_TYPE_FOLDER" ]; then
         gcloud resource-manager folders add-iam-policy-binding "$resource_id" \
             --member="serviceAccount:$sa_email" \
             --role="organizations/$organization_id/roles/$role_name" \
             --condition=None \
             --quiet > /dev/null
    fi
    echo -e "${GREEN}DONE${RESET}"
}

# Function to assign predefined role to service account
assign_predefined_role() {
    local resource_type=$1
    local resource_id=$2
    local sa_email=$3
    local role=$4

    echo "Assigning predefined role $role to service account..."
    if [ "$resource_type" == "$RESOURCE_TYPE_PROJECT" ]; then
        gcloud projects add-iam-policy-binding "$resource_id" \
            --member="serviceAccount:$sa_email" \
            --role="$role" \
            --condition=None \
            --quiet > /dev/null
    elif  [ "$resource_type" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
        gcloud organizations add-iam-policy-binding "$resource_id" \
                --member="serviceAccount:$sa_email" \
                --role="$role"\
                --condition=None \
                --quiet > /dev/null
    elif [ "$resource_type" == "$RESOURCE_TYPE_FOLDER" ]; then
        gcloud resource-manager folders add-iam-policy-binding "$resource_id" \
            --member="serviceAccount:$sa_email" \
            --role="$role" \
            --condition=None \
            --quiet > /dev/null
    fi
    echo -e "${GREEN}DONE${RESET}"
}

# Function to enable impersonation permissions
enable_impersonation_permissions() {
    local sa_email=$1
    local project_id=$2
    local illumio_sa_email=$3

    echo "Configuring impersonation permissions..."
    gcloud iam service-accounts add-iam-policy-binding "$sa_email" \
        --member="serviceAccount:$illumio_sa_email" \
        --role="roles/iam.serviceAccountTokenCreator" \
        --project="$project_id" \
        --condition=None \
        --quiet > /dev/null
    echo -e "${GREEN}DONE${RESET}"
}

send_data_to_endpoint() {
    local sa_email=$1
    local resource_id=$2
    local project_id=$3
    local post_url=$4
    local tenant_id=$5
    local auth_key=$6  # Basic Authentication key
    local auth_secret=$7  # Basic Authentication secret
    local event=$8
    local resource_type=$9

    if [[ "$post_url" !=  *"proxy"* ]] || [[ "$post_url" == *"sunnyvale"* ]]; then
        post_url="$post_url/api/v1/integrations/cloud_credentials"
    fi

    # Determine onboarding_type based on resource_type
    local integration_type=""
    if [ "$resource_type" == "project" ]; then
        integration_type="GcpProject"
    elif [ "$resource_type" == "folder" ]; then
        integration_type="GcpFolder"
    elif [ "$resource_type" == "organization" ]; then
        integration_type="GcpOrganization"
    else
        echo "Error: Invalid resource_type provided."
        exit 1
    fi

    echo "Sending resource ID, service account email, and project ID to $post_url"

    # Perform the HTTP POST request with Basic Authentication and custom headers
    response=$(curl -s -w "\n%{http_code}" -X POST \
         -H "Content-Type: application/json" \
         -H "Authorization: Basic $(echo -n "$auth_key:$auth_secret" | base64 | tr -d '\n')" \
         -H "X-Tenant-Id: $tenant_id" \
         -d "{\"sa_email\":\"$sa_email\",\"gcp_resource_id\":\"$resource_id\",\"project_csp_id\":\"$project_id\",\"type\":\"$event\",\"integration_type\":\"$integration_type\"}" \
         "$post_url")

    # Extract the response body and status code
    body=$(echo "$response" | sed '$d')  # Remove last line (status code)
    status_code=$(echo "$response" | tail -n1)  # Extract last line (status code)

    # Check the HTTP status code
    if [ "$status_code" -ge 200 ] && [ "$status_code" -lt 300 ]; then
        echo "POST request successful. Response:"
        echo "$body"
    else
        echo "Error: POST request failed with status code $status_code. Response:"
        echo "$body"
        exit 1  # Trigger cleanup via ERR trap if there's an error.
    fi
}

# Function to check if gcloud CLI is available
check_gcloud_availability() {
    if ! command -v gcloud &> /dev/null; then
        echo -e "${RED}Error: gcloud CLI is not available. Please install and configure it before running this script.${RESET}"
        exit 1
    fi
    echo "gcloud CLI is available. Proceeding with the script."
}

# Function to enable specific APIs for a project
enable_apis_for_project() {
    local project_id=$1
    echo "Enabling APIs (iamcredentials.googleapis.com, cloudresourcemanager.googleapis.com) for project $project_id..."
    if ! gcloud services enable iamcredentials.googleapis.com cloudresourcemanager.googleapis.com --project="$project_id" --quiet; then
        echo -e "${RED}Failed to enable required APIs for project $project_id.${RESET}"
        exit 1
    fi
    echo -e "${GREEN}DONE${RESET}"
}

# Main execution sequence
main() {
    check_gcloud_availability
    if [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_PROJECT" ]; then
        set_project "$PROJECT_ID"
    fi
    # TODO: Check user permissions before proceeding

    SA_EMAIL=$(create_service_account "$PROJECT_ID" "$SA_NAME" "$SA_DISPLAY_NAME" | tail -n 1)
    # doing add_resource here for service account because we are using a pipe (| tail -n 1) when calling the function, which creates a subshell.
    # Variables modified in subshells do not propagate back to the parent shell, hence the added service account was not visible in cleanup function
    add_resource "service_accounts" "$SA_EMAIL"

    # Prompt user for consent to enable APIs for Illumio-supported resources
    echo "Do you allow Illumio to enable APIs for supported resources at the time of ingestion? (y/N)"
    read -r consent
    if [[ "$consent" == "y" || "$consent" == "Y" ]]; then
        echo "User consented to enable APIs for Illumio-supported resources."
        create_iam_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$DEFAULT_API_ENABLE_ROLE_NAME" "$API_ENABLE_PERMISSION" "$ORGANIZATION_ID"
        bind_role_to_service_account "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "$DEFAULT_API_ENABLE_ROLE_NAME" "$ORGANIZATION_ID"
    else
        echo "User did not consent to enable APIs for Illumio-supported resources."
    fi

    # Convert the comma-separated list into an array and loop through it
    IFS=',' read -ra ROLES_ARRAY <<< "$PREDEFINED_ROLES"
    for role in "${ROLES_ARRAY[@]}"; do
        assign_predefined_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "$role"
    done
    enable_impersonation_permissions "$SA_EMAIL" "$PROJECT_ID" "$ILLUMIO_SA_EMAIL"

    # required to list projects under the organization or folder
    if [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_ORGANIZATION" ] || [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_FOLDER" ]; then
      assign_predefined_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "roles/browser"
    fi

    # Check if READ_WRITE_MODE is true to create and bind IAM role with WRITE_PERMISSIONS
    if $READ_WRITE_MODE; then
        echo "Read-write mode is enabled. Creating IAM role with write permissions..."
        create_iam_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$DEFAULT_WRITE_ROLE_NAME" "$WRITE_PERMISSIONS" "$ORGANIZATION_ID"
        bind_role_to_service_account "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "$DEFAULT_WRITE_ROLE_NAME" "$ORGANIZATION_ID"
    fi

    # Enable APIs for the project to allow impersonation and cloudresourcemanager to read child resources
    enable_apis_for_project "$PROJECT_ID"

    send_data_to_endpoint "$SA_EMAIL" "$RESOURCE_ID" "$PROJECT_ID" "$POST_URL" "$CS_TENANT_ID" "$AUTH_KEY" "$AUTH_SECRET" "GCPRole" "$RESOURCE_TYPE"
}

main
echo -e "${GREEN}Script executed successfully.${RESET}"#!/bin/bash

#Constants
RESOURCE_TYPE_PROJECT="project"
RESOURCE_TYPE_ORGANIZATION="organization"
RESOURCE_TYPE_FOLDER="folder"
DEFAULT_ROLE_NAME="illumio_role_$(date +%s)"
DEFAULT_SA_NAME="illumio-sa-$(date +%s)"
DEFAULT_SA_DISPLAY_NAME="Illumio Service Account"
DEFAULT_POST_URL="https://cloud.illum.io"
PREDEFINED_ROLES="roles/iam.securityReviewer,roles/compute.viewer,roles/cloudasset.viewer"
DEFAULT_WRITE_ROLE_NAME="illumio_write_role_$(date +%s)"
WRITE_PERMISSIONS="compute.firewalls.create,compute.firewalls.delete,compute.firewalls.get,compute.firewalls.update,compute.networks.updatePolicy"
DEFAULT_API_ENABLE_ROLE_NAME="illumio_api_enable_role_$(date +%s)"
API_ENABLE_PERMISSION="serviceusage.services.enable,serviceusage.services.list,serviceusage.services.get"

# Resource tracking
created_resources__service_accounts=""
created_resources__roles=""
RESOURCE_TYPE="$RESOURCE_TYPE_PROJECT" # using default resource type as object, based on if organization id is passed in arguments, it changes to "$RESOURCE_TYPE_ORGANIZATION"

# Function to add a resource to the tracking list - these lists will be used to clean up resources in case of an error
add_resource() {
    local resource_type=$1
    local resource_value=$2
    local var_name="created_resources__${resource_type}"
    if [[ -z "${!var_name}" ]]; then
        eval "$var_name=\"$resource_value\""
    else
        eval "$var_name=\"${!var_name},$resource_value\""
    fi
}

# Cleanup function for errors (triggered by trap)
cleanup() {
    if [ "$1" == "0" ]; then
      return
    fi
    echo "Cleaning up resources..."

    # Delete service accounts
    IFS=',' read -ra SAS <<< "${created_resources__service_accounts}"
    for sa in "${SAS[@]}"; do
        echo "Deleting service account: $sa"
        gcloud iam service-accounts delete "$sa" --quiet --project="$PROJECT_ID" || true
    done

    # Delete roles
    IFS=',' read -ra ROLES <<< "${created_resources__roles}"
    for role in "${ROLES[@]}"; do
        echo "Deleting IAM role: $role"
        if [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
            gcloud iam roles delete "$role" --organization="$ORGANIZATION_ID" --quiet || true
        else
            gcloud iam roles delete "$role" --project="$PROJECT_ID" --quiet || true
        fi
    done
}

# Error handling setup
set -e
trap 'cleanup $?' EXIT

ILLUMIO_SA_EMAIL="[email protected]"

print_usage() {
    echo "Usage: $0 --project-id PROJECT_ID --auth-key AUTH_KEY --auth-secret AUTH_SECRET --tenant-id CS_TENANT_ID  [--organization-id ORGANIZATION_ID] [--read-write] [--role-name ROLE_NAME] [--sa-name SA_NAME] [--sa-display-name SA_DISPLAY_NAME] [--illumio-sa-email ILLUMIO_SA_EMAIL] [--post-url POST_URL]"
    echo "  --project-id PROJECT_ID             Specify the GCP project ID (mandatory)"
    echo "  --post-url POST_URL                 Specify the POST URL (optional)"
    echo "  --role-name ROLE_NAME               Specify the role name (optional)"
    echo "  --sa-name SA_NAME                   Specify the service account name (optional)"
    echo "  --sa-display-name SA_DISPLAY_NAME   Specify the service account display name (optional)"
    echo "  --illumio-sa-email ILLUMIO_SA_EMAIL Specify the Illumio service account email (optional)"
    echo "  --auth-key AUTH_KEY                 Specify the authentication key for basic auth to Illumio endpoint (mandatory)"
    echo "  --auth-secret AUTH_SECRET           Specify the authentication secret for basic auth Illumio endpoint (mandatory)"
    echo "  --tenant-id CS_TENANT_ID            Specify the tenant ID for HTTP requests (mandatory)"
    echo "  --organization-id ORGANIZATION_ID   Specify the GCP organization ID (optional)"
    echo "  --folder-id FOLDER_ID               Specify the GCP folder ID (optional)"
    echo "  --read-write                        Enable read-write mode (optional)"
    exit 1
}


# Parse arguments
READ_WRITE_MODE=false
while [[ $# -gt 0 ]]; do
    case $1 in
        --project-id) PROJECT_ID="$2"; shift 2 ;;
        --post-url) POST_URL="$2"; shift 2 ;;
        --role-name) ROLE_NAME="$2"; shift 2 ;;
        --sa-name) SA_NAME="$2"; shift 2 ;;
        --sa-display-name) SA_DISPLAY_NAME="$2"; shift 2 ;;
        --illumio-sa-email) ILLUMIO_SA_EMAIL="$2"; shift 2 ;;
        --organization-id) ORGANIZATION_ID="$2"; shift 2 ;;
        --folder-id) FOLDER_ID="$2"; shift 2 ;;
        --read-write) READ_WRITE_MODE=true; shift ;;
        --auth-key) AUTH_KEY="$2"; shift 2 ;;
        --auth-secret) AUTH_SECRET="$2"; shift 2 ;;
        --tenant-id) CS_TENANT_ID="$2"; shift 2 ;;
        *) echo "Unknown option: $1"; print_usage ;;
    esac
done


# Validate mandatory parameters
if [ -z "$PROJECT_ID" ] || [ -z "$AUTH_KEY" ] || [ -z "$AUTH_SECRET" ] || [ -z "$CS_TENANT_ID" ]; then
#    echo "$PROJECT_ID $AUTH_KEY $AUTH_SECRET $CS_TENANT_ID"
    echo "Error: Project id, auth key, secret and tenant id are mandatory."
    print_usage
fi

# Set default values if not provided
ROLE_NAME="${ROLE_NAME:-$DEFAULT_ROLE_NAME}"
SA_NAME="${SA_NAME:-$DEFAULT_SA_NAME}"
SA_DISPLAY_NAME="${SA_DISPLAY_NAME:-$DEFAULT_SA_DISPLAY_NAME}"
POST_URL="${POST_URL:-$DEFAULT_POST_URL}"

# Validate PROJECT_ID
if ! [[ "$PROJECT_ID" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
    echo "Error: Invalid PROJECT_ID. It must start with a letter and can only contain alphanumeric characters, hyphens, or underscores."
    exit 1
fi

# Validate POST_URL
if ! [[ "$POST_URL" =~ ^https?:// ]]; then
    echo "Error: Invalid POST_URL. It must start with http:// or https://."
    exit 1
fi

# Validate ROLE_NAME
if [ ${#ROLE_NAME} -gt 64 ]; then
    echo "Error: ROLE_NAME must not exceed 64 characters."
    exit 1
fi

# Validate SA_NAME
if [ ${#SA_NAME} -gt 30 ]; then
    echo "Error: SA_NAME must not exceed 30 characters."
    exit 1
fi

# Validate SA_DISPLAY_NAME
if [ ${#SA_DISPLAY_NAME} -gt 100 ]; then
    echo "Error: SA_DISPLAY_NAME must not exceed 100 characters."
    exit 1
fi

# Validate ILLUMIO_SA_EMAIL
if ! [[ "$ILLUMIO_SA_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9_-]+\.iam\.gserviceaccount\.com$ ]]; then
    echo "Error: Invalid ILLUMIO_SA_EMAIL. It must be a valid email address."
    exit 1
fi

# Validate PROJECT_ID
if [ ${#PROJECT_ID} -lt 6 ] || [ ${#PROJECT_ID} -gt 30 ]; then
    echo "Error: PROJECT_ID must be between 6 to 30 characters."
    exit 1
fi

# Determine resource type based on folder ID or organization ID presence
if [ -n "$FOLDER_ID" ] && [ -n "$ORGANIZATION_ID" ]; then
    RESOURCE_TYPE="$RESOURCE_TYPE_FOLDER"
    RESOURCE_ID="$FOLDER_ID"
    echo "Operating at folder level: $FOLDER_ID"

    # Validate folder ID format (typically numeric)
    if ! [[ "$FOLDER_ID" =~ ^[0-9]+$ ]]; then
        echo "Error: Invalid FOLDER_ID. It must be numeric."
        exit 1
    fi

    # Validate organization ID format (typically numeric)
    # This is required for folder onboarding to create custom IAM role
    if ! [[ "$ORGANIZATION_ID" =~ ^[0-9]+$ ]]; then
        echo "Error: Invalid ORGANIZATION_ID. It must be numeric. Organization id is required to create custom IAM role for folder onboarding"
        exit 1
    fi

elif [ -n "$ORGANIZATION_ID" ]; then
    RESOURCE_TYPE="$RESOURCE_TYPE_ORGANIZATION"
    RESOURCE_ID="$ORGANIZATION_ID"
    echo "Operating at organization level: $ORGANIZATION_ID"

    # Validate organization ID format (typically numeric)
    if ! [[ "$ORGANIZATION_ID" =~ ^[0-9]+$ ]]; then
        echo "Error: Invalid ORGANIZATION_ID. It must be numeric."
        exit 1
    fi
else
    RESOURCE_TYPE="$RESOURCE_TYPE_PROJECT"
    RESOURCE_ID="$PROJECT_ID"
    echo "Operating at project level: $PROJECT_ID"
fi


# READ_WRITE_MODE
if $READ_WRITE_MODE; then
    echo "Read-write mode enabled."
else
    echo "Read-only mode."
fi

# Function to set the active project
set_project() {
    local project_id=$1
    echo "Setting active project to $project_id..."
    gcloud config set project "$project_id"
}

# Function to create service account
create_service_account() {
    local project_id=$1
    local sa_name=$2
    local sa_display_name=$3

    echo "Creating service account..."
    gcloud iam service-accounts create "$sa_name" \
        --display-name="$sa_display_name" \
        --project="$project_id" \
        --quiet
    #creating service account email using the service account name and project id
    local sa_email="${sa_name}@${project_id}.iam.gserviceaccount.com"
    echo "$sa_email"
}

# Function to create IAM role
create_iam_role() {
    local resource_type=$1
    local resource_id=$2
    local role_name=$3
    local permissions=$4
    local organization_id=$5

    echo "Creating custom IAM role..."
    if [ "$resource_type" == "$RESOURCE_TYPE_PROJECT" ]; then
        gcloud iam roles create "$role_name" \
            --project="$resource_id" \
            --title="$role_name" \
            --description="Custom role for listing and getting storage and VPCs" \
            --permissions="$permissions" \
            --stage="GA"
    elif [ "$resource_type" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
         gcloud iam roles create "$role_name" \
             --organization="$resource_id" \
             --title="$role_name" \
             --description="Custom role for organization-level permissions" \
             --permissions="$permissions" \
             --stage="GA"
    elif [ "$resource_type" == "$RESOURCE_TYPE_FOLDER" ]; then
#      iam role could only be created at project/organization level, for folder onboarding creating it at organization level
       gcloud iam roles create "$role_name" \
           --organization="$organization_id" \
           --title="$role_name" \
           --description="Custom role for folder-level permissions" \
           --permissions="$permissions" \
           --stage="GA"
    fi
    add_resource "roles" "$role_name"
}

# Function to bind IAM role to service account
bind_role_to_service_account() {
    local resource_type=$1
    local resource_id=$2
    local sa_email=$3
    local role_name=$4
    local organization_id=$5

    echo "Binding custom IAM role to service account..."
    if [ "$resource_type" == "$RESOURCE_TYPE_PROJECT" ]; then
        gcloud projects add-iam-policy-binding "$resource_id" \
            --member="serviceAccount:$sa_email" \
            --role="projects/$resource_id/roles/$role_name" \
            --condition=None \
            --quiet
    elif [ "$resource_type" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
         gcloud organizations add-iam-policy-binding "$resource_id" \
             --member="serviceAccount:$sa_email" \
             --role="organizations/$resource_id/roles/$role_name" \
             --condition=None \
             --quiet
    elif [ "$resource_type" == "$RESOURCE_TYPE_FOLDER" ]; then
         gcloud resource-manager folders add-iam-policy-binding "$resource_id" \
             --member="serviceAccount:$sa_email" \
             --role="organizations/$organization_id/roles/$role_name" \
             --condition=None \
             --quiet
    fi
}

# Function to assign predefined role to service account
assign_predefined_role() {
    local resource_type=$1
    local resource_id=$2
    local sa_email=$3
    local role=$4

    echo "Assigning predefined role $role to service account..."
    if [ "$resource_type" == "$RESOURCE_TYPE_PROJECT" ]; then
        gcloud projects add-iam-policy-binding "$resource_id" \
            --member="serviceAccount:$sa_email" \
            --role="$role" \
            --condition=None \
            --quiet
    elif  [ "$resource_type" == "$RESOURCE_TYPE_ORGANIZATION" ]; then
        gcloud organizations add-iam-policy-binding "$resource_id" \
                --member="serviceAccount:$sa_email" \
                --role="$role"\
                --condition=None \
                --quiet
    elif [ "$resource_type" == "$RESOURCE_TYPE_FOLDER" ]; then
        gcloud resource-manager folders add-iam-policy-binding "$resource_id" \
            --member="serviceAccount:$sa_email" \
            --role="$role" \
            --condition=None \
            --quiet
    fi
}

# Function to enable impersonation permissions
enable_impersonation_permissions() {
    local sa_email=$1
    local project_id=$2
    local illumio_sa_email=$3

    echo "Configuring impersonation permissions..."
    gcloud iam service-accounts add-iam-policy-binding "$sa_email" \
        --member="serviceAccount:$illumio_sa_email" \
        --role="roles/iam.serviceAccountTokenCreator" \
        --project="$project_id" \
        --condition=None \
        --quiet
}

send_data_to_endpoint() {
    local sa_email=$1
    local resource_id=$2
    local project_id=$3
    local post_url=$4
    local tenant_id=$5
    local auth_key=$6  # Basic Authentication key
    local auth_secret=$7  # Basic Authentication secret
    local event=$8
    local resource_type=$9

    if [[ "$post_url" !=  *"proxy"* ]] || [[ "$post_url" == *"sunnyvale"* ]]; then
        post_url="$post_url/api/v1/integrations/cloud_credentials"
    fi

    # Determine onboarding_type based on resource_type
    local integration_type=""
    if [ "$resource_type" == "project" ]; then
        integration_type="GcpProject"
    elif [ "$resource_type" == "folder" ]; then
        integration_type="GcpFolder"
    elif [ "$resource_type" == "organization" ]; then
        integration_type="GcpOrganization"
    else
        echo "Error: Invalid resource_type provided."
        exit 1
    fi

    echo "Sending resource ID, service account email, and project ID to $post_url"

    # Perform the HTTP POST request with Basic Authentication and custom headers
    response=$(curl -s -w "\n%{http_code}" -X POST \
         -H "Content-Type: application/json" \
         -H "Authorization: Basic $(echo -n "$auth_key:$auth_secret" | base64 | tr -d '\n')" \
         -H "X-Tenant-Id: $tenant_id" \
         -d "{\"sa_email\":\"$sa_email\",\"gcp_resource_id\":\"$resource_id\",\"project_csp_id\":\"$project_id\",\"type\":\"$event\",\"integration_type\":\"$integration_type\"}" \
         "$post_url")

    # Extract the response body and status code
    body=$(echo "$response" | sed '$d')  # Remove last line (status code)
    status_code=$(echo "$response" | tail -n1)  # Extract last line (status code)

    # Check the HTTP status code
    if [ "$status_code" -ge 200 ] && [ "$status_code" -lt 300 ]; then
        echo "POST request successful. Response:"
        echo "$body"
    else
        echo "Error: POST request failed with status code $status_code. Response:"
        echo "$body"
        exit 1  # Trigger cleanup via ERR trap if there's an error.
    fi
}

# Function to check if gcloud CLI is available
check_gcloud_availability() {
    if ! command -v gcloud &> /dev/null; then
        echo "Error: gcloud CLI is not available. Please install and configure it before running this script."
        exit 1
    fi
    echo "gcloud CLI is available. Proceeding with the script."
}


# Main execution sequence
main() {
    check_gcloud_availability
    if [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_PROJECT" ]; then
        set_project "$PROJECT_ID"
    fi
    # TODO: Check user permissions before proceeding

    SA_EMAIL=$(create_service_account "$PROJECT_ID" "$SA_NAME" "$SA_DISPLAY_NAME" | tail -n 1)
    # doing add_resource here for service account because we are using a pipe (| tail -n 1) when calling the function, which creates a subshell.
    # Variables modified in subshells do not propagate back to the parent shell, hence the added service account was not visible in cleanup function
    add_resource "service_accounts" "$SA_EMAIL"

    # Prompt user for consent to enable APIs for Illumio-supported resources
    echo "Do you want to allow Illumio to enable APIs for supported resources? (y/N)"
    read -r consent
    if [[ "$consent" == "y" || "$consent" == "Y" ]]; then
        echo "User consented to enable APIs for Illumio-supported resources."
        create_iam_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$DEFAULT_API_ENABLE_ROLE_NAME" "$API_ENABLE_PERMISSION" "$ORGANIZATION_ID"
        bind_role_to_service_account "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "$DEFAULT_API_ENABLE_ROLE_NAME" "$ORGANIZATION_ID"
    else
        echo "User did not consent to enable APIs for Illumio-supported resources."
    fi

    # Convert the comma-separated list into an array and loop through it
    IFS=',' read -ra ROLES_ARRAY <<< "$PREDEFINED_ROLES"
    for role in "${ROLES_ARRAY[@]}"; do
        assign_predefined_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "$role"
    done
    enable_impersonation_permissions "$SA_EMAIL" "$PROJECT_ID" "$ILLUMIO_SA_EMAIL"

    # required to list projects under the organization or folder
    if [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_ORGANIZATION" ] || [ "$RESOURCE_TYPE" == "$RESOURCE_TYPE_FOLDER" ]; then
      assign_predefined_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "roles/browser"
    fi

    # Check if READ_WRITE_MODE is true to create and bind IAM role with WRITE_PERMISSIONS
    if $READ_WRITE_MODE; then
        echo "Read-write mode is enabled. Creating IAM role with write permissions..."
        create_iam_role "$RESOURCE_TYPE" "$RESOURCE_ID" "$DEFAULT_WRITE_ROLE_NAME" "$WRITE_PERMISSIONS" "$ORGANIZATION_ID"
        bind_role_to_service_account "$RESOURCE_TYPE" "$RESOURCE_ID" "$SA_EMAIL" "$DEFAULT_WRITE_ROLE_NAME" "$ORGANIZATION_ID"
    fi

    send_data_to_endpoint "$SA_EMAIL" "$RESOURCE_ID" "$PROJECT_ID" "$POST_URL" "$CS_TENANT_ID" "$AUTH_KEY" "$AUTH_SECRET" "GCPRole" "$RESOURCE_TYPE"
}

main
echo "Script executed successfully."

Flow log support

Illumio Cloud supports VPC and firewall flow logs.

See Set up flow logs in your CSP environment.