Inside the GCP Onboarding Script
The GCP onboarding script automates the onboarding of a GCP project, folder, or organization into ACE. It provisions the required GCP resources and IAM permissions, enabling AlgoSec to integrate with and monitor the environment. The Cloud App Analyzer deployment is fully automated through Google Cloud Functions.
This page documents each section of the script to help users understand its purpose and functionality.
There are two scripts available.
-
For ACE with license for Cloud App Analyzer
-
For ACE without license for Cloud App Analyzer
#!/bin/bash
# Capture pipe failures
set -o pipefail
#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
# Define associate array for onboarded organization:service-accounts list
declare -A ONBOARDED_ORGS_SERVICE_ACCOUNTS=( <GCP_ONBOARDED_ORGS_SERVICE_ACCOUNTS> )
# Define function to print error message and exit
print_error_and_exit(){
local error_message=$1
echo "ERROR: $error_message"
echo "ERROR: The onboarding process has failed — please review the error message above for details and verify your GCP configuration or permissions"
exit 1
}
# 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
#----------------------------------------------------
# Cloud Network Security custom role
local cns_role=organizations/$organization_id/roles/inheritedPolicyACViewer
local cns_permissions="compute.firewallPolicies.list,resourcemanager.folders.get,resourcemanager.organizations.get,storage.buckets.list"
echo "Checking if custom role $cns_role exists..."
local cns_role_exists=$(gcloud iam roles describe inheritedPolicyACViewer --organization=$organization_id 2>&1)
if [[ $? -eq 0 ]]; then
echo "Custom role $cns_role exists. Updating permissions..."
local cns_update_role=$(
gcloud iam roles update inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions=$cns_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to update custom role: $cns_update_role"
fi
echo "Custom role $cns_role updated successfully."
elif [[ $cns_role_exists == *NOT_FOUND* ]]; then
echo "Custom role not found. Creating custom role $cns_role..."
local cns_create_role=$(
gcloud iam roles create inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions=$cns_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create custom role: $cns_create_role"
fi
echo "Custom role $cns_role created successfully."
else
print_error_and_exit "Failed to check if custom role exists: $cns_role_exists"
fi
# Assign custom role to service-account at org level
echo "Assigning [$cns_role] role to service-account [$service_account_email] at organization level [$organization_id]..."
local cns_assign_role=$(gcloud organizations add-iam-policy-binding $organization_id --member=serviceAccount:$service_account_email --role=$cns_role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to assign role: $cns_assign_role"
fi
echo "Custom role [$cns_role] assigned successfully to service-account [$service_account_email]."
# Cloud App Analyzer custom role
local caa_role=organizations/$organization_id/roles/appAnalyzerOrgLevelViewer
local caa_permissions="essentialcontacts.contacts.list,essentialcontacts.contacts.get"
echo "Checking if custom role $caa_role exists..."
local caa_role_exists=$(gcloud iam roles describe appAnalyzerOrgLevelViewer --organization=$organization_id 2>&1)
if [[ $? -eq 0 ]]; then
echo "Custom role $caa_role exists. Updating permissions..."
local caa_update_role=$(
gcloud iam roles update appAnalyzerOrgLevelViewer --organization=$organization_id \
--title="Cloud App Analyzer Org Viewer" --description="Org level viewer role for Algosec Cloud App Analyzer" \
--permissions=$caa_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to update custom role: $caa_update_role"
fi
echo "Custom role $caa_role updated successfully."
elif [[ $caa_role_exists == *NOT_FOUND* ]]; then
echo "Custom role not found. Creating custom role $caa_role..."
local caa_create_role=$(
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=$caa_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create custom role: $caa_create_role"
fi
echo "Custom role $caa_role created successfully."
else
print_error_and_exit "Failed to check if custom role exists: $caa_role_exists"
fi
# Assign custom role to service-account at org level
echo "Assigning [$caa_role] role to service-account [$service_account_email] at organization level [$organization_id]..."
local caa_assign_role=$(gcloud organizations add-iam-policy-binding $organization_id --member=serviceAccount:$service_account_email --role=$caa_role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to assign role: $caa_assign_role"
fi
echo "Custom role [$caa_role] assigned successfully to service-account [$service_account_email]"
#----------------------------------------------------
# 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]"
role_assign=$(gcloud $resource add-iam-policy-binding $target_resource --member=serviceAccount:$service_account_email --role=roles/$role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to add role [roles/$role] to ${resource%s} $target_resource: $role_assign"
fi
done
}
# Verify billing account
echo "Verifying billing account..."
BILLING_OUTPUT=$(gcloud alpha billing accounts list --filter=open=true --format json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to list billing accounts: $BILLING_OUTPUT"
fi
BILLING_ACCOUNTS=$(echo "$BILLING_OUTPUT" | jq 'length')
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
echo "Billing account verified."
# Find target resource type and organization
echo "Searching for target resource $TARGET_RESOURCE..."
ORGS_LIST=$(gcloud organizations list --format json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to list organizations: $ORGS_LIST"
fi
ORGANIZATION_IDS=($(echo "$ORGS_LIST" | 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
# In case target resource isn't an organization
if [[ -z "$TARGET" ]];then
gcloud resource-manager folders describe $TARGET_RESOURCE &>/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]"
FOLDER_ORG=$(gcloud resource-manager folders get-ancestors-iam-policy $TARGET_RESOURCE --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given folder id: $FOLDER_ORG"
fi
ORGANIZATION_ID=$(echo "$FOLDER_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ORGANIZATION_ID] found for Folder Id [$TARGET_RESOURCE]"
else
# In case target resource isn't a folder
gcloud projects describe $TARGET_RESOURCE &>/dev/null
if [[ $? -eq 0 ]]; then
RESOURCE="projects"
RESOURCE_TYPE="project"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: Project [$TARGET_RESOURCE]"
PROJECT_ORG=$(gcloud projects get-ancestors $TARGET_RESOURCE --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given project id: $PROJECT_ORG"
fi
ORGANIZATION_ID=$(echo "$PROJECT_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ORGANIZATION_ID] found for Project Id [$TARGET_RESOURCE]"
else
print_error_and_exit "The target resource [$TARGET_RESOURCE] wasn't found or the user has no permission to work with it"
fi
fi
fi
# Verify project id
echo "Verifying access project $PROJECT_ID..."
ACCESS_PROJECT=$(gcloud projects describe $PROJECT_ID 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to find or access project $PROJECT_ID: $ACCESS_PROJECT"
fi
echo "Access project $PROJECT_ID verified."
# Find organization for access project id
echo "Finding organization for access project..."
ACCESS_PROJECT_ORG=$(gcloud projects get-ancestors $PROJECT_ID --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given access project id: $ACCESS_PROJECT_ORG"
fi
ACCESS_ORGANIZATION_ID=$(echo "$ACCESS_PROJECT_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ACCESS_ORGANIZATION_ID] found for Access Project Id [$PROJECT_ID]"
# Update existing Algosec Cloud service-account of project or delete if service-account is from another project in the organization
echo "Searching for 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
SA_PROJECT_ID=$(gcloud iam service-accounts describe $ONBOARDED_SA --format="value(projectId)" 2>&1)
if [[ $? -eq 0 ]]; then
echo "Found another Algosec Cloud service-account [$ONBOARDED_SA] for access organization [$ACCESS_ORGANIZATION_ID]"
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
echo "Preparing to onboard the target resource [$TARGET]"
# Service-account creation
# Delete service-account if it already exists before creating
gcloud config set project $PROJECT_ID 1>/dev/null
DESCRIBE_SA=$(gcloud iam service-accounts describe $SERVICE_ACCOUNT_EMAIL 2>&1)
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]..."
CREATE_SA=$(gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME --description="$SERVICE_ACCOUNT_DESCRIPTION" --display-name="$SERVICE_ACCOUNT_DISPLAY_NAME" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create service-account: $CREATE_SA"
fi
sleep 3
# Verify service account was created
VERIFY_SA=$(gcloud iam service-accounts describe "$SERVICE_ACCOUNT_EMAIL" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to verify service-account creation: $VERIFY_SA"
fi
echo "Service account [$SERVICE_ACCOUNT_NAME] created successfully."
# Create service-account key
echo "Creating a key for the service account [$SERVICE_ACCOUNT_NAME]..."
rm -f $SERVICE_ACCOUNT_NAME.json
CREATE_SA_KEY=$(gcloud iam service-accounts keys create "$SERVICE_ACCOUNT_NAME.json" --iam-account=$SERVICE_ACCOUNT_EMAIL 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create a key for the service-account: $CREATE_SA_KEY"
fi
SERVICE_ACCOUNT_KEY=$(cat $SERVICE_ACCOUNT_NAME.json | base64 | tr -d \\n )
echo "Key for the service account [$SERVICE_ACCOUNT_NAME] created successfully."
# Add roles to resource for service-account
add_resource_access_to_service_account "$RESOURCE" "$TARGET_RESOURCE" "$SERVICE_ACCOUNT_EMAIL" "$ORGANIZATION_ID"
#----------------------------------------------------
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
}
#----------------------------------------------------
#----------------------------------------------------
#GCP region
REGION='<GCP_REGION>'
#Prevasio host
PREVASIO_HOST='<PREVASIO_HOST>'
#Prevasio source code location
SOURCES_URL='<PREVASIO_SOURCES_URL>'
HASH=$(echo -n "$ALGOSEC_TENANT_ID" | cut -c1-5)
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
}
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
}
create_project_resources() {
create_attestor $1
create_application $1 $REGION $2
}
mkdir -p prevasio-onboarding && rm -rf prevasio-onboarding/*
cd prevasio-onboarding
echo "Downloading Prevasio application resources"
wget -O sources.zip --header "user-agent: algosec/1.0" "${SOURCES_URL}?tenant_id=${ALGOSEC_TENANT_ID}"
unzip sources.zip
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
create_project_resources "$project" "$ORGANIZATION_ID"
done
cd ..
rm -rf prevasio-onboarding
#----------------------------------------------------
#----------------------------------------------------
get_project_ids "$RESOURCE_TYPE" "$TARGET_RESOURCE"
for project in "${PROJECT_IDS[@]}" ; do
echo "Setting current project to [$project]"
gcloud config set project "$project"
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 --condition=None 1>/dev/null
done
#----------------------------------------------------
# Activate service account
echo "Activating service account key [$SERVICE_ACCOUNT_NAME]..."
gcloud config set account $SERVICE_ACCOUNT_EMAIL 1>/dev/null
ACTIVATE_SA=$(gcloud auth activate-service-account --project=$PROJECT_ID --key-file $SERVICE_ACCOUNT_NAME.json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to activate service-account key: $ACTIVATE_SA"
fi
echo "Service account key [$SERVICE_ACCOUNT_NAME] activated successfully."
# Send service-account details to Algosec Cloud
echo "Sending 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" } }')
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
Script Sections
1. Shell Options and Initialize Variables
#!/bin/bash
# Capture pipe failures
set -o pipefail
#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 begins by enabling set -o pipefail, which causes a pipeline
to return the exit code of the first failed command rather than the last one.
It then defines the core variables used throughout the script:
ALGOSEC_TENANT_ID— AlgoSec tenant IDALGOSEC_CLOUD_HOST— The host of AlgoSec's APIsALGOSEC_CLOUD_ONBOARDING_URL— The URL for AlgoSec's onboarding APITOKEN— An authentication token for the APIADDITIONALS— Base64-encoded login credentialsENV— Deployment environment (stage/prod)PROJECT_ID— The GCP project in which the service account will be created; trailing newlines are stripped withtrTARGET_RESOURCE— The GCP project, folder, or organization to be onboarded into ACE; trailing newlines are stripped withtrSERVICE_ACCOUNT_EMAIL— Derived email address of the service account that 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: Declares an associative array that maps previously onboarded organization IDs to their corresponding ACE service account email addresses. This is populated at runtime with data from AlgoSec Cloud and allows the script to detect and reuse or clean up existing service accounts for the same organization, rather than creating duplicates.
3. Print Error and Exit Utility Function
# Define function to print error message and exit
print_error_and_exit(){
local error_message=$1
echo "ERROR: $error_message"
echo "ERROR: The onboarding process has failed — please review the error message above for details and verify your GCP configuration or permissions"
exit 1
}
Purpose: A centralized error-handling utility used throughout the script. Any step that encounters a failure calls this function with a descriptive context message. It prints two lines — the specific error detail and a standard guidance message — then exits the script immediately. Centralizing error output this way ensures consistent messaging and avoids repeating boilerplate error text in every individual step.
4. Bind Resource to Service Account Function
# Define function to bind resource 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
# Cloud Network Security custom role
local cns_role=organizations/$organization_id/roles/inheritedPolicyACViewer
local cns_permissions="compute.firewallPolicies.list,resourcemanager.folders.get,resourcemanager.organizations.get,storage.buckets.list"
echo "Checking if custom role $cns_role exists..."
local cns_role_exists=$(gcloud iam roles describe inheritedPolicyACViewer --organization=$organization_id 2>&1)
if [[ $? -eq 0 ]]; then
echo "Custom role $cns_role exists. Updating permissions..."
local cns_update_role=$(
gcloud iam roles update inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions=$cns_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to update custom role: $cns_update_role"
fi
echo "Custom role $cns_role updated successfully."
elif [[ $cns_role_exists == *NOT_FOUND* ]]; then
echo "Custom role not found. Creating custom role $cns_role..."
local cns_create_role=$(
gcloud iam roles create inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions=$cns_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create custom role: $cns_create_role"
fi
echo "Custom role $cns_role created successfully."
else
print_error_and_exit "Failed to check if custom role exists: $cns_role_exists"
fi
# Assign custom role to service-account at org level
echo "Assigning [$cns_role] role to service-account [$service_account_email] at organization level [$organization_id]..."
local cns_assign_role=$(gcloud organizations add-iam-policy-binding $organization_id --member=serviceAccount:$service_account_email --role=$cns_role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to assign role: $cns_assign_role"
fi
echo "Custom role [$cns_role] assigned successfully to service-account [$service_account_email]."
# Cloud App Analyzer custom role
local caa_role=organizations/$organization_id/roles/appAnalyzerOrgLevelViewer
local caa_permissions="essentialcontacts.contacts.list,essentialcontacts.contacts.get"
echo "Checking if custom role $caa_role exists..."
local caa_role_exists=$(gcloud iam roles describe appAnalyzerOrgLevelViewer --organization=$organization_id 2>&1)
if [[ $? -eq 0 ]]; then
echo "Custom role $caa_role exists. Updating permissions..."
local caa_update_role=$(
gcloud iam roles update appAnalyzerOrgLevelViewer --organization=$organization_id \
--title="Cloud App Analyzer Org Viewer" --description="Org level viewer role for Algosec Cloud App Analyzer" \
--permissions=$caa_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to update custom role: $caa_update_role"
fi
echo "Custom role $caa_role updated successfully."
elif [[ $caa_role_exists == *NOT_FOUND* ]]; then
echo "Custom role not found. Creating custom role $caa_role..."
local caa_create_role=$(
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=$caa_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create custom role: $caa_create_role"
fi
echo "Custom role $caa_role created successfully."
else
print_error_and_exit "Failed to check if custom role exists: $caa_role_exists"
fi
# Assign custom role to service-account at org level
echo "Assigning [$caa_role] role to service-account [$service_account_email] at organization level [$organization_id]..."
local caa_assign_role=$(gcloud organizations add-iam-policy-binding $organization_id --member=serviceAccount:$service_account_email --role=$caa_role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to assign role: $caa_assign_role"
fi
echo "Custom role [$caa_role] assigned successfully to service-account [$service_account_email]"
# 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]"
role_assign=$(gcloud $resource add-iam-policy-binding $target_resource --member=serviceAccount:$service_account_email --role=roles/$role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to add role [roles/$role] to ${resource%s} $target_resource: $role_assign"
fi
done
}
Purpose: The core IAM setup function. It is called after the service account is created and binds the required permissions to the target GCP resource. The function handles two organization-level custom roles and then assigns all standard roles to the target resource:
inheritedPolicyACViewer— Required by Cloud Network Security (CNS) to read hierarchical firewall policies, folders, organizations, and storage buckets. Uses a create-or-update pattern: if the role already exists its permissions are updated; if it does not exist it is created; any other error causes the script to exit. This ensures the role is always in the correct state across re-runs.appAnalyzerOrgLevelViewer— Required by Cloud App Analyzer to read essential contacts at the organization level. Same create-or-update pattern as above.- Standard roles — A set of roles (e.g.,
compute.viewer,iam.serviceAccountViewer) are assigned to the target resource (project, folder, or organization) scoped to the service account.
5. Retrieve Billing Account
# Verify billing account
echo "Verifying billing account..."
BILLING_OUTPUT=$(gcloud alpha billing accounts list --filter=open=true --format json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to list billing accounts: $BILLING_OUTPUT"
fi
BILLING_ACCOUNTS=$(echo "$BILLING_OUTPUT" | jq 'length')
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
echo "Billing account verified."
Purpose: Verifies that at least one open billing account is
available before proceeding. The CLI output is captured so that if the gcloud command itself fails (e.g., due
to a permissions issue), the error detail is passed to print_error_and_exit. If the command
succeeds but returns no billing accounts, a clear error is printed explaining
that billing must be enabled for the GCP services required by AlgoSec Cloud.
6. Retrieve Organization (Target is Organization)
# Find target resource type and organization
echo "Searching for target resource $TARGET_RESOURCE..."
ORGS_LIST=$(gcloud organizations list --format json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to list organizations: $ORGS_LIST"
fi
ORGANIZATION_IDS=($(echo "$ORGS_LIST" | 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: Attempts to match TARGET_RESOURCE against the list of GCP organizations
accessible to the authenticated user. The organization list is captured in ORGS_LIST so that CLI errors (e.g.,
insufficient permissions) are reported via print_error_and_exit
rather than silently failing. If the target is found to be an organization, ORGANIZATION_ID, RESOURCE, RESOURCE_TYPE, and TARGET
are set for use in later steps. If no organizations exist at all, an
explanatory error is printed since AlgoSec Cloud requires Google Workspace or
Cloud Identity accounts with access to the GCP resource hierarchy.
7. Retrieve Organization (Target is Folder or Project)
# In case target resource isn't an organization
if [[ -z "$TARGET" ]];then
gcloud resource-manager folders describe $TARGET_RESOURCE &>/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]"
FOLDER_ORG=$(gcloud resource-manager folders get-ancestors-iam-policy $TARGET_RESOURCE --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given folder id: $FOLDER_ORG"
fi
ORGANIZATION_ID=$(echo "$FOLDER_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ORGANIZATION_ID] found for Folder Id [$TARGET_RESOURCE]"
else
# In case target resource isn't a folder
gcloud projects describe $TARGET_RESOURCE &>/dev/null
if [[ $? -eq 0 ]]; then
RESOURCE="projects"
RESOURCE_TYPE="project"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: Project [$TARGET_RESOURCE]"
PROJECT_ORG=$(gcloud projects get-ancestors $TARGET_RESOURCE --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given project id: $PROJECT_ORG"
fi
ORGANIZATION_ID=$(echo "$PROJECT_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ORGANIZATION_ID] found for Project Id [$TARGET_RESOURCE]"
else
print_error_and_exit "The target resource [$TARGET_RESOURCE] wasn't found or the user has no permission to work with it"
fi
fi
fi
Purpose: Runs only if TARGET_RESOURCE
was not identified as an organization in section 6. It probes the resource as a
folder first (using &>/dev/null
to suppress all output), then as a project. Whichever matches first sets RESOURCE, RESOURCE_TYPE, and TARGET.
The organization ID is then retrieved by walking the resource's ancestors via get-ancestors-iam-policy (for folders)
or get-ancestors (for
projects). All ancestor lookup outputs are captured and passed to print_error_and_exit on failure. If the
target resource cannot be resolved as any known type, the script exits with a
clear error.
8. Verify Access Project
# Verify project id
echo "Verifying access project $PROJECT_ID..."
ACCESS_PROJECT=$(gcloud projects describe $PROJECT_ID 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to find or access project $PROJECT_ID: $ACCESS_PROJECT"
fi
echo "Access project $PROJECT_ID verified."
Purpose: Confirms that the access project (PROJECT_ID) — the project in which the
ACE service account will be created — exists and is accessible to the
authenticated user. The CLI output is captured so that error details are
included in the failure message via print_error_and_exit.
This is a dedicated verification step.
9. Find Organization for Access Project
# Find organization for access project id
echo "Finding organization for access project..."
ACCESS_PROJECT_ORG=$(gcloud projects get-ancestors $PROJECT_ID --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given access project id: $ACCESS_PROJECT_ORG"
fi
ACCESS_ORGANIZATION_ID=$(echo "$ACCESS_PROJECT_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ACCESS_ORGANIZATION_ID] found for Access Project Id [$PROJECT_ID]"
Purpose: Retrieves the organization ID of the access
project by walking its resource hierarchy ancestors. The result is stored in ACCESS_ORGANIZATION_ID, which is used
later to look up any existing AlgoSec service accounts registered for the same
organization and to send the correct organization ID in the onboarding API
call. CLI output is captured and passed to print_error_and_exit
on failure.
10. Existing Service Accounts Check
# Update existing Algosec Cloud service-account of project or delete if service-account is from another project in the organization
echo "Searching for 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
SA_PROJECT_ID=$(gcloud iam service-accounts describe $ONBOARDED_SA --format="value(projectId)" 2>&1)
if [[ $? -eq 0 ]]; then
echo "Found another Algosec Cloud service-account [$ONBOARDED_SA] for access organization [$ACCESS_ORGANIZATION_ID]"
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 whether an ACE service account is already registered for the access organization. Three outcomes are possible:
- Same service account already exists in the same project — The target resource is bound to the existing service account directly, and the script exits successfully without creating a new one.
- A different service account exists (from another
project in the same organization) — The stale service account is
deleted to make way for the new one. Its project ID is retrieved using
gcloud iam service-accounts describe --format="value(projectId)" - No existing service account — Proceeds to create a new one in the next step.
11. Echo Information About the Target Resource
echo "Preparing to onboard the target resource [$TARGET]"
Purpose: Prints a log line confirming the resolved target resource being onboarded. This is used for visibility and traceability in the terminal session.
12. Create Service Account
# Service-account creation
# Delete service-account if it already exists before creating
gcloud config set project $PROJECT_ID 1>/dev/null
DESCRIBE_SA=$(gcloud iam service-accounts describe $SERVICE_ACCOUNT_EMAIL 2>&1)
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]..."
CREATE_SA=$(gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME --description="$SERVICE_ACCOUNT_DESCRIPTION" --display-name="$SERVICE_ACCOUNT_DISPLAY_NAME" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create service-account: $CREATE_SA"
fi
sleep 3
Purpose: Creates the ACE service account in the access
project. Before creation, the script checks whether the account already exists
using gcloud iam service-accounts describe.
There are three outcomes:
- Exists — The old service account is deleted before the new one is created.
- Does not exist — Proceeds directly to creation.
A 3-second sleep after both deletion and creation allows GCP's IAM system time to propagate the change before the next step verifies the account.
13. Verify Service Account Created
# Verify service account was created
VERIFY_SA=$(gcloud iam service-accounts describe "$SERVICE_ACCOUNT_EMAIL" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to verify service-account creation: $VERIFY_SA"
fi
echo "Service account [$SERVICE_ACCOUNT_NAME] created successfully."
Purpose: Confirms the service account was successfully
created by describing it immediately after creation. If the account cannot be
found or described, the captured error output is passed to print_error_and_exit. A success log line
is printed to confirm the account is ready for the next steps.
14. Create Service Account Key
# Create service-account key
echo "Creating a key for the service account [$SERVICE_ACCOUNT_NAME]..."
rm -f $SERVICE_ACCOUNT_NAME.json
CREATE_SA_KEY=$(gcloud iam service-accounts keys create "$SERVICE_ACCOUNT_NAME.json" --iam-account=$SERVICE_ACCOUNT_EMAIL 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create a key for the service-account: $CREATE_SA_KEY"
fi
SERVICE_ACCOUNT_KEY=$(cat $SERVICE_ACCOUNT_NAME.json | base64 | tr -d \\n )
echo "Key for the service account [$SERVICE_ACCOUNT_NAME] created successfully."
Purpose: Generates a JSON key for the service account and
stores the base64-encoded contents in SERVICE_ACCOUNT_KEY
for use in the onboarding API call. Any pre-existing key file is removed before
creation to prevent stale data from being used. The gcloud command output is captured and
passed to print_error_and_exit
on failure. A trailing newline stripped with tr
-d \\n ensures the base64 string can be safely embedded in JSON.
15. Bind 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: Invokes the add_resource_access_to_service_account
function defined in section 4 to assign the required IAM roles to the target
resource. All arguments are now quoted for safety. This call covers both the
organization-level custom roles (inheritedPolicyACViewer,
appAnalyzerOrgLevelViewer)
and the standard roles at the target resource scope.
16. Initialize PROJECT_IDS Array and get_project_ids Function (Optional - Cloud App Analyzer license)
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: Initializes the PROJECT_IDS
array and defines the get_project_ids
function used to populate it. When the target resource is a project, the
project ID is added directly. When it is a folder or organization, all
accessible GCP projects are listed and each one's ancestor chain is inspected
to determine whether it falls under the target resource — those that do are
added to PROJECT_IDS. This
array is later iterated to deploy Cloud App Analyzer resources and apply GKE
roles to every relevant project.
17. Initialize Cloud App Analyzer Variables (Optional - Onboard CM Mitigation enabled)
#GCP region
REGION='<GCP_REGION>'
#Prevasio host
PREVASIO_HOST='<PREVASIO_HOST>'
#Prevasio source code location
SOURCES_URL='<PREVASIO_SOURCES_URL>'
HASH=$(echo -n "$ALGOSEC_TENANT_ID" | cut -c1-5)
Purpose: Defines variables used exclusively by the Cloud App Analyzer deployment steps:
REGION— The GCP region where Cloud App Analyzer resources (Cloud Functions, schedulers, etc.) will be createdPREVASIO_HOST— The host of AlgoSec's Cloud App Analyzer APIsSOURCES_URL— The URL from which the Cloud App Analyzer application source files are downloadedHASH— A short unique identifier derived from the first 5 characters ofALGOSEC_TENANT_ID, used to namespace all Cloud App Analyzer resources within a project to avoid naming conflicts
18. Create Application Function (Optional - Onboard CM Mitigation enabled)
create_application() {
PROJECT_NUMBER=$(gcloud projects describe "${1}" --format="value(projectNumber)")
echo "Creating secrets [prevasio-$HASH-org-id, prevasio-$HASH-host, prevasio-$HASH-additionals, prevasio-$HASH-algosec-cloud-host] 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: Deploys all Cloud App Analyzer application components to a single GCP project. It performs the following steps:
- Create Secret Manager secrets — Four
secrets are created (
org-id,host,additionals,algosec-cloud-host) and the default Compute Engine service account (${PROJECT_NUMBER}[email protected]) is grantedsecretmanager.secretAccessoron each. The compute service account email format has been corrected in this version. - Deploy Cloud Functions — Three functions
are deployed using gen2 runtime (
python310): prevasio-$HASH-events-forwarder— Forwards Artifact Registry push events to AlgoSecprevasio-$HASH-cloud-run-scanner— Scans Cloud Run services; triggered on a schedule via Cloud Scheduler every 6 hoursprevasio-$HASH-image-attestation-creator— Creates Binary Authorization attestations for signed images- Publish existing images — Iterates all
Artifact Registry repositories and images in every region, publishing them
in batches of 10 to the
prevasio-$HASH-images-to-signPub/Sub topic so existing images are attested immediately. - Set up Pub/Sub — Creates the
gcrtopic (if not already present) and a subscription that pushes Artifact Registry events to the events-forwarder function.
19. Create Attestor Function (Optional - Onboard CM Mitigation enabled)
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:\n'
printf ' enforcementMode: ENFORCED_BLOCK_AND_AUDIT_LOG\n'
printf ' evaluationMode: REQUIRE_ATTESTATION\n'
printf ' requireAttestationsBy:'
for attestor in "${ATTESTORS[@]}"; do
printf '\n - %s' "$attestor"
done
printf '\nglobalPolicyEvaluationMode: 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: Sets up a Binary Authorization attestor in the specified GCP project, enabling cryptographic enforcement of image signing before deployment. The steps are:
- Create a Container Analysis note — A note is created via the Container Analysis REST API to serve as the attestation authority's backing resource.
- Create the attestor — A Binary Authorization attestor is created referencing the note.
- Grant IAM access — The Binary
Authorization service account is granted
containeranalysis.notes.occurrences.vieweron the note so it can verify attestations. - Create a KMS key — A Cloud KMS asymmetric
signing key (
ec-sign-p256-sha256) is created and its public key is registered with the attestor. - Update the Binary Authorization policy
(conditional) — If
IMAGE_LOCKING_ENABLEDis true, the project's Binary Authorization policy is exported, the new attestor is appended to therequireAttestationsBylist, and the policy is reimported with enforcement mode set toENFORCED_BLOCK_AND_AUDIT_LOG.
20. Create Project Resources Function (Optional - Onboard CM Mitigation enabled)
create_project_resources() {
create_attestor $1
create_application $1 $REGION $2
}
Purpose: A wrapper function that orchestrates the full
Cloud App Analyzer deployment for a single project by calling create_attestor followed by create_application. It receives the
project ID as $1 and the
organization ID as $2,
passing the configured REGION
to create_application.
Separating deployment into this wrapper allows the per-project loop in section
22 to invoke all deployment steps with a single call.
21. Download and Unpack Application Sources (Optional - Onboard CM Mitigation enabled)
mkdir -p prevasio-onboarding && rm -rf prevasio-onboarding/*
cd prevasio-onboarding
echo "Downloading Prevasio application resources"
wget -O sources.zip --header "user-agent: algosec/1.0" "${SOURCES_URL}?tenant_id=${ALGOSEC_TENANT_ID}"
unzip sources.zip
Purpose: Creates a clean temporary working directory (prevasio-onboarding), then downloads the
Cloud App Analyzer source archive from SOURCES_URL,
passing the AlgoSec tenant ID as a query parameter. The user-agent: algosec/1.0 header is
included so AlgoSec's servers can identify the request origin. The archive is
extracted in place, making the Cloud Function source directories available for
the create_application
function to deploy.
22. Deploy Cloud App Analyzer to Target Projects (Optional - Onboard CM Mitigation enabled)
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
create_project_resources "$project" "$ORGANIZATION_ID"
done
Purpose: Populates PROJECT_IDS
for the target resource type and iterates over every relevant project to deploy
Cloud App Analyzer resources. For each project:
- The project is set as the active gcloud context.
- All enabled APIs are listed and compared against the
required set:
artifactregistry,cloudfunctions,cloudkms,cloudscheduler,compute,container,pubsub,secretmanager,binaryauthorization,cloudbuild,containeranalysis, andrun. If any required API is missing, a message is logged and the project is skipped withcontinuerather than failing the whole script. create_project_resourcesis called to deploy the attestor and application to the project.
23. Cleanup Resources (Optional - Onboard CM Mitigation enabled)
cd ..
rm -rf prevasio-onboarding
Purpose: Removes the temporary prevasio-onboarding working directory
along with all downloaded and extracted files (source archive, Cloud Function
source directories). This ensures no large artifacts or sensitive configuration
files are left behind on the machine running the script.
24. Apply GKE Custom Role to Target Projects (Optional - Cloud App Analyzer license)
get_project_ids "$RESOURCE_TYPE" "$TARGET_RESOURCE"
for project in "${PROJECT_IDS[@]}" ; do
echo "Setting current project to [$project]"
gcloud config set project "$project"
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 --condition=None 1>/dev/null
done
Purpose: A dedicated post-deployment pass that creates and
assigns the algosec.gke.custom.role
custom IAM role to the ACE service account in every target project. This role
grants the permissions required for AlgoSec's GKE integrations:
container.jobs.create/container.jobs.deletecontainer.namespaces.createcontainer.nodes.proxycontainer.pods.getLogs
get_project_ids is called
again to repopulate PROJECT_IDS
(since the array may have been consumed or the working directory changed in the
previous loop). The role creation uses 2>/dev/null
so that the idempotent "already exists" error on re-runs is silently
ignored. The --condition=None
flag is set on the policy binding consistent with the approach used throughout
the script.
Using a separate loop ensures GKE role assignment runs for all projects regardless of whether a project was skipped during the Cloud App Analyzer deployment phase due to missing APIs.
25. Service Account Activation
# Activate service account
echo "Activating service account key [$SERVICE_ACCOUNT_NAME]..."
gcloud config set account $SERVICE_ACCOUNT_EMAIL 1>/dev/null
ACTIVATE_SA=$(gcloud auth activate-service-account --project=$PROJECT_ID --key-file $SERVICE_ACCOUNT_NAME.json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to activate service-account key: $ACTIVATE_SA"
fi
echo "Service account key [$SERVICE_ACCOUNT_NAME] activated successfully."
Purpose: Activates the newly created service account key so
that subsequent gcloud calls
(including the onboarding API call) are authenticated as the service account.
The activation output is captured and passed to print_error_and_exit on failure. A success echo confirms
activation before the script proceeds to the onboarding call.
26. Onboarding Call
# Send service-account details to Algosec Cloud
echo "Sending 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 AlgoSec Cloud
onboarding API to register the GCP environment. The request body includes the
access organization ID, the base64-encoded service account key, and the access
project ID. A log line is printed before the request is sent. The --silent flag suppresses curl's default progress output so that
only the JSON response body is captured in response
for processing in the next step.
27. 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: Parses the JSON response from the onboarding API
to extract the status code
and message from the initialOnboardResult field. If the
status is 200, a success
message is displayed along with a prompt to close the terminal. Any other
status is treated as a failure and the error message from the API is displayed.
The comparison uses the [[
double-bracket construct for more robust string matching.
1. Shell Options and Initialize Variables
#!/bin/bash
# Capture pipe failures
set -o pipefail
#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 begins by enabling set -o pipefail, which causes a pipeline
to return the exit code of the first failed command rather than the last one.
It then defines the core variables used throughout the script:
ALGOSEC_TENANT_ID— AlgoSec tenant IDALGOSEC_CLOUD_HOST— The host of AlgoSec's APIsALGOSEC_CLOUD_ONBOARDING_URL— The URL for AlgoSec's onboarding APITOKEN— An authentication token for the APIADDITIONALS— Base64-encoded login credentialsENV— Deployment environment (stage/prod)PROJECT_ID— The GCP project in which the service account will be created; trailing newlines are stripped withtrTARGET_RESOURCE— The GCP project, folder, or organization to be onboarded into ACE; trailing newlines are stripped withtrSERVICE_ACCOUNT_EMAIL— Derived email address of the service account that 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: Declares an associative array that maps previously onboarded organization IDs to their corresponding ACE service account email addresses. This is populated at runtime with data from AlgoSec Cloud and allows the script to detect and reuse or clean up existing service accounts for the same organization, rather than creating duplicates.
3. Print Error and Exit Utility Function
# Define function to print error message and exit
print_error_and_exit(){
local error_message=$1
echo "ERROR: $error_message"
echo "ERROR: The onboarding process has failed — please review the error message above for details and verify your GCP configuration or permissions"
exit 1
}
Purpose: A centralized error-handling utility used throughout the script. Any step that encounters a failure calls this function with a descriptive context message. It prints two lines — the specific error detail and a standard guidance message — then exits the script immediately. Centralizing error output this way ensures consistent messaging and avoids repeating boilerplate error text in every individual step.
4. Bind Resource to Service Account Function
# Define function to bind resource 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
# Cloud Network Security custom role
local cns_role=organizations/$organization_id/roles/inheritedPolicyACViewer
local cns_permissions="compute.firewallPolicies.list,resourcemanager.folders.get,resourcemanager.organizations.get,storage.buckets.list"
echo "Checking if custom role $cns_role exists..."
local cns_role_exists=$(gcloud iam roles describe inheritedPolicyACViewer --organization=$organization_id 2>&1)
if [[ $? -eq 0 ]]; then
echo "Custom role $cns_role exists. Updating permissions..."
local cns_update_role=$(
gcloud iam roles update inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions=$cns_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to update custom role: $cns_update_role"
fi
echo "Custom role $cns_role updated successfully."
elif [[ $cns_role_exists == *NOT_FOUND* ]]; then
echo "Custom role not found. Creating custom role $cns_role..."
local cns_create_role=$(
gcloud iam roles create inheritedPolicyACViewer --organization=$organization_id \
--title="Inherited Policy Viewer Algosec Cloud" --description="Inherited Viewer Roles for Algosec Cloud" \
--permissions=$cns_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create custom role: $cns_create_role"
fi
echo "Custom role $cns_role created successfully."
else
print_error_and_exit "Failed to check if custom role exists: $cns_role_exists"
fi
# Assign custom role to service-account at org level
echo "Assigning [$cns_role] role to service-account [$service_account_email] at organization level [$organization_id]..."
local cns_assign_role=$(gcloud organizations add-iam-policy-binding $organization_id --member=serviceAccount:$service_account_email --role=$cns_role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to assign role: $cns_assign_role"
fi
echo "Custom role [$cns_role] assigned successfully to service-account [$service_account_email]."
# Cloud App Analyzer custom role
local caa_role=organizations/$organization_id/roles/appAnalyzerOrgLevelViewer
local caa_permissions="essentialcontacts.contacts.list,essentialcontacts.contacts.get"
echo "Checking if custom role $caa_role exists..."
local caa_role_exists=$(gcloud iam roles describe appAnalyzerOrgLevelViewer --organization=$organization_id 2>&1)
if [[ $? -eq 0 ]]; then
echo "Custom role $caa_role exists. Updating permissions..."
local caa_update_role=$(
gcloud iam roles update appAnalyzerOrgLevelViewer --organization=$organization_id \
--title="Cloud App Analyzer Org Viewer" --description="Org level viewer role for Algosec Cloud App Analyzer" \
--permissions=$caa_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to update custom role: $caa_update_role"
fi
echo "Custom role $caa_role updated successfully."
elif [[ $caa_role_exists == *NOT_FOUND* ]]; then
echo "Custom role not found. Creating custom role $caa_role..."
local caa_create_role=$(
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=$caa_permissions 2>&1
)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create custom role: $caa_create_role"
fi
echo "Custom role $caa_role created successfully."
else
print_error_and_exit "Failed to check if custom role exists: $caa_role_exists"
fi
# Assign custom role to service-account at org level
echo "Assigning [$caa_role] role to service-account [$service_account_email] at organization level [$organization_id]..."
local caa_assign_role=$(gcloud organizations add-iam-policy-binding $organization_id --member=serviceAccount:$service_account_email --role=$caa_role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to assign role: $caa_assign_role"
fi
echo "Custom role [$caa_role] assigned successfully to service-account [$service_account_email]"
# 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]"
role_assign=$(gcloud $resource add-iam-policy-binding $target_resource --member=serviceAccount:$service_account_email --role=roles/$role --condition=None 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to add role [roles/$role] to ${resource%s} $target_resource: $role_assign"
fi
done
}
Purpose: The core IAM setup function. It is called after the service account is created and binds the required permissions to the target GCP resource. The function handles two organization-level custom roles and then assigns all standard roles to the target resource:
inheritedPolicyACViewer— Required by Cloud Network Security (CNS) to read hierarchical firewall policies, folders, organizations, and storage buckets. Uses a create-or-update pattern: if the role already exists its permissions are updated; if it does not exist it is created; any other error causes the script to exit. This ensures the role is always in the correct state across re-runs.appAnalyzerOrgLevelViewer— Required by Cloud App Analyzer to read essential contacts at the organization level. Same create-or-update pattern as above.- Standard roles — A set of roles (e.g.,
compute.viewer,iam.serviceAccountViewer) are assigned to the target resource (project, folder, or organization) scoped to the service account.
5. Retrieve Billing Account
# Verify billing account
echo "Verifying billing account..."
BILLING_OUTPUT=$(gcloud alpha billing accounts list --filter=open=true --format json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to list billing accounts: $BILLING_OUTPUT"
fi
BILLING_ACCOUNTS=$(echo "$BILLING_OUTPUT" | jq 'length')
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
echo "Billing account verified."
Purpose: Verifies that at least one open billing account is
available before proceeding. The CLI output is captured so that if the gcloud command itself fails (e.g., due
to a permissions issue), the error detail is passed to print_error_and_exit. If the command
succeeds but returns no billing accounts, a clear error is printed explaining
that billing must be enabled for the GCP services required by AlgoSec Cloud.
6. Retrieve Organization (Target is Organization)
# Find target resource type and organization
echo "Searching for target resource $TARGET_RESOURCE..."
ORGS_LIST=$(gcloud organizations list --format json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to list organizations: $ORGS_LIST"
fi
ORGANIZATION_IDS=($(echo "$ORGS_LIST" | 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: Attempts to match TARGET_RESOURCE against the list of GCP organizations
accessible to the authenticated user. The organization list is captured in ORGS_LIST so that CLI errors (e.g.,
insufficient permissions) are reported via print_error_and_exit
rather than silently failing. If the target is found to be an organization, ORGANIZATION_ID, RESOURCE, RESOURCE_TYPE, and TARGET
are set for use in later steps. If no organizations exist at all, an
explanatory error is printed since AlgoSec Cloud requires Google Workspace or
Cloud Identity accounts with access to the GCP resource hierarchy.
7. Retrieve Organization (Target is Folder or Project)
# In case target resource isn't an organization
if [[ -z "$TARGET" ]];then
gcloud resource-manager folders describe $TARGET_RESOURCE &>/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]"
FOLDER_ORG=$(gcloud resource-manager folders get-ancestors-iam-policy $TARGET_RESOURCE --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given folder id: $FOLDER_ORG"
fi
ORGANIZATION_ID=$(echo "$FOLDER_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ORGANIZATION_ID] found for Folder Id [$TARGET_RESOURCE]"
else
# In case target resource isn't a folder
gcloud projects describe $TARGET_RESOURCE &>/dev/null
if [[ $? -eq 0 ]]; then
RESOURCE="projects"
RESOURCE_TYPE="project"
TARGET="$RESOURCE_TYPE $TARGET_RESOURCE"
echo "Target resource found: Project [$TARGET_RESOURCE]"
PROJECT_ORG=$(gcloud projects get-ancestors $TARGET_RESOURCE --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given project id: $PROJECT_ORG"
fi
ORGANIZATION_ID=$(echo "$PROJECT_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ORGANIZATION_ID] found for Project Id [$TARGET_RESOURCE]"
else
print_error_and_exit "The target resource [$TARGET_RESOURCE] wasn't found or the user has no permission to work with it"
fi
fi
fi
Purpose: Runs only if TARGET_RESOURCE
was not identified as an organization in section 6. It probes the resource as a
folder first (using &>/dev/null
to suppress all output), then as a project. Whichever matches first sets RESOURCE, RESOURCE_TYPE, and TARGET.
The organization ID is then retrieved by walking the resource's ancestors via get-ancestors-iam-policy (for folders)
or get-ancestors (for
projects). All ancestor lookup outputs are captured and passed to print_error_and_exit on failure. If the
target resource cannot be resolved as any known type, the script exits with a
clear error.
8. Verify Access Project
# Verify project id
echo "Verifying access project $PROJECT_ID..."
ACCESS_PROJECT=$(gcloud projects describe $PROJECT_ID 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Unable to find or access project $PROJECT_ID: $ACCESS_PROJECT"
fi
echo "Access project $PROJECT_ID verified."
Purpose: Confirms that the access project (PROJECT_ID) — the project in which the
ACE service account will be created — exists and is accessible to the
authenticated user. The CLI output is captured so that error details are
included in the failure message via print_error_and_exit.
This is a dedicated verification step.
9. Find Organization for Access Project
# Find organization for access project id
echo "Finding organization for access project..."
ACCESS_PROJECT_ORG=$(gcloud projects get-ancestors $PROJECT_ID --format="json(id,type)" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to retrieve organization id for the given access project id: $ACCESS_PROJECT_ORG"
fi
ACCESS_ORGANIZATION_ID=$(echo "$ACCESS_PROJECT_ORG" | jq -r '.[] | select(.type == "organization") | .id')
echo "Organization Id [$ACCESS_ORGANIZATION_ID] found for Access Project Id [$PROJECT_ID]"
Purpose: Retrieves the organization ID of the access
project by walking its resource hierarchy ancestors. The result is stored in ACCESS_ORGANIZATION_ID, which is used
later to look up any existing AlgoSec service accounts registered for the same
organization and to send the correct organization ID in the onboarding API
call. CLI output is captured and passed to print_error_and_exit
on failure.
10. Existing Service Accounts Check
# Update existing Algosec Cloud service-account of project or delete if service-account is from another project in the organization
echo "Searching for 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
SA_PROJECT_ID=$(gcloud iam service-accounts describe $ONBOARDED_SA --format="value(projectId)" 2>&1)
if [[ $? -eq 0 ]]; then
echo "Found another Algosec Cloud service-account [$ONBOARDED_SA] for access organization [$ACCESS_ORGANIZATION_ID]"
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 whether an ACE service account is already registered for the access organization. Three outcomes are possible:
- Same service account already exists in the same project — The target resource is bound to the existing service account directly, and the script exits successfully without creating a new one.
- A different service account exists (from another
project in the same organization) — The stale service account is
deleted to make way for the new one. Its project ID is retrieved using
gcloud iam service-accounts describe --format="value(projectId)" - No existing service account — Proceeds to create a new one in the next step.
11. Echo Information About the Target Resource
echo "Preparing to onboard the target resource [$TARGET]"
Purpose: Prints a log line confirming the resolved target resource being onboarded. This is used for visibility and traceability in the terminal session.
12. Create Service Account
# Service-account creation
# Delete service-account if it already exists before creating
gcloud config set project $PROJECT_ID 1>/dev/null
DESCRIBE_SA=$(gcloud iam service-accounts describe $SERVICE_ACCOUNT_EMAIL 2>&1)
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]..."
CREATE_SA=$(gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME --description="$SERVICE_ACCOUNT_DESCRIPTION" --display-name="$SERVICE_ACCOUNT_DISPLAY_NAME" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create service-account: $CREATE_SA"
fi
sleep 3
Purpose: Creates the ACE service account in the access
project. Before creation, the script checks whether the account already exists
using gcloud iam service-accounts describe.
There are three outcomes:
- Exists — The old service account is deleted before the new one is created.
- Does not exist — Proceeds directly to creation.
A 3-second sleep after both deletion and creation allows GCP's IAM system time to propagate the change before the next step verifies the account.
13. Verify Service Account Created
# Verify service account was created
VERIFY_SA=$(gcloud iam service-accounts describe "$SERVICE_ACCOUNT_EMAIL" 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to verify service-account creation: $VERIFY_SA"
fi
echo "Service account [$SERVICE_ACCOUNT_NAME] created successfully."
Purpose: Confirms the service account was successfully
created by describing it immediately after creation. If the account cannot be
found or described, the captured error output is passed to print_error_and_exit. A success log line
is printed to confirm the account is ready for the next steps.
14. Create Service Account Key
# Create service-account key
echo "Creating a key for the service account [$SERVICE_ACCOUNT_NAME]..."
rm -f $SERVICE_ACCOUNT_NAME.json
CREATE_SA_KEY=$(gcloud iam service-accounts keys create "$SERVICE_ACCOUNT_NAME.json" --iam-account=$SERVICE_ACCOUNT_EMAIL 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to create a key for the service-account: $CREATE_SA_KEY"
fi
SERVICE_ACCOUNT_KEY=$(cat $SERVICE_ACCOUNT_NAME.json | base64 | tr -d \\n )
echo "Key for the service account [$SERVICE_ACCOUNT_NAME] created successfully."
Purpose: Generates a JSON key for the service account and
stores the base64-encoded contents in SERVICE_ACCOUNT_KEY
for use in the onboarding API call. Any pre-existing key file is removed before
creation to prevent stale data from being used. The gcloud command output is captured and
passed to print_error_and_exit
on failure. A trailing newline stripped with tr
-d \\n ensures the base64 string can be safely embedded in JSON.
15. Bind 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: Invokes the add_resource_access_to_service_account
function defined in section 4 to assign the required IAM roles to the target
resource. All arguments are now quoted for safety. This call covers both the
organization-level custom roles (inheritedPolicyACViewer,
appAnalyzerOrgLevelViewer)
and the standard roles at the target resource scope.
16. Service Account Activation
# Activate service account
echo "Activating service account key [$SERVICE_ACCOUNT_NAME]..."
gcloud config set account $SERVICE_ACCOUNT_EMAIL 1>/dev/null
ACTIVATE_SA=$(gcloud auth activate-service-account --project=$PROJECT_ID --key-file $SERVICE_ACCOUNT_NAME.json 2>&1)
if [[ $? -ne 0 ]]; then
print_error_and_exit "Failed to activate service-account key: $ACTIVATE_SA"
fi
echo "Service account key [$SERVICE_ACCOUNT_NAME] activated successfully."
Purpose: Activates the newly created service account key so
that subsequent gcloud calls
(including the onboarding API call) are authenticated as the service account.
The activation output is captured and passed to print_error_and_exit on failure. A success echo confirms
activation before the script proceeds to the onboarding call.
17. Onboarding Call
# Send service-account details to Algosec Cloud
echo "Sending 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 AlgoSec Cloud
onboarding API to register the GCP environment. The request body includes the
access organization ID, the base64-encoded service account key, and the access
project ID. A log line is printed before the request is sent. The --silent flag suppresses curl's default progress output so that
only the JSON response body is captured in response
for processing in the next step.
18. 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: Parses the JSON response from the onboarding API
to extract the status code
and message from the initialOnboardResult field. If the
status is 200, a success
message is displayed along with a prompt to close the terminal. Any other
status is treated as a failure and the error message from the API is displayed.
The comparison uses the [[
double-bracket construct for more robust string matching.