Inside the Google Onboarding Script
The Google Cloud shell script automates the onboarding of a GCP project into ACE. This template provisions the required GCP resources and permissions, enabling AlgoSec to integrate with and monitor the environment. The onboarding process is fully automated through a Google Cloud Function.
There are two scripts available.
-
For ACE with license for Cloud App Analyzer
-
For ACE without license for Cloud App Analyzer
This topic outlines each step of the script to help users understand its purpose and functionality.
Script Sections
1. Initialize variables
#Algosec Cloud tenantId
ALGOSEC_TENANT_ID='<ALGOSEC_TENANT_ID>'
#Algosec Cloud onboarding URL
ALGOSEC_CLOUD_HOST='https://<HOST>'
ALGOSEC_CLOUD_ONBOARDING_URL="$ALGOSEC_CLOUD_HOST<ONBOARDING_PATH>"
# Token
TOKEN='<ONBOARDING_TOKEN>'
ADDITIONALS='<ALGOSEC_ADDITIONALS>' # Optional for CD Mitigation
ENV='<ENVIRONMENT>'
# Define a working project where a service account would be created
PROJECT_ID='<GCP_PROJECT_ID>'
PROJECT_ID=($(echo $PROJECT_ID | tr -d '\n'))
# Define a target project/folder/organization would be onboarded into Algosec Cloud
TARGET_RESOURCE='<GCP_TARGET_RESOURCE>'
TARGET_RESOURCE=($(echo $TARGET_RESOURCE | tr -d '\n'))
# Service account definitions
SERVICE_ACCOUNT_NAME="ace-sa-$ALGOSEC_TENANT_ID"
SERVICE_ACCOUNT_DISPLAY_NAME="AlgoSec Cloud Enterprise"
SERVICE_ACCOUNT_DESCRIPTION="Service account for AlgoSec Cloud Enterprise"
SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com
Purpose: The script starts by defining several variables:
-
ALGOSEC_TENANT_ID = Algosec tenant id
-
ALGOSEC_CLOUD_HOST = The host of Algosec's APIs
-
ALGOSEC_CLOUD_ONBOARDING_URL = The URL for Algosec's onboarding API
-
TOKEN = An authentication token for the API
-
ADDITIONALS = base64-encoded login credentials
-
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 Onboarded Organizations variable
# Define associate array for onboarded organization:service-accounts list
declare -A ONBOARDED_ORGS_SERVICE_ACCOUNTS=( <GCP_ONBOARDED_ORGS_SERVICE_ACCOUNTS> )
Purpose: Define associate array for already onboarded organization:service accounts list
3. Bind resource to Service Account function definition
# Define function to bind resouce to a service-account
add_resource_access_to_service_account(){
local resource=$1
local target_resource=$2
local service_account_email=$3
local organization_id=$4
# Create custom role if it doesn't exist
local custom_role=organizations/$organization_id/roles/inheritedPolicyACViewer
gcloud iam roles describe inheritedPolicyACViewer --organization=$organization_id 2>/dev/null
if [ $? -ne 0 ]; then
echo "Creating custom role inheritedPolicyACViewer"
gcloud iam roles create inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions="compute.firewallPolicies.list,resourcemanager.folders.get,resourcemanager.organizations.get,storage.buckets.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
# Assign custom role to service-account at org level
echo "Assigning [$custom_role] 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=$custom_role 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
# Create app analyzer custom role if it doesn't exist
local caa_custom_role=organizations/$organization_id/roles/appAnalyzerOrgLevelViewer
gcloud iam roles describe appAnalyzerOrgLevelViewer --organization=$organization_id 2>/dev/null
if [ $? -ne 0 ]; then
echo "Creating custom role appAnalyzerOrgLevelViewer"
gcloud iam roles create appAnalyzerOrgLevelViewer --organization=$organization_id \
--title="Cloud App Analyzer Org Viewer" --description="Org level viewer role for Algosec Cloud App Analyzer" \
--permissions="essentialcontacts.contacts.list,essentialcontacts.contacts.get" --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
# Assign app analyzer custom role to service-account at org level
echo "Assigning [$caa_custom_role] 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=$caa_custom_role 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
# Assign required roles for target resource to service-account
roles=( <SERVICE_ACCOUNT_ROLES> )
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")
if [ $? -ne 0 ]; then
echo "ERROR: The onboarding process has failed while adding roles — please ensure you have the required permissions"
exit 1
fi
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
4. Retrieving Billing Account
# Verify billing account
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 Algosec Cloud"
exit 1
fi
Purpose: Making sure Billing Account is enabled before Onboarding process begins
5. Retrieving Organization (Target is Organization)
# Find target resource type and organization
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 "Algosec Cloud 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
else
for orgId in "${ORGANIZATION_IDS[@]}"; do
if [[ $orgId == $TARGET_RESOURCE ]]; then
ORGANIZATION_ID=$orgId
RESOURCE="organizations"
RESOURCE_TYPE="organization"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: 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
6. Retrieve Organization (Target is Folder/Project)
# In case target resource isn't an organization
if [ -z "$TARGET" ];then
gcloud resource-manager folders describe $TARGET_RESOURCE 2>/dev/null
if [ $? -eq 0 ]; then
RESOURCE="resource-manager folders"
RESOURCE_TYPE="folder"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: 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
# In case target resource isn't a folder
gcloud projects describe $TARGET_RESOURCE 2>/dev/null
if [ $? -eq 0 ]; then
RESOURCE="projects"
RESOURCE_TYPE="project"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: 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.
7. Retrieve Organization (Access Project)
# Find organization for access project id
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 retrieve 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.
8. Initialize Project
# Verify project id
gcloud projects describe $PROJECT_ID 2>/dev/null
if [ $? -ne 0 ]; then
echo "ERROR: Cannot find project with ID $PROJECT_ID"
exit 1
fi
# Verify project service-accounts
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.
9. Existing Service Accounts
# Update existing Algosec Cloud service-account of project or delete if service-account is from another project in the organization
echo "Finding existing service-accounts for Algosec Cloud"
# Check if access organization is already known
if [[ -v ONBOARDED_ORGS_SERVICE_ACCOUNTS["$ACCESS_ORGANIZATION_ID"] ]]; then
ONBOARDED_SA=${ONBOARDED_ORGS_SERVICE_ACCOUNTS["$ACCESS_ORGANIZATION_ID"]}
# Update service-account if it already exists
if [[ "$ONBOARDED_SA" == "$SERVICE_ACCOUNT_EMAIL" ]]; then
echo "Algosec Cloud service-account $ONBOARDED_SA for access project id $PROJECT_ID already exists"
echo "Binding service-account: $ONBOARDED_SA to resource: ${RESOURCE%s} $TARGET_RESOURCE"
add_resource_access_to_service_account $RESOURCE $TARGET_RESOURCE $ONBOARDED_SA $ORGANIZATION_ID
echo "Success: ${RESOURCE%s} $TARGET_RESOURCE onboarded to Algosec Cloud successfully. The onboarding process may take up to several hours."
exit 0
else
# Delete access organization service-account from another project
# Extract project id from SA email
SA_PROJECT_ID="${ONBOARDED_SA#*@}"
SA_PROJECT_ID="${SA_PROJECT_ID%%.*}"
gcloud iam service-accounts list --project $SA_PROJECT_ID --format="value(email)" | grep $ONBOARDED_SA
if [ $? -eq 0 ]; then
echo "Deleting old service-account [$ONBOARDED_SA]"
gcloud config set project $SA_PROJECT_ID
gcloud iam service-accounts delete $ONBOARDED_SA --quiet
fi
fi
else
echo "No existing service-account found for ${ACCESS_ORGANIZATION_ID}"
fi
Purpose: Checks for existing ACE service accounts under access project and if present will be attached to selected Target Resource else old service account will be cleaned up.
10. Echo Information about the Target Resource
echo "Preparing to onboard the target resource [$TARGET]"
Purpose: Provides information about the target resource being onboarded, for logging purposes
11. Create Service Account
# Service-account creation
# Delete service-account if it already exists before creating
gcloud config set project $PROJECT_ID
gcloud iam service-accounts list --project $PROJECT_ID --format="value(email)" | grep $SERVICE_ACCOUNT_EMAIL
if [ $? -eq 0 ]; then
echo "Algosec Cloud service-account already exists ${SERVICE_ACCOUNT_EMAIL}. Deleting it."
gcloud iam service-accounts delete $SERVICE_ACCOUNT_EMAIL --quiet
sleep 3
fi
# Create service-account
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
sleep 3
Purpose: Creates a new service account for ACE if it doesn't exist
12. Verify service account created
# Verify service account was created
gcloud iam service-accounts describe "$SERVICE_ACCOUNT_EMAIL" 1>/dev/null
if [ $? -ne 0 ]; then
echo "ERROR: Service account does not exist after creation attempt."
exit 1
fi
Purpose: Verify the service account was created successfuly
13. Generate service account key
# Create service-account key
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
SERVICE_ACCOUNT_KEY=$(cat $SERVICE_ACCOUNT_NAME.json | base64 | tr -d \\n )
Purpose: Generate a key for the service account, and store value in a variable
14. Binding resource to Service Account
# Add roles to resource for service-account
add_resource_access_to_service_account $RESOURCE $TARGET_RESOURCE $SERVICE_ACCOUNT_EMAIL $ORGANIZATION_ID
Purpose: Invoke function to bind target resource to the Service Account
15. Initialize Prevasio related variables
#GCP region
REGION='<GCP_REGION>'
#Prevasio host
PREVASIO_HOST='<PREVASIO_HOST>'
#Prevasio source code location
SOURCES_URL='<PREVASIO_SOURCES_URL>'
PROJECT_IDS=()
HASH=$(echo -n "$ALGOSEC_TENANT_ID" | cut -c1-5)
Purpose: Define Prevasio related variables:
-
REGION = The GCP region where resources will be created
-
PREVASIO_HOST = The host of Algosec's APIs
-
SOURCES_URL = The URL to download Prevasio application source files
-
PROJECT_IDS = Initialize a list for onboarded projects
-
HASH = Short unique identifier
16. Define a function to Retrieve and Filter Project IDs
get_project_ids() {
local resource_type=$1
local resource_id=$2
if [[ "$resource_type" == "project" ]]; then
PROJECT_IDS+=("$resource_id")
else
echo "Fetching projects for $resource_type $resource_id"
projects=($(gcloud projects list --format="value(projectId)"))
for curr_project in "${projects[@]}" ; do
ancestor=$(gcloud projects get-ancestors "$curr_project" --format json | jq ".[] | select(.type == \"$resource_type\" and .id == \"$resource_id\")")
if [[ -n "$ancestor" ]]; then
PROJECT_IDS+=("$curr_project")
fi
done
fi
}
Purpose: This function is responsible to retrieves all available GCP project IDs and filters them based on the given target resource.
17. Create application
Optional for CD Mitigation
create_application() {
PROJECT_NUMBER=$(gcloud projects describe "${1}" --format="value(projectNumber)")
echo "Creating secrets [prevasio-$HASH-auth-token, prevasio-$HASH-url, prevasio-$HASH-api-key] in project $1"
printf "${3}" | gcloud secrets create prevasio-$HASH-org-id --data-file=- --project=$1 --quiet --verbosity=error > /dev/null
gcloud secrets add-iam-policy-binding prevasio-$HASH-org-id --member="serviceAccount:${PROJECT_NUMBER}[email protected]" --role="roles/secretmanager.secretAccessor" --quiet --verbosity=error
printf "${PREVASIO_HOST}" | gcloud secrets create prevasio-$HASH-host --data-file=- --project=$1 --quiet --verbosity=error > /dev/null
gcloud secrets add-iam-policy-binding prevasio-$HASH-host --member="serviceAccount:${PROJECT_NUMBER}[email protected]" --role="roles/secretmanager.secretAccessor" --quiet --verbosity=error
printf "${ADDITIONALS}" | gcloud secrets create prevasio-$HASH-additionals --data-file=- --project=$1 --quiet --verbosity=error > /dev/null
gcloud secrets add-iam-policy-binding prevasio-$HASH-additionals --member="serviceAccount:${PROJECT_NUMBER}[email protected]" --role="roles/secretmanager.secretAccessor" --quiet --verbosity=error
printf "${ALGOSEC_CLOUD_HOST}" | gcloud secrets create prevasio-$HASH-algosec-cloud-host --data-file=- --project=$1 --quiet --verbosity=error > /dev/null
gcloud secrets add-iam-policy-binding prevasio-$HASH-algosec-cloud-host --member="serviceAccount:${PROJECT_NUMBER}[email protected]" --role="roles/secretmanager.secretAccessor" --quiet --verbosity=error
echo "Deploying cloud function prevasio-$HASH-events-forwarder in project $1"
gcloud functions deploy prevasio-$HASH-events-forwarder --gen2 --set-env-vars=HASH=$HASH --set-secrets=PREVASIO_HOST=prevasio-$HASH-host:1,PREVASIO_ADDITIONALS=prevasio-$HASH-additionals:1,ORGANIZATION_ID=prevasio-$HASH-org-id:1,ALGOSEC_CLOUD_HOST=prevasio-$HASH-algosec-cloud-host:1 --region=$2 --runtime=python310 --source=./function/events_forwarder --entry-point=forward_func --trigger-http --no-allow-unauthenticated --max-instances=1 --memory=128Mi --project=$1 --quiet --verbosity=error > /dev/null
echo "Deploying cloud function prevasio-$HASH-cloud-run-scanner in project $1"
gcloud functions deploy prevasio-$HASH-cloud-run-scanner --gen2 --region=$2 --runtime=python310 --source=./function/cloud_run_scanner --entry-point=scan_func --trigger-http --no-allow-unauthenticated --max-instances=1 --memory=256Mi --project=$1 --quiet --verbosity=error > /dev/null
gcloud scheduler jobs create http prevasio-$HASH-cloud-run-scanner-scheduler --schedule="0 */6 * * *" --uri="https://$2-$1.cloudfunctions.net/prevasio-$HASH-cloud-run-scanner" --location=$2 --oidc-service-account-email=$PROJECT_NUMBER[email protected]
echo "Enabling Binary Authorization for Cloud Run services and jobs"
curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" -s -o /dev/null "https://$2-$1.cloudfunctions.net/prevasio-$HASH-cloud-run-scanner"
gcloud pubsub topics create prevasio-$HASH-images-to-sign --project=$1 --quiet --verbosity=error > /dev/null
gcloud pubsub topics add-iam-policy-binding prevasio-$HASH-images-to-sign --member="serviceAccount:${PROJECT_NUMBER}[email protected]" --role="roles/pubsub.publisher" --quiet --verbosity=error
echo "Deploying cloud function prevasio-$HASH-image-attestation-creator in project $1"
gcloud functions deploy prevasio-$HASH-image-attestation-creator --gen2 --set-env-vars=HASH=$HASH --region=$2 --runtime=python310 --source=./function/image_attestation_creator --entry-point=creator_func --trigger-http --no-allow-unauthenticated --max-instances=10 --memory=128Mi --project=$1 --quiet --verbosity=error > /dev/null
gcloud pubsub subscriptions create prevasio-$HASH-image-attestation-creator-subscription --topic=projects/$1/topics/prevasio-$HASH-images-to-sign --expiration-period=never --push-endpoint=https://$2-$1.cloudfunctions.net/prevasio-$HASH-image-attestation-creator --push-auth-service-account=$PROJECT_NUMBER[email protected] --ack-deadline=65 --message-retention-duration=10m --project=$1 --quiet --verbosity=error > /dev/null
IMAGE_BATCH=()
for current_region in $(gcloud compute regions list --format="value(name)" --project=${1} --quiet 2>/dev/null); do
for current_repository in $(gcloud artifacts repositories list --location=${current_region} --project=${1} --format="value(REPOSITORY)" 2>/dev/null); do
for current_image in $(gcloud artifacts docker images list ${current_region}-docker.pkg.dev/${1}/${current_repository} --project=${1} --format="value[separator='@'](IMAGE,DIGEST)" 2>/dev/null); do
IMAGE_BATCH+=($current_image)
if [[ "${#IMAGE_BATCH[@]}" -ge 10 ]] ; then
gcloud pubsub topics publish prevasio-$HASH-images-to-sign --message="$(IFS=, ; echo "${IMAGE_BATCH[*]}")" --project=${1} > /dev/null
IMAGE_BATCH=()
fi
done
done
done
if [[ "${#IMAGE_BATCH[@]}" -gt 0 ]] ; then
gcloud pubsub topics publish prevasio-$HASH-images-to-sign --message="$(IFS=, ; echo "${IMAGE_BATCH[*]}")" --project=${1} > /dev/null
fi
should_create_topic=true
for topic in $(gcloud pubsub topics list --format='value(name)' --project=${1}); do
if [[ "$topic" == */gcr ]] ; then
should_create_topic=false
fi
done
if $should_create_topic ; then
echo "Creating gcr topic to get Artifact Registry events in project $1"
gcloud pubsub topics create gcr --project=$1 --quiet --verbosity=error > /dev/null
fi
echo "Creating subscription prevasio-$HASH-event-subscription in project $1"
gcloud pubsub subscriptions create prevasio-$HASH-event-subscription --topic=projects/$1/topics/gcr --expiration-period=never --push-endpoint=https://$2-$1.cloudfunctions.net/prevasio-$HASH-events-forwarder --push-auth-service-account=$PROJECT_NUMBER[email protected] --ack-deadline=65 --message-retention-duration=10m --project=$1 --quiet --verbosity=error > /dev/null
}
Purpose: This method sets up all required Cloud App Analyzer cloud components in a specified GCP project. It creates secrets, deploys multiple Cloud Functions, configures schedulers, and sets up Pub/Sub topics and subscriptions. It also scans existing Artifact Registry images and publishes them for attestation, enabling automated image security enforcement.
18. Create attestors
Optional for CD Mitigation
create_attestor() {
NOTE_ID="prevasio-$HASH-note"
ATTESTOR_ID="prevasio-$HASH-attestor"
PROJECT_NUMBER=$(gcloud projects describe "${1}" --format="value(projectNumber)")
echo "Creating prevasio-$HASH-attestor in project $1"
echo "{
'attestation': {
'hint': {
'human_readable_name': 'Prevasio attestation authority'
}
}
}" > ./attestor_note.json
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" --data-binary @./attestor_note.json -s -o /dev/null "https://containeranalysis.googleapis.com/v1/projects/${1}/notes/?noteId=${NOTE_ID}"
rm -rf ./attestor_note.json
gcloud container binauthz attestors create $ATTESTOR_ID --attestation-authority-note=$NOTE_ID --attestation-authority-note-project=$1 --project=$1 --quiet --verbosity=error
echo "Adding Prevasio IAM role for prevasio-$HASH-attestor in project $1"
BINAUTHZ_SA_EMAIL="service-${PROJECT_NUMBER}@gcp-sa-binaryauthorization.iam.gserviceaccount.com"
echo "{
'resource': 'projects/${1}/notes/${NOTE_ID}',
'policy': {
'bindings': [
{
'role': 'roles/containeranalysis.notes.occurrences.viewer',
'members': [
'serviceAccount:${BINAUTHZ_SA_EMAIL}'
]
}
]
}
}" > ./iam_request.json
curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" --data-binary @./iam_request.json -s -o /dev/null "https://containeranalysis.googleapis.com/v1/projects/${1}/notes/${NOTE_ID}:setIamPolicy"
rm -rf ./iam_request.json
echo "Adding Prevasio KMS key for prevasio-$HASH-attestor in project $1"
gcloud kms keyrings create prevasio-attestor-keyring --location=global --verbosity=critical --project=$1 --quiet > /dev/null
gcloud kms keys create prevasio-attestor-key --keyring=prevasio-attestor-keyring --location=global --purpose=asymmetric-signing --default-algorithm=ec-sign-p256-sha256 --verbosity=critical --project=$1 --quiet > /dev/null
gcloud kms keys add-iam-policy-binding prevasio-attestor-key --member="serviceAccount:${PROJECT_NUMBER}[email protected]" --role="roles/cloudkms.signer" --location=global --keyring=prevasio-attestor-keyring --quiet --verbosity=error > /dev/null
gcloud beta container binauthz attestors public-keys add --attestor=$ATTESTOR_ID --keyversion-project=$1 --keyversion-location=global --keyversion-keyring=prevasio-attestor-keyring --keyversion-key=prevasio-attestor-key --keyversion=1 --project=$1 --quiet --verbosity=error > /dev/null
if $IMAGE_LOCKING_ENABLED ; then
echo "Setting prevasio-$HASH-attestor to the binary auth policy in project $1"
gcloud container binauthz policy export > ./policy.yaml --project=$1 --quiet --verbosity=error
ATTESTORS=()
for exported_attestor in $(awk '$1 == "-"{ if (key == "requireAttestationsBy:") print $NF; next } {key=$1}' ./policy.yaml); do
ATTESTORS+=($exported_attestor)
done
ATTESTORS+=(projects/$1/attestors/$ATTESTOR_ID)
{
printf 'defaultAdmissionRule:
'
printf ' enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG
'
printf ' evaluationMode: REQUIRE_ATTESTATION
'
printf ' requireAttestationsBy:'
for attestor in "${ATTESTORS[@]}"; do
printf '
- %s' "$attestor"
done
printf '
globalPolicyEvaluationMode: ENABLE'
} > ./updated_policy.yaml
gcloud container binauthz policy import ./updated_policy.yaml --project=$1 --quiet --verbosity=error > /dev/null
rm -rf ./policy.yaml
rm -rf ./updated_policy.yaml
fi
}
Purpose: This method sets up a Binary Authorization attestor in a GCP project for image verification. It creates a Container Analysis note, IAM bindings, KMS keys for signing, and attaches a public key to the attestor. If image locking is enabled, it also updates the Binary Authorization policy to enforce deployment only for attested images.
19. Create resource for the Project
Optional for CD Mitigation
create_project_resources() {
create_attestor $1
create_application $1 $REGION $2
}
Purpose: This method is a wrapper that calls two previously defined methods — create_attestor and create_application. It automates the setup of project-level CLoud App Analyzer resources by:
-
Creating a Binary Authorization attestor via create_attestor.
-
Deploying cloud functions, secrets, and scheduled jobs using create_application.
20. Download and Unpack Application Sources
Optional for CD Mitigation
mkdir -p prevasio-onboarding && rm -rf prevasio-onboarding/*
cd prevasio-onboarding
echo "Downloading Prevasio application resources"
wget -O sources.zip "${SOURCES_URL}?tenant_id=${ALGOSEC_TENANT_ID}"
unzip sources.zip
Purpose: Downloads and unpacks application source files, preparing for deployment.
21. Validate and Prepare Target Projects
Optional for CD Mitigation
get_project_ids "$RESOURCE_TYPE" "$TARGET_RESOURCE"
for project in "${PROJECT_IDS[@]}" ; do
echo "Setting current project to [$project]"
gcloud config set project "$project"
echo "Checking mandatory APIs for [$project]"
MANDATORY_APIS=(artifactregistry.googleapis.com cloudfunctions.googleapis.com cloudkms.googleapis.com cloudscheduler.googleapis.com compute.googleapis.com container.googleapis.com pubsub.googleapis.com secretmanager.googleapis.com binaryauthorization.googleapis.com cloudbuild.googleapis.com containeranalysis.googleapis.com run.googleapis.com)
ENABLED_APIS=()
for enabled_api in $(gcloud services list --enabled --project ${project} --format="value(config.name)"); do
ENABLED_APIS+=("$enabled_api")
done
SHOULD_EXIT=false
for mandatory_api in "${MANDATORY_APIS[@]}"; do
API_NOT_PRESENTED=true
for enabled_api in "${ENABLED_APIS[@]}"; do
if [[ "$mandatory_api" == "$enabled_api" ]] ; then
API_NOT_PRESENTED=false
fi
done
if $API_NOT_PRESENTED ; then
echo "Mandatory API $mandatory_api is not enabled. Please, enable it and try to onboard one more time"
SHOULD_EXIT=true
fi
done
if $SHOULD_EXIT ; then
continue
fi
gcloud iam roles create algosec.gke.custom.role \
--title="Algosec GKE Custom Role" \
--permissions="container.jobs.create,container.jobs.delete,container.namespaces.create,container.nodes.proxy,container.pods.getLogs" \
--project=$project 2>/dev/null
gcloud projects add-iam-policy-binding $project \
--member=serviceAccount:$SERVICE_ACCOUNT_EMAIL \
--role=projects/$project/roles/algosec.gke.custom.role 1>/dev/null
create_project_resources "$project" "$ORGANIZATION_ID"
done
Purpose: This step iterates over all target projects identified by get_project_ids, and for each project:
-
Sets the project as the current working context using gcloud config set project.
-
Verifies that all mandatory Google Cloud APIs required by Prevasio are enabled (e.g., Cloud Functions, Artifact Registry, Binary Authorization).
-
If any required API is missing, it logs a message and skips that project.
-
Creates a custom IAM role (algosec.gke.custom.role) with permissions needed for Prevasio’s GKE integrations.
-
Grants the service account the newly created role.
-
Invokes the create_project_resources method to deploy the attestor and application resources for that project.
22. Cleanup resources
Optional for CD Mitigation
cd ..
rm -rf prevasio-onboarding
Purpose: Remove the downloaded application source files
23. Service Account Activation
# Activate service account
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
24. Onboarding Call
# Send service-account details to Algosec Cloud
response=$(curl -X POST "$ALGOSEC_CLOUD_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 ACE onboarding URL with the service account key and other details.
25. Response Handling
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.
1. Initialize variables
#Algosec Cloud tenantId
ALGOSEC_TENANT_ID='<ALGOSEC_TENANT_ID>'
#Algosec Cloud onboarding URL
ALGOSEC_CLOUD_HOST='https://<HOST>'
ALGOSEC_CLOUD_ONBOARDING_URL="$ALGOSEC_CLOUD_HOST<ONBOARDING_PATH>"
# Token
TOKEN='<ONBOARDING_TOKEN>'
ADDITIONALS='<ALGOSEC_ADDITIONALS>'
ENV='<ENVIRONMENT>'
# Define a working project where a service account would be created
PROJECT_ID='<GCP_PROJECT_ID>'
PROJECT_ID=($(echo $PROJECT_ID | tr -d '\n'))
# Define a target project/folder/organization would be onboarded into Algosec Cloud
TARGET_RESOURCE='<GCP_TARGET_RESOURCE>'
TARGET_RESOURCE=($(echo $TARGET_RESOURCE | tr -d '\n'))
# Service account definitions
SERVICE_ACCOUNT_NAME="ace-sa-$ALGOSEC_TENANT_ID"
SERVICE_ACCOUNT_DISPLAY_NAME="AlgoSec Cloud Enterprise"
SERVICE_ACCOUNT_DESCRIPTION="Service account for AlgoSec Cloud Enterprise"
SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com
Purpose: The script starts by defining several variables:
-
ALGOSEC_TENANT_ID = Algosec tenant id
-
ALGOSEC_CLOUD_HOST = The host of Algosec's APIs
-
ALGOSEC_CLOUD_ONBOARDING_URL = The URL for Algosec's onboarding API
-
TOKEN = An authentication token for the API
-
ADDITIONALS = base64-encoded login credentials
-
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 Onboarded Organizations variable
# Define associate array for onboarded organization:service-accounts list
declare -A ONBOARDED_ORGS_SERVICE_ACCOUNTS=( <GCP_ONBOARDED_ORGS_SERVICE_ACCOUNTS> )
Purpose: Define associate array for already onboarded organization:service accounts list
3. Bind resource to Service Account function definition
# Define function to bind resouce to a service-account
add_resource_access_to_service_account(){
local resource=$1
local target_resource=$2
local service_account_email=$3
local organization_id=$4
# Create custom role if it doesn't exist
local custom_role=organizations/$organization_id/roles/inheritedPolicyACViewer
gcloud iam roles describe inheritedPolicyACViewer --organization=$organization_id 2>/dev/null
if [ $? -ne 0 ]; then
echo "Creating custom role inheritedPolicyACViewer"
gcloud iam roles create inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions="compute.firewallPolicies.list,resourcemanager.folders.get,resourcemanager.organizations.get,storage.buckets.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
# Assign custom role to service-account at org level
echo "Assigning [$custom_role] 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=$custom_role 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
# Assign required roles for target resource to service-account
roles=( <SERVICE_ACCOUNT_ROLES> )
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")
if [ $? -ne 0 ]; then
echo "ERROR: The onboarding process has failed while adding roles — please ensure you have the required permissions"
exit 1
fi
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
4. Retrieving Billing Account
# Verify billing account
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 Algosec Cloud"
exit 1
fi
Purpose: Making sure Billing Account is enabled before Onboarding process begins
5. Retrieving Organization (Target is Organization)
# Find target resource type and organization
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 "Algosec Cloud 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
else
for orgId in "${ORGANIZATION_IDS[@]}"; do
if [[ $orgId == $TARGET_RESOURCE ]]; then
ORGANIZATION_ID=$orgId
RESOURCE="organizations"
RESOURCE_TYPE="organization"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: 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
6. Retrieve Organization (Target is Folder/Project)
# In case target resource isn't an organization
if [ -z "$TARGET" ];then
gcloud resource-manager folders describe $TARGET_RESOURCE 2>/dev/null
if [ $? -eq 0 ]; then
RESOURCE="resource-manager folders"
RESOURCE_TYPE="folder"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: 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
# In case target resource isn't a folder
gcloud projects describe $TARGET_RESOURCE 2>/dev/null
if [ $? -eq 0 ]; then
RESOURCE="projects"
RESOURCE_TYPE="project"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: 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.
7. Retrieve Organization (Access Project)
# Find organization for access project id
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 retrieve 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.
8. Initialize Project
# Verify project id
gcloud projects describe $PROJECT_ID 2>/dev/null
if [ $? -ne 0 ]; then
echo "ERROR: Cannot find project with ID $PROJECT_ID"
exit 1
fi
# Verify project service-accounts
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.
9. Existing Service Accounts
# Update existing Algosec Cloud service-account of project or delete if service-account is from another project in the organization
echo "Finding existing service-accounts for Algosec Cloud"
# Check if access organization is already known
if [[ -v ONBOARDED_ORGS_SERVICE_ACCOUNTS["$ACCESS_ORGANIZATION_ID"] ]]; then
ONBOARDED_SA=${ONBOARDED_ORGS_SERVICE_ACCOUNTS["$ACCESS_ORGANIZATION_ID"]}
# Update service-account if it already exists
if [[ "$ONBOARDED_SA" == "$SERVICE_ACCOUNT_EMAIL" ]]; then
echo "Algosec Cloud service-account $ONBOARDED_SA for access project id $PROJECT_ID already exists"
echo "Binding service-account: $ONBOARDED_SA to resource: ${RESOURCE%s} $TARGET_RESOURCE"
add_resource_access_to_service_account $RESOURCE $TARGET_RESOURCE $ONBOARDED_SA $ORGANIZATION_ID
echo "Success: ${RESOURCE%s} $TARGET_RESOURCE onboarded to Algosec Cloud successfully. The onboarding process may take up to several hours"
exit 0
else
# Delete access organization service-account from another project
# Extract project id from SA email
SA_PROJECT_ID="${ONBOARDED_SA#*@}"
SA_PROJECT_ID="${SA_PROJECT_ID%%.*}"
gcloud iam service-accounts list --project $SA_PROJECT_ID --format="value(email)" | grep $ONBOARDED_SA
if [ $? -eq 0 ]; then
echo "Deleting old service-account [$ONBOARDED_SA]"
gcloud config set project $SA_PROJECT_ID
gcloud iam service-accounts delete $ONBOARDED_SA --quiet
fi
fi
else
echo "No existing service-account found for ${ACCESS_ORGANIZATION_ID}"
fi
Purpose: Checks for existing ACE service accounts under access project and if present will be attached to selected Target Resource else old service account will be cleaned up.
10. Echo Information about the Target Resource
echo "Preparing to onboard the target resource [$TARGET]"
Purpose: Provides information about the target resource being onboarded, for logging purposes
11. Create Service Account
# Service-account creation
# Delete service-account if it already exists before creating
gcloud config set project $PROJECT_ID
gcloud iam service-accounts list --project $PROJECT_ID --format="value(email)" | grep $SERVICE_ACCOUNT_EMAIL
if [ $? -eq 0 ]; then
echo "Algosec Cloud service-account already exists ${SERVICE_ACCOUNT_EMAIL}. Deleting it."
gcloud iam service-accounts delete $SERVICE_ACCOUNT_EMAIL --quiet
sleep 3
fi
# Create service-account
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
sleep 3
Purpose: Creates a new service account for ACE if it doesn't exist
12. Verify service account created
# Verify service account was created
gcloud iam service-accounts describe "$SERVICE_ACCOUNT_EMAIL" 1>/dev/null
if [ $? -ne 0 ]; then
echo "ERROR: Service account does not exist after creation attempt."
exit 1
fi
Purpose: Verify the service account was created successfuly
13. Generate service account key
# Create service-account key
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
SERVICE_ACCOUNT_KEY=$(cat $SERVICE_ACCOUNT_NAME.json | base64 | tr -d \\n )
Purpose: Generate a key for the service account, and store value in a variable
14. Binding resource to Service Account
# Add roles to resource for service-account
add_resource_access_to_service_account $RESOURCE $TARGET_RESOURCE $SERVICE_ACCOUNT_EMAIL $ORGANIZATION_ID
Purpose: Invoke function to bind target resource to the Service Account
15. Service Account Activation
# Activate service account
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
16. Onboarding Call
# Send service-account details to Algosec Cloud
response=$(curl -X POST "$ALGOSEC_CLOUD_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 ACE onboarding URL with the service account key and other details.
17. Response Handling
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.