Cloud Network Security Google Onboarding Script

This GCP shell script automates the onboarding of a resource (a Project or Folder or Organization) into ACE Cloud Network Security. The following is a breakdown of its sections and their purposes:

Flow Overview:

  1. Check if ACE Cloud Network Security PROJECT_ID service account in the organization already exists

  2. If found, move to Point 4, if not delete other existing ACE Cloud Network Security service-account in the organization and create a new PROJECT_ID service account

  3. Create a custom role with relevant permission at organization level required for hierarchical policies

  4. Assign PROJECT_ID service account to provided TARGET_RESOURCE (project/folder/organization) and bind required roles

  5. If new PROJECT_ID service account was created in Point 2, make POST request to the CloudFlow onboarding URL with the service account key and other details

Find more detailed information below.

1. Initialize Variables

Copy
TOKEN='<ONBOARDING_TOKEN>'
                    ENV='<ENVIRONMENT>'
                    PROJECT_ID=($(echo $PROJECT_ID | tr -d '\n'))
                    TARGET_RESOURCE=($(echo $TARGET_RESOURCE | tr -d '\n'))
                SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com

Purpose: Token -> JWT Token used by application, ENV → stage/prod, PROJECT_ID → project where a service account would be created, TARGET_RESOURCE → project/folder/organization would be onboarded into ACE Cloud Network Security, SERVICE_ACCOUNT_EMAIL → Email of SA which will be created

2. Initialize CloudFlow Onboarding URL

Copy
CF_ONBOARDING_URL='https://<HOST>/cloudflow/api/admin/v1/onboarding/gcp'

Purpose: ACE Cloud Network Security Onboarding URL for onboarding azure tenant

3. Initialize Onboarded Organizations variable

Copy
declare -A ONBOARDED_ORGS_SERVICE_ACCOUNTS=( <GCP_ONBOARDED_ORGS_SERVICE_ACCOUNTS> )

Purpose: Define associate array for already onboarded organization:projects list

4. Binding resource to Service Account

Copy
add_resource_access_to_service_account(){
                    local RESOURCE=$1
                    local TARGET_RESOURCE=$2
                    local SERVICE_ACCOUNT_EMAIL=$3
                    local ORGANIZATION_ID=$4
 
                    customRole=organizations/$ORGANIZATION_ID/roles/inheritedPolicyCFViewer
                    gcloud iam roles describe inheritedPolicyCFViewer --organization=$ORGANIZATION_ID 2>/dev/null
                    if [ $? -ne 0 ]; then
                    echo "Creating custom role inheritedPolicyCFViewer"
                    gcloud iam roles create inheritedPolicyCFViewer --organization=$ORGANIZATION_ID \
                    --title="Inherited Policy Viewer CloudFlow" --description="Inherited Viewer Roles for CloudFlow" \
                    --permissions="compute.firewallPolicies.list,resourcemanager.folders.get,resourcemanager.organizations.get,storage.buckets.list,networksecurity.addressGroups.list,cloudasset.assets.searchAllResources,resourcemanager.tagKeys.list" --stage=ALPHA 1>/dev/null
                    if [ $? -ne 0 ]; then
                    echo "ERROR: The onboarding process has failed while creating organization role — please ensure you have the required permissions"
                    exit 1
                    fi
                    fi
                    echo "Assigning [$customRole] role to service account email [$SERVICE_ACCOUNT_EMAIL] at organization level [$ORGANIZATION_ID]"
                    eval $(echo "gcloud organizations add-iam-policy-binding $ORGANIZATION_ID --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL --role=$customRole 1>/dev/null")
                    if [ $? -ne 0 ]; then
                    echo "ERROR: The onboarding process has failed while adding roles — please ensure you have the required permissions"
                    exit 1
                    fi
                    roles=(compute.viewer iam.serviceAccountViewer serviceusage.serviceUsageAdmin)
                    for role in ${roles[@]}; do
                    echo "Adding a role to the IAM policy of the [${RESOURCE%s} $TARGET_RESOURCE]: [roles/$role]"
                    eval $(echo "gcloud $RESOURCE add-iam-policy-binding $TARGET_RESOURCE --member=serviceAccount:$SERVICE_ACCOUNT_EMAIL --role=roles/$role 1>/dev/null")
                    done
                }

Purpose: Define function to bind resource to a service-account following is the detailed working

  • Custom role is created if not present with relevant permission at organization level which is required for hierarchal policies (Inherited)

  • Assigning Roles ('compute.viewer', 'iam.serviceAccountViewer', 'serviceusage.serviceUsageAdmin') to IAM policy of the Target Resource in scope of member service account

5 Retrieving Billing Account

Copy
IFS=$'\n' BILLING_ACCOUNTS=($(gcloud alpha billing accounts list --filter=open=true --format json 2>/dev/null | jq -r '.[].name'))
                    if [ ${#BILLING_ACCOUNTS[@]} -eq 0 ]; then
                    echo "ERROR: No billing account found"
                    echo "Billing must be enabled for activation of some of the services used by CloudFlow"
                    exit 1
                fi

Purpose: Making sure Billing Account is enabled before Onboarding process begins

6. Retrieving Organization (Target is Organization)

Copy
ORGANIZATION_IDS=($(gcloud organizations list --format json | jq -r '.[].name' | cut -d'/' -f2))
                    if [ ${#ORGANIZATION_IDS[@]} -eq 0 ]; then
                    echo "ERROR: No organizations found"
                    echo "CloudFlow supports only the Google Workspace and Cloud Identity accounts that have access to additional features of the Google Cloud resource hierarchy, such as organization and folder resources."
                    exit 1
                    elif [ ${#ORGANIZATION_IDS[@]} -eq 1 ]; then
                    ORGANIZATION_ID=${ORGANIZATION_IDS[0]}
                    echo "Organization Id [$ORGANIZATION_ID]"
                    else
                    for orgId in "${ORGANIZATION_IDS[@]}"; do
                    if [[ $orgId == $TARGET_RESOURCE ]]; then
                    ORGANIZATION_ID=$orgId
                    resource="organizations"
                    target="organization $TARGET_RESOURCE"
                    echo "Organization Id [$ORGANIZATION_ID]"
                    break
                    fi
                    done
                fi

Purpose: Retrieves the correct organization for the selected target resource with support for Multi Organization Onboarding in case target resource is Organization

7. Retrieve Organization (Target is Folder/Project)

Copy
if [ -z "$target" ];then
                    gcloud resource-manager folders describe $TARGET_RESOURCE 2>/dev/null
                    if [ $? -eq 0 ]; then
                    resource="resource-manager folders"
                    target="folder $TARGET_RESOURCE"
                    if [ -z ${ORGANIZATION_ID} ];then
                    ORGANIZATION_ID=$(gcloud resource-manager folders get-ancestors-iam-policy $TARGET_RESOURCE --format="json(id,type)" | jq -r '.[] | select(.type == "organization") | .id')
                    echo "Organization Id [$ORGANIZATION_ID] found for Folder Id [$TARGET_RESOURCE]"
                    fi
                    else
                    gcloud projects describe $TARGET_RESOURCE 2>/dev/null
                    if [ $? -eq 0 ]; then
                    resource="projects"
                    target="project $TARGET_RESOURCE"
                    if [ -z ${ORGANIZATION_ID} ];then
                    ORGANIZATION_ID=$(gcloud projects get-ancestors $TARGET_RESOURCE --format="json(id,type)" | jq -r '.[] | select(.type == "organization") | .id')
                    echo "Organization Id [$ORGANIZATION_ID] found for Project Id [$TARGET_RESOURCE]"
                    fi
                    else
                    echo "ERROR: The target resource [$TARGET_RESOURCE] wasn't found or the user has no permission to work with it"
                    echo "The onboarding process has failed — please ensure you have the required permissions"
                    exit 1
                    fi
                    fi
                fi

Purpose: Determines the target resource type (organization, folder, or project) and retrieves the associated organization ID.

8. Retrieve Organization (Access Project)

Copy
ACCESS_ORGANIZATION_ID=$(gcloud projects get-ancestors $PROJECT_ID --format="json(id,type)" | jq -r '.[] | select(.type == "organization") | .id')
                    if [ -z ${ACCESS_ORGANIZATION_ID} ];then
                    echo "ERROR: Failed to retreive organization id for the given access project id — please ensure you have the required permissions"
                    exit 1
                fi

Purpose: Verifies the access project ID and retrieves its organization ID.

9. Initialize Project

Copy
gcloud projects describe $PROJECT_ID 2>/dev/null
                    if [ $? -ne 0 ]; then
                    echo "ERROR: Cannot find project with ID $PROJECT_ID"
                    exit 1
                    fi
 
                    gcloud config set project $PROJECT_ID
                    eval $(echo "gcloud iam service-accounts list --project $PROJECT_ID --format json 1>/dev/null")
                    if [ $? -ne 0 ]; then
                    echo "ERROR: The onboarding process has failed while listing service-accounts — please ensure you have the required permissions"
                    exit 1
                fi

Purpose: Set project before proceeding to Onboard.

10. Existing Service Accounts

Copy
if [[ -v ONBOARDED_ORGS_SERVICE_ACCOUNTS["$ACCESS_ORGANIZATION_ID"] ]]; then
                    IFS=',' read -ra serviceAccs <<< "${ONBOARDED_ORGS_SERVICE_ACCOUNTS["$ACCESS_ORGANIZATION_ID"]}"
                    if [[ "${serviceAccs[0]}" == *"@$PROJECT_ID.iam.gserviceaccount.com"* ]]; then
                    echo "Cloudflow service account ${serviceAccs[0]} for access project id $PROJECT_ID already exists"
                    echo "Binding service account: ${serviceAccs[0]} to resource: ${resource%s} $TARGET_RESOURCE"
                    add_resource_access_to_service_account $resource $TARGET_RESOURCE "${serviceAccs[0]}" $ORGANIZATION_ID
                    echo "Success: ${resource%s} $TARGET_RESOURCE onboarded to CloudFlow successfully. Sync may take upto an hour."
                    exit 0
                    fi
                    for delServiceAccoutEmail in "${serviceAccs[@]}"; do
                    # extract project id from sa email
                    saProjectId="${delServiceAccoutEmail#*@}" 
                    saProjectId="${saProjectId%%.*}" 
                    gcloud iam service-accounts list --project $saProjectId --format="value(email)" | grep $delServiceAccoutEmail
                    if [ $? -eq 0 ]; then
                    echo "Deleting old service account [$delServiceAccoutEmail]"
                    gcloud config set project $saProjectId
                    gcloud iam service-accounts delete $delServiceAccoutEmail --quiet
                    fi
                    done
                    else
                    echo "No existing service-account found for ${ACCESS_ORGANIZATION_ID}"
                fi

Purpose: Checks for existing CloudFlow service accounts under access project and if present will be attached to selected Target Resource else old service account will be cleaned up

11. Create Service Account

Copy
gcloud iam service-accounts list --project $PROJECT_ID --format="value(email)" | grep $SERVICE_ACCOUNT_EMAIL
                    if [ $? -eq 0 ]; then
                    echo "Cloudflow service-account already exists ${SERVICE_ACCOUNT_EMAIL}. Deleting it."
                    gcloud iam service-accounts delete $SERVICE_ACCOUNT_EMAIL --quiet
                    fi
                    echo "Creating service account [$SERVICE_ACCOUNT_NAME]"
                    gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
                    --description="$SERVICE_ACCOUNT_DESCRIPTION" \
                    --display-name="$SERVICE_ACCOUNT_DISPLAY_NAME" 1>/dev/null
                    if [ $? -ne 0 ]; then
                    echo "ERROR: The onboarding process has failed while creating service-account — please ensure you have the required permissions"
                    exit 1
                    fi
 
                    echo "Creating a key for the service account [$SERVICE_ACCOUNT_NAME]"
                    rm -f $SERVICE_ACCOUNT_NAME.json
                    gcloud iam service-accounts keys create "$SERVICE_ACCOUNT_NAME.json" \
                    --iam-account=$SERVICE_ACCOUNT_EMAIL 1>/dev/null
                    if [ $? -ne 0 ]; then
                    echo "ERROR: The onboarding process has failed while creating key — please ensure you have the required permissions"
                    exit 1
                fi

Purpose: Creates a new service account for CloudFlow if it doesn't exist. along with generating key

12. Service Account Activation

Copy
gcloud config set account $SERVICE_ACCOUNT_EMAIL
                gcloud auth activate-service-account --project=$PROJECT_ID --key-file $SERVICE_ACCOUNT_NAME.json

Purpose: Activates the service account

13. CloudFlow Onboarding Call

Copy
response=$(curl -X POST "$CF_ONBOARDING_URL" \
                    -H "Content-Type: application/json" \
                    -H "Accept: application/json" \
                    -H "Authorization: $TOKEN" \
                    --silent \
                -d '{ "organization_id":"'$ACCESS_ORGANIZATION_ID'", "data":"'$SERVICE_ACCOUNT_KEY'", "project_id":"'$PROJECT_ID'", "event": { "RequestType": "Create" } }')

Purpose: Makes a POST request to the CloudFlow onboarding URL with the service account key and other details.

14. Response Handling

Copy
status=$(echo $response | jq -r '.initialOnboardResult' | jq -r '.status')
                    message=$(echo $response | jq -r '.initialOnboardResult' | jq -r '.message')
                    if [ "$status" == 200 ]; then
                    echo "The onboarding process is finished: $message"
                    echo "Press CTRL+D to close the terminal session"
                    else
                    echo "ERROR: The onboarding process has failed: $message"
                fi

Purpose:

Finally, the script processes the API response:

  • It extracts the status and message from the response.

  • If the status is 200, it prints a success message.

  • If the status is not 200, it prints an error message.