diff --git a/cloudguard-network-application/azure/README.md b/cloudguard-network-application/azure/README.md new file mode 100644 index 00000000..1151882f --- /dev/null +++ b/cloudguard-network-application/azure/README.md @@ -0,0 +1,99 @@ +# cgns_onboarding_azure.sh + + +This script automates onboarding of Azure accounts for the CloudGuard Network Security (CGNS) SaaS application. + +## Prerequisites + +- Sufficient Azure permissions (Owner permission over the selected subscription or management group for assigning ARM access role and Global Administrator role for creating new Azure application). + +## Overview + +This script supports onboarding at both the subscription or management group level supporting two modes: + +- **Customer-managed (single-tenant):** You create and own a dedicated Azure application and service principal within your tenant. +- **CloudGuard-managed (multi-tenant):** You use a pre-existing CloudGuard-managed Azure application. CloudGuard owns and manages the app registration, while you only assign its service principal to your Azure resources. + + +For customer-managed (single-tenant) onboarding, the script performs the following steps: +- **Azure Application Registration:** Creates a dedicated Azure application for the customer’s tenant. +- **Service Principal Creation:** Registers a service principal for the newly created application, enabling programmatic access to Azure resources. +- **Role Assignment:** Assigns the necessary Azure roles (such as `Reader` or `Contributor`) to the service principal at the subscription or management group level to ensure CGNS can operate as required. +- **Resource Cleanup (optional, using `--clean` flag):** Removes the application, service principal, and associated role assignments to fully clean up the integration if requested. + > **Note:** When using the `--clean` option, you must also provide the `--app_name`, `--scope`, and the relevant `--subscription_id` or `--management_group_id` to ensure proper identification and removal of resources. + +For CloudGuard-managed (multi-tenant) onboarding, the script performs the following steps: +- **Service Principal Assignment:** Assigns a service principal for the pre-existing CloudGuard-managed Azure application to the customer’s subscription or management group. +- **Role Assignment:** Ensures the service principal has the required permissions by assigning appropriate roles. +- **Resource Cleanup (optional, using `--clean` flag):** Removes the service principal assignment and revokes permissions when offboarding. + +## Features + +### Script Support + +- Onboarding at both Subscription and Management Group scopes. +- Single-tenant (customer-managed) and multi-tenant (CloudGuard-managed) app registrations. +- Dry-run and quiet modes. + +### Script Actions + +- Assigns required Azure roles (`Reader`, `Contributor`, `User Access Administrator`). +- Validates user permissions before making changes. +- Optional clean up (delete) of created resources. + +## Usage + +```sh +./cgns_onboarding_azure.sh [OPTIONS] +``` + +### Options + +- `--scope` **[required]**: Specifies the onboarding scope. Can be either `subscription` or `management-group`. +- `--subscription_id` **[required for subscription scope]**: Azure Subscription ID. +- `--management_group_id` **[required for management-group scope]**: Azure Management Group ID. +- `--onboarding_mode` **[required]**: Onboarding mode for CloudGuard_CGNS. Can be either `read-only`assigns 'Reader' role or `manage` assigns 'Contributor' and 'User Access Administrator'. +- `--multi_tenant_app_id` **[required for CloudGuard-managed (multi-tenant) mode]**: CloudGuard_CGNS Azure application ID (for CloudGuard-managed application). +- `--single_tenant_app_mode` **[required for customer-managed (single-tenant) mode]**: Use customer-managed Azure application registration. +- `--app_name` **[required with --single_tenant_app_mode]**: Name for the Azure AD application. +- `--dry_run` **[optional]**: Run in dry-run mode (no changes will be made). +- `--clean` **[optional]**: Delete all resources created by the script. +- `--quiet` **[optional]**: Suppress user interaction prompts. +- `--help`: Show usage information. + +### Example + +Onboard a subscription with a new customer-managed application: + +```sh +./cgns_onboarding_azure.sh \ + --scope subscription \ + --subscription_id \ + --onboarding_mode manage \ + --single_tenant_app_mode true \ + --app_name "CloudGuardApp" +``` + +Onboard using an existing multi-tenant application: + +```sh +./cgns_onboarding_azure.sh \ + --scope management-group \ + --management_group_id \ + --onboarding_mode read-only \ + --multi_tenant_app_id +``` + +Clean up resources: + +```sh +./cgns_onboarding_azure.sh --scope subscription --subscription_id --onboarding_mode "read-only" --single_tenant_app_mode true --clean +``` + + + +## Notes + +- The script will prompt for confirmation unless `--quiet` is specified. +- Use `--dry_run` to preview actions without making changes. +- Output includes sensitive credentials; handle with care. diff --git a/cloudguard-network-application/azure/cgns_onboarding.sh b/cloudguard-network-application/azure/cgns_onboarding.sh new file mode 100644 index 00000000..7b855a6b --- /dev/null +++ b/cloudguard-network-application/azure/cgns_onboarding.sh @@ -0,0 +1,892 @@ +#!/bin/bash + + +# Set the output format for Azure CLI to JSON +export AZURE_CORE_OUTPUT=json + +#GLOBAL CONSTANTS +MANAGEMENT_GROUPS="providers/Microsoft.Management/managementGroups" +MANAGEMENT_GROUPS_SCOPE="management-group" +SUBSCRIPTIONS="subscriptions" +SUBSCRIPTION_SCOPE="subscription" +NECESSARY_PERMISSIONS_STR="Please make sure you have all the necessary permissions" +MANAGE="manage" +READ_ONLY="read-only" +# Roles required for onboarding +ROLES=("Contributor" "User Access Administrator") +CLIENT_SECRET_PRINTED_ON_FIRST_RUN="Printed on the first run" + +# Function to display usage information +usage() { + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " --scope [required] Specifies the scope of the onboarding. Can be either '$SUBSCRIPTION_SCOPE' or '$MANAGEMENT_GROUPS_SCOPE'" + echo " --subscription_id [required for '$SUBSCRIPTION_SCOPE' scope] Specifies the subscription id" + echo " --management_group_id [required for '$MANAGEMENT_GROUPS_SCOPE' scope] Specifies the management group id" + echo " --onboarding_mode [required] Specifies the onboard mode for CloudGuard_CGNS. Can be either 'read-only' or 'manage' [default: 'read-only']" + echo " --multi_tenant_app_id [required for CloudGuard-managed (multi-tenant) mode] Specifies CloudGuard_CGNS Azure application id - for CloudGuard_CGNS application managed" + echo " --single_tenant_app_mode [required for customer-managed (single-tenant) mode] Specifies CloudGuard_CGNS Azure application - customer app registration handling" + echo " --app_name [required for single tenant app mode] Specifies the name of the application to be created" + echo " --dry_run [optional] Specifies whether to run the script in dry-run mode [default: 'false']" + echo " --clean [optional] Specifies whether to delete all the resources that the script created [default: 'false']" + echo " --quiet [optional] Specifies whether to quiet all the user interactions [default: 'false']" + echo " --help Show this help message and exit" +} + + +# ----------------------------------------------------------------------------- +# Color Output Functions +# +# This script defines several functions and variables to print colored text +# to the terminal using ANSI escape codes. Each function prints its arguments +# in a specific color and resets the color at the end. +# +# Variables: +# end - ANSI code to reset text formatting. +# red - ANSI code for red text. +# yellowb - ANSI code for bold yellow text. +# yellow - ANSI code for yellow text. +# green - ANSI code for green text. +# lightblue - ANSI code for light blue (cyan) text. +# +# Functions: +# red - Prints arguments in red. +# yellowb - Prints arguments in bold yellow. +# yellow - Prints arguments in yellow. +# green - Prints arguments in green. +# lightblue - Prints arguments in light blue (cyan). +# +# Usage: +# Call the desired function with the text to print in color, e.g.: +# red "This is an error message" +# ----------------------------------------------------------------------------- +end="\033[0m" +red="\033[0;31m" +function red { + echo -e "${red}$*${end}" +} + +yellowb="\033[1;33m" +function yellowb { + echo -e "${yellowb}$*${end}" +} + +yellow="\033[0;33m" +function yellow { + echo -e "${yellow}$*${end}" +} + +green="\033[0;32m" +function green { + echo -e "${green}$*${end}" +} + +lightblue="\033[0;36m" +function lightblue { + echo -e "${lightblue}$*${end}" +} + + + +# Function to handle errors +exit_with_error() { + echo "Error: $1" + usage + exit 1 +} + + +exit_without_error() { + green "$1" + exit 0 +} + + + +# Function to set default input values +default_inputs() { + dry_run="false" #Set default dry-run mode to false + clean="false" #delete all the resources that the script created [default: 'false'](except service principale) + quiet="false" #quiet all the user interactions [default: 'false'] + application_id="" + sp_id="" +} + + + +# Function to parse input arguments +parse_input() { + # Parse input arguments + default_inputs + + while [[ $# -gt 0 ]]; do + case "$1" in + --scope) + scope="$2" + if [[ -z "$2" || "$2" == --* ]]; then + exit_with_error "Missing required scope argument value" + fi + shift 2 + ;; + --management_group_id) + management_group_id="$2" + if [[ -z "$2" || "$2" == --* ]]; then + exit_with_error "Missing required management_group_id argument value" + fi + shift 2 + ;; + --onboarding_mode) + onboarding_mode="$2" + if [[ -z "$2" || "$2" == --* ]]; then + exit_with_error "Missing required onboarding_mode argument value" + fi + shift 2 + ;; + --multi_tenant_app_id) + multi_tenant_app_id="$2" + if [[ -z "$2" || "$2" == --* ]]; then + exit_with_error "Missing required multi_tenant_app_id argument value" + fi + shift 2 + ;; + --single_tenant_app_mode) + single_tenant_app_mode="$2" + if [[ -z "$2" || "$2" == --* ]]; then + exit_with_error "Missing required single_tenant_app_mode argument value" + fi + shift 2 + ;; + --app_name) + app_name_input="$2" + if [ -z "$app_name_input" ]; then + exit_with_error "Missing required app_name argument" + fi + app_name="$app_name_input" + shift 2 + ;; + --subscription_id) + subscription_id="$2" + if [[ -z "$2" || "$2" == --* ]]; then + exit_with_error "Missing required subscription_id argument value" + fi + shift 2 + ;; + --clean) + clean="true" + shift 1 + ;; + --dry_run) + dry_run="true" + shift 1 + ;; + --quiet) + quiet="true" + shift 1 + ;; + --help) + usage + exit 0 + ;; + *) + yellow "Invalid option: $1" + exit 1 + ;; + esac + done +} + +# az_wrapper +# ---------------------------- +# Helper function to execute Azure CLI (az) commands with optional dry-run support. +# If the global variable 'dry_run' is set to "true", the function will print the command instead of executing it. +# Otherwise, it runs the az command with '--only-show-errors', capturing both output and exit code. +# The command's output is stored in the variable 'AzOutput', and the exit code in 'AzRetVal'. +# Errors from the az command do not cause the script to exit; they are handled by the caller. +# --------------------------------------------------------------------------- +az_wrapper() { + if [ "$dry_run" = "true" ]; then + echo "az $*" + return 0 + fi + + # do not exit on error, capture it and forward to stdout to be handled by caller + set +e + AzOutput=$(az "$@" --only-show-errors 2>&1) + AzRetVal=$? + set -e +} + + + +# Function to validate input arguments +validate_inputs() { + if [ -z "$scope" ]; then + exit_with_error "Missing required argument: --scope" + fi + + if [ "$scope" != "$SUBSCRIPTION_SCOPE" ] && [ "$scope" != "$MANAGEMENT_GROUPS_SCOPE" ]; then + exit_with_error "Invalid scope argument. Can be either '$SUBSCRIPTION_SCOPE' or '$MANAGEMENT_GROUPS_SCOPE'" + fi + + if [ "$scope" = "$SUBSCRIPTION_SCOPE" ] && [ -z "$subscription_id" ]; then + exit_with_error "Missing required argument: --subscription_id" + fi + + if [ "$scope" = "$MANAGEMENT_GROUPS_SCOPE" ] && [ -z "$management_group_id" ]; then + exit_with_error "Missing required argument: --management_group_id" + fi + if [ "$clean" = "true" ]; then + if [ -z "$app_name" ]; then + exit_with_error "When --clean is specified, --app_name must also be provided." + fi + return 0 + fi + if [ -z "$onboarding_mode" ]; then + exit_with_error "Missing required argument: --onboarding_mode" + fi + + if [ "$onboarding_mode" != "$READ_ONLY" ] && [ "$onboarding_mode" != "manage" ]; then + exit_with_error "Invalid onboarding_mode argument. Can be either 'read-only' or 'manage'" + fi + + if [ -z "$multi_tenant_app_id" ] && [ -z "$single_tenant_app_mode" ]; then + exit_with_error "Missing required argument: --multi_tenant_app_id or --single_tenant_app_mode" + fi + + if [ -n "$multi_tenant_app_id" ] && [ -n "$single_tenant_app_mode" ]; then + exit_with_error "Invalid argument: --multi_tenant_app_id and --single_tenant_app_mode cannot be used together" + fi + + if [ -n "$single_tenant_app_mode" ] && [ -z "$app_name" ]; then + exit_with_error "Missing required argument: --app_name" + fi + + if [ "$quiet" = "true" ]; then + lightblue "\nQuiet mode is enabled." + fi + + if [ "$dry_run" = "true" ]; then + lightblue "\nDry_run mode is enabled." + fi +} + + + +# Function to handle rollback operations +rollback() { + if [ "$clean" = "true" ]; then + lightblue "\nClean mode is enabled." + lightblue "\nWe will delete role-assignment created in the script." + check_if_user_would_like_to_proceed "" + + lightblue "\nStarts cleaning process" + if [ -z "$multi_tenant_app_id" ]; then + # delete role assignments for customer app + rollback_delete_customer_app + else + # delete role assignments for multi-tenant app + rollback_delete_multi_tenant_sp_role_assignments + fi + exit_without_error " \n--------- Clean Completed Successfully --------" + fi +} + + + +# rollback_delete_customer_app +# ---------------------------- +# Deletes an Azure AD application with the specified name, including its role assignments. +# +# Steps performed: +# 1. Lists Azure AD applications matching the given display name ($app_name). +# 2. Checks for errors in listing applications. +# 3. Ensures only one application matches the name; exits with error if multiple found. +# 4. If no application is found, logs a message and returns. +# 5. Deletes all role assignments associated with the application. +# 6. Deletes the Azure AD application by its App ID. +# 7. Handles errors during deletion and logs success message upon completion. +# +# Globals: +# $app_name - The display name of the Azure AD application to delete. +# $AzRetVal - Return value from the last az_wrapper command. +# $AzOutput - Output from the last az_wrapper command. +# $NECESSARY_PERMISSIONS_STR - String describing necessary permissions for error messages. +# +# Returns: +# 0 if the application is not found or deleted successfully. +# Exits with error if multiple applications are found or if any operation fails. +# ------------------------------------------------------------------------------- +rollback_delete_customer_app() { + az_wrapper ad app list --filter "displayName eq '$app_name'" --query "[].appId" -o tsv + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to list applications with the name: $app_name. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + application_id="$AzOutput" + + local app_result_count + app_result_count=$(echo "$application_id" | grep -c '^') + + if [ "$app_result_count" -gt 1 ]; then + exit_with_error "More than one application found with the name $app_name - Can't decide which app to delete." + fi + + if [ -z "$application_id" ]; then + lightblue "\nApplication: $app_name not found." + return 0 + fi + + rollback_delete_role_assignments "$application_id" + + az_wrapper ad app delete --id "$application_id" + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "\nFailed to delete application: $app_name, App ID: $application_id." + fi + + lightblue "\nApplication: $app_name, App ID: $application_id deleted successfully." +} + + + +# Function to prompt user for confirmation +check_if_user_would_like_to_proceed() { + local message="$1" + if [ "$quiet" = "true" ]; then + return 0 + fi + + while true; do + read -r -p "Would you like to proceed? (y/n): " choice + + case "$choice" in + [Yy]) + green "Proceeding\n" + break + ;; + [Nn]) + if [ -n "$message" ]; then + yellow "$message" + fi + exit_without_error "Exiting" + ;; + *) + red "Invalid choice. Please enter 'y' or 'n'." + ;; + esac + done +} + + + +# Function to delete multi-tenant service principal role assignments +rollback_delete_multi_tenant_sp_role_assignments(){ + if service_principal_doesnt_exists "$multi_tenant_app_id"; then + yellow "" + else + rollback_delete_role_assignments "$multi_tenant_app_id" + fi +} + +# rollback_delete_role_assignments +# ------------------------------------------------------------------------- +# Deletes all Azure role assignments for a specified application within a given scope. +# +# Arguments: +# $1 - The principal name (application ID) whose role assignments should be deleted. +# +# Behavior: +# - Lists all role assignments for the specified application within the onboarding scope. +# - If listing fails, prints a warning message with the error details. +# - Extracts the IDs of the role assignments. +# - Deletes all role assignments by their IDs. +# - If deletion fails, prints a warning message with the error details. +# -------------------------------------------------------------------------- +rollback_delete_role_assignments(){ + local app_to_delete=$1 + local role_assignments + local role_assignments_ids + local space_separated_role_assignments_ids + + az_wrapper role assignment list --scope "$onboarding_scope" --query "[?principalName=='$app_to_delete']" -o json + if [ $AzRetVal -ne 0 ]; then + yellow "\nFailed to list role assignments for application ID: $app_to_delete. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + role_assignments="$AzOutput" + role_assignments_ids=$(echo "$role_assignments" | jq -r '.[].id') + space_separated_role_assignments_ids=$(echo "$role_assignments_ids" | tr '\n' ' ') + + lightblue "\nDeleting role assignments" + az_wrapper role assignment delete --ids "$space_separated_role_assignments_ids" + if [ $AzRetVal -ne 0 ]; then + yellow "\nFailed to delete role assignments for application ID: $app_to_delete. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi +} + + + +# Function to display user information and required permissions +user_information () { + lightblue "\nPlease make sure the current user has the required permissions to perform the steps below." + lightblue "This can be accomplished by granting 'Owner' permission over the '$onboarding_scope' scope, and the 'Global Administrator' role." + + lightblue "\nThe script will execute the following steps:" + if [ -n "$single_tenant_app_mode" ]; then + lightblue "- Create a new application for '$onboarding_scope' scope, with role:'Reader', and name:'$app_name'." + if [ "$onboarding_mode" = "$MANAGE" ]; then + lightblue "- Assign 'Contributor' and 'User Access Administrator' to application: '$app_name' for '$onboarding_scope' scope." + fi + fi + if [ -n "$multi_tenant_app_id" ]; then + lightblue "- Create a service principal for CloudGuard application ID: '$multi_tenant_app_id'." + lightblue "- Assign 'Reader' role to service principal for '$onboarding_scope' scope." + if [ "$onboarding_mode" = "$MANAGE" ]; then + lightblue "- Assign 'Contributor' and 'User Access Administrator' to service principal for '$onboarding_scope' scope." + fi + fi + lightblue "" + check_if_user_would_like_to_proceed "" +} + + +# Function to set the scope for onboarding +set_scope() { + if [ "$scope" = "$SUBSCRIPTION_SCOPE" ]; then + scopeVar="$SUBSCRIPTIONS" + scopeIdVar="$subscription_id" + elif [ "$scope" = "$MANAGEMENT_GROUPS_SCOPE" ]; then + scopeVar="$MANAGEMENT_GROUPS" + scopeIdVar="$management_group_id" + fi + onboarding_scope="/$scopeVar/$scopeIdVar" +} + + + +# validate_user_permissions +#------------------------------------------------------------------------------ +# Validates whether the currently signed-in Azure user has sufficient permissions +# to perform role assignments within a specified Azure scope. +# +# Arguments: +# $1 - The Azure scope (e.g., subscription, resource group, or resource ID) +# +# Behavior: +# - Retrieves the signed-in user's principal name using Azure CLI. +# - Lists the role assignments for the user at the specified scope, including +# inherited and group-based assignments. +# - Checks if the user has permissions to assign roles by invoking +# validate_user_can_assign_role. +# - Outputs the result and exits with an error message if any Azure CLI +# operation fails. +# +# Returns: +# 0 if the user has sufficient permissions, otherwise exits with an error. +#------------------------------------------------------------------------------ +validate_user_permissions() { + local scope_type="$1" + + # Get user's principal name + local userPrincipalName + az_wrapper ad signed-in-user show --query "userPrincipalName" --output tsv + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to get the signed-in user's principal name. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + userPrincipalName="$AzOutput" + lightblue "\nLogged-in user's principal name: '$userPrincipalName." + + # skip validatenuser permissions - Because it is impossible to check the inheritance of permissions in Azure + # return 0 + local userRoleAssignments + az_wrapper role assignment list --assignee "$userPrincipalName" --include-inherited --include-groups --scope "$scope_type" + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to list role assignments for user: $userPrincipalName. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + userRoleAssignments="$AzOutput" + + if validate_user_can_assign_role "$userRoleAssignments"; then + green "\nUser has sufficient permissions" + else + yellow "\nUser unauthorized to perform role assignments" + fi +} + + +# Function to validate if user has a specific role assignment +validate_user_role_assignment() { + local role_name="$1" + local list_role_assignments="$2" + + lightblue "\nChecking if current user has a role of '$role_name'" + + local app_result_count + app_result_count=$(echo "$list_role_assignments" | jq ".[] | select(.roleDefinitionName==\"$role_name\")" | jq -s '. | length') + + if [[ "$app_result_count" == "0" ]]; then + return 1 + fi + return 0 +} + + +# Function to validate if user can assign roles +validate_user_can_assign_role() { + local user_role_assignments="$1" + lightblue "\nChecking if the current user has an 'Owner' or 'Application Administrator' role in '$onboarding_scope' scope." + + if validate_user_role_assignment "Owner" "$user_role_assignments"; then + return 0 + elif validate_user_role_assignment "User Access Administrator" "$user_role_assignments"; then + return 0 + fi + + return 1 +} + + +# Function to get application ID +get_app_id() { + if [ -n "$single_tenant_app_mode" ]; then + create_cloudguard_app_registration + app_id="$application_id" + elif [ -n "$multi_tenant_app_id" ]; then + create_service_principal_for_multi_tenant_app + app_id="$multi_tenant_app_id" + else + exit_with_error "No valid app mode provided. Please specify either --single_tenant_app_mode or --multi_tenant_app_id." + fi +} + +# check_if_app_exists +# --------------------- +# Validates whether an Azure AD application with the specified name already exists. +# If the application exists, prompts the user to decide whether to proceed with the +# existing application or exit to create new application with different app_name. +# +# Globals: +# $app_name - The display name of the Azure AD application to check for. +# $AzRetVal - Return value from the last az_wrapper command. +# $AzOutput - Output from the last az_wrapper command. +# $application_id - Set to the App ID if a matching application is found. +# +# Behavior: +# 1. Queries Azure AD for applications matching the specified display name. +# 2. Counts the number of matching applications returned. +# 3. If multiple applications are found with the same name, exits with an error. +# 4. If exactly one application is found, warns the user and prompts for confirmation +# to proceed with the existing application or exit to rename it. +# 5. If no application is found, returns 1 to indicate the application doesn't exist. +# +# Returns: +# 0 if an application with the specified name exists and user chooses to proceed. +# 1 if no application with the specified name exists. +# Exits with error if multiple applications are found or if Azure CLI operations fail. +# ------------------------------------------------------------------------------------ +check_if_app_exists() { + lightblue "\nValidating that application with the name: '$app_name', doesn't exist" + az_wrapper ad app list --filter "displayName eq '$app_name'" --query "[].appId" -o tsv + application_id="$AzOutput" + + local app_result_count + app_result_count=$(echo "$application_id" | grep -c '^') + + if [ "$app_result_count" -gt 1 ]; then + exit_with_error "More than one application found with the name $app_name - Please rename the 'app_name' parameter to a different name." + fi + + if [ -n "$application_id" ]; then + yellow "\nApplication with the name: '$app_name', already exist. We will proceed with the existing application." + check_if_user_would_like_to_proceed "Please rename the 'app_name' parameter to a different name." + return 0 + fi + + return 1 +} + +# create_cloudguard_app_registration +# -------------------------------------- +# Creates or reuses an Azure AD application registration for CloudGuard in single tenant mode. +# This function handles both scenarios: creating a new application or using an existing one. +# +# Globals: +# $app_name - The display name of the Azure AD application to create or reuse. +# $onboarding_scope - The Azure scope (subscription/management group) for the application. +# $application_id - Set to the App ID of the created or existing application. +# $client_secret - Set to the client secret (password) of the application. +# $sp_id - Set to the service principal ID associated with the application. +# $AzRetVal - Return value from the last az_wrapper command. +# $AzOutput - Output from the last az_wrapper command. +# $CLIENT_SECRET_PRINTED_ON_FIRST_RUN - Constant indicating secret was shown previously. +# $NECESSARY_PERMISSIONS_STR - String describing necessary permissions for error messages. +# +# Behavior: +# 1. Checks if an application with the specified name already exists using check_if_app_exists. +# 2. If application exists: +# - Logs a message about proceeding with existing application. +# - Sets client_secret to a placeholder indicating it was shown on first run. +# 3. If application doesn't exist: +# - Creates a new Azure AD application with service principal using az ad sp create-for-rbac. +# - Assigns the "Reader" role to the application for the specified scope. +# - Extracts the application ID and client secret from the creation response. +# 4. Retrieves and stores the service principal ID for the application. +# +# Returns: +# 0 on success. +# Exits with error if application creation fails or service principal ID cannot be retrieved. +# +# Side Effects: +# - Creates a new Azure AD application and service principal if one doesn't exist. +# - Assigns "Reader" role to the application for the specified scope. +# - Sets global variables: $application_id, $client_secret, $sp_id. +# ------------------------------------------------------------------------------------ +create_cloudguard_app_registration() { + if check_if_app_exists; then + lightblue "\nApplication with the name: '$app_name', appId: '$application_id' already exists. Proceeding with existing application." + client_secret="$CLIENT_SECRET_PRINTED_ON_FIRST_RUN" + else + lightblue "\nCreating a new application for '$onboarding_scope' scope, with role:'Reader', and name:'$app_name'" + # Create a new service principal with the Reader role + local sp_info + az_wrapper ad sp create-for-rbac -n "$app_name" --role "Reader" --scopes "$onboarding_scope" 2>/dev/null + sp_info="$AzOutput" + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to create app registration with role Reader. Error from Azure: \n$AzOutput" + fi + + lightblue "\nApplication info: $sp_info" + application_id=$(echo "$sp_info" | jq -r ".appId") + client_secret=$(echo "$sp_info" | jq -r ".password") + fi + + az_wrapper ad sp show --id "$application_id" --query id --output tsv + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to fetch service principal id for application: $application_id. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + sp_id="$AzOutput" + +} + + + +# Function to create service principal for multi-tenant app +create_service_principal_for_multi_tenant_app() { + # create service principal if required + if service_principal_doesnt_exists "$multi_tenant_app_id"; then + lightblue "\nCreating a service principal with '$onboarding_scope' scope for app:'$multi_tenant_app_id'" + # create service principal for given application id + az_wrapper ad sp create --id "$multi_tenant_app_id" + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to create service principal for application: $multi_tenant_app_id. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + # fetch service principal id + sp_id=$(echo "$AzOutput" | jq -r '.id') + else + lightblue "\nService principal already exists. proceed with existing service principal id: '$sp_id'" + fi +} + + +# Function to check if service principal does not exist +service_principal_doesnt_exists() { + local app_id=$1 + az_wrapper ad sp show --id "$app_id" --query id --output tsv + sp_id="$AzOutput" + + if [ $AzRetVal -ne 0 ] || [ -z "$sp_id" ]; then + lightblue "Service principal doesn't exists for application $app_id" + return 0 + fi + lightblue "Service principal $sp_id exists for application $app_id" + return 1 +} + + +# create_role_assignments_for_cloudguard_app +# --------------------------------------------- +# Creates role assignments for a CloudGuard application based on the onboarding mode. +# This function assigns the minimum required "Reader" role and additional roles if +# "manage" mode is specified. +# +# Arguments: +# $1 - The application (service principal) ID to assign roles to. +# +# Globals: +# $sp_id - Service principal ID associated with the application (set if not already available). +# $onboarding_scope - The Azure scope (subscription/management group) for role assignments. +# $onboarding_mode - The onboarding mode ("read-only" or "manage"). +# $ROLES - Array of roles required for "manage" mode ("Contributor", "User Access Administrator"). +# $AzRetVal - Return value from the last az_wrapper command. +# $AzOutput - Output from the last az_wrapper command. +# $NECESSARY_PERMISSIONS_STR - String describing necessary permissions for error messages. +# +# Behavior: +# 1. If $sp_id is not set, retrieves the service principal ID for the given application. +# 2. Always assigns the "Reader" role to the application for the specified scope. +# 3. If onboarding_mode is "manage": +# - Iterates through the ROLES array. +# - Assigns each role ("Contributor", "User Access Administrator") to the application. +# 4. Uses app_add_role_assignment_if_needed to check for existing assignments before creating new ones. +# +# Returns: +# 0 on success. +# Exits with error if service principal ID cannot be retrieved or role assignment fails. +# +# Side Effects: +# - Sets the global variable $sp_id if it was not previously set. +# - Creates role assignments in Azure for the specified application and scope. +# - May create multiple role assignments depending on the onboarding mode. +# ------------------------------------------------------------------------------------ +create_role_assignments_for_cloudguard_app() { + local app_id=$1 + if [ -z "$sp_id" ]; then + # fetch service principal id if it has not been set yet + az_wrapper ad sp show --id "$app_id" --query id --output tsv + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to fetch service principal id for application: $app_id. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + sp_id="$AzOutput" + fi + + app_add_role_assignment_if_needed "$app_id" "$onboarding_scope" "Reader" + if [[ "$onboarding_mode" = "manage" ]]; then + for role in "${ROLES[@]}"; do + app_add_role_assignment_if_needed "$app_id" "$onboarding_scope" "$role" + done + fi +} + +# app_add_role_assignment_if_needed +# ------------------------------------------------------------------------------------ +# Adds a role assignment to an Azure application if it does not already exist. +# +# Arguments: +# $1 - The application (service principal) ID to assign the role to. +# $2 - The scope at which the role assignment should be applied (e.g., subscription, resource group). +# $3 - The name of the Azure role to assign. +# +# Behavior: +# - Checks if the specified role assignment already exists for the given application and scope. +# - If the assignment exists, logs a message indicating so. +# - If the assignment does not exist, attempts to create it. +# - Exits with an error message if listing or creating the role assignment fails. +# ------------------------------------------------------------------------------------ +app_add_role_assignment_if_needed() { + local app_id=$1 + local scope=$2 + local role=$3 + + local role_assignment_id + az_wrapper role assignment list --assignee "$app_id" --role "$role" --scope "$scope" + + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to list role assignments for application: $app_id. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + + if [ "$(echo "$AzOutput" | jq -e '. | length > 0')" = "true" ]; then + role_assignment_id=$(echo "$AzOutput" | jq -r '.[0].id') + fi + + if [ -z "$role_assignment_id" ]; then + # role assignment does not exists, create it + lightblue "\nCreating '$role' role assignment for app '$app_id'" + az_wrapper role assignment create --assignee "$app_id" --role "$role" --scope "$scope" + if [ $AzRetVal -ne 0 ]; then + exit_with_error "Failed to create $role role assignment for application:$app_id. Error from Azure: \n$AzOutput" + fi + else + lightblue "\n'$role' role assignment exists for app '$app_id'" + fi +} + + + +# Function to print onboarding parameters +print_onboarding_parameters() { + az_wrapper account show --query "tenantId" -o tsv + + if [ $AzRetVal -ne 0 ]; then + yellow "Failed to get tenant id. $NECESSARY_PERMISSIONS_STR. Error from Azure: \n$AzOutput" + fi + tenant_id="$AzOutput" + + lightblue "Tenant ID: $tenant_id" + + if [ "$scope" = "$SUBSCRIPTION_SCOPE" ]; then + lightblue "Subscription ID: $subscription_id" + elif [ "$scope" = "$MANAGEMENT_GROUPS_SCOPE" ]; then + lightblue "Management Group ID: $management_group_id" + fi +} + + +# Function to print service principal parameters +print_onboarding_parameters_service_principal() { + lightblue "\n\n-----Outputs-----" + + lightblue "Service Principal ID: $sp_id" +} + +# Function to print customer app parameters +print_onboarding_parameters_customer_app() { + yellowb "\nThe output includes credentials that you must protect, and can only be viewed once. \nBe sure that you do not include these credentials in your code or check the credentials into your source control. \nFor more information, see https://aka.ms/azadsp-cli" + + lightblue "\n\n-----Outputs-----" + + lightblue "Application(client) ID: $application_id" + lightblue "Secret Key: $client_secret" +} + + +######################### +######### Main ########## +######################### + +main() { + parse_input "$@" + validate_inputs + set_scope + if [ "$clean" = "true" ]; then + rollback + fi + user_information + validate_user_permissions "$onboarding_scope" + + app_id="" + get_app_id + + create_role_assignments_for_cloudguard_app "$app_id" + + + # script output handling + if [ -z "$multi_tenant_app_id" ]; then + print_onboarding_parameters_customer_app + else + print_onboarding_parameters_service_principal + fi + print_onboarding_parameters + + + #print_onboarding_parameters_service_principal + + #print_onboarding_parameters + + green " \n--------- CloudGuard Network Security app registered Successfully --------\n" + lightblue "\nPlease go back to the CloudGuard Network Security onboarding wizard to complete the onboarding process\n" + +} + +main "$@" + + diff --git a/cloudguard-network-application/gcp/README.md b/cloudguard-network-application/gcp/README.md new file mode 100644 index 00000000..9fc49909 --- /dev/null +++ b/cloudguard-network-application/gcp/README.md @@ -0,0 +1,95 @@ +# cgns_onboarding_gcp.sh + +This script automates onboarding of Google Cloud Platform (GCP) accounts for the CloudGuard Network Security (CGNS) SaaS application. + +## Prerequisites + +To connect your GCP account to CloudGuard Network Security, ensure you have the following permissions: + +### Required GCP Organization Permissions +- `roles/iam.organizationRoleAdmin` +- `roles/iam.securityAdmin` + +### Required GCP Project Permission +- `roles/owner` (Project Owner role) + +Alternatively, you must have equivalent permissions that allow you to: +- Create and manage service accounts +- Create custom IAM roles at both organization and project levels +- Assign roles to service accounts +- Enable required APIs + +These permissions are necessary for the script to properly configure the integration between CloudGuard and your GCP environment. + + +## Overview + +This script supports onboarding at the project level, including: +- Creation of custom IAM roles with the required permissions for CGNS. +- Creation of a dedicated service account for CloudGuard. +- Assignment of custom roles to the service account at both project and organization levels. +- Enabling required GCP APIs. +- Optional creation of a service account private key. +- Optional cleanup (removal) of all created resources using the `--clean` flag. + +### Main Actions +- **Custom Role Creation:** Creates project-level and organization-level custom roles with all permissions required by CGNS. +- **Service Account Creation:** Creates a dedicated service account in the specified project. +- **Role Assignment:** Assigns the custom roles and any required built-in roles to the service account at both project and organization scopes. +- **API Enablement:** Enables required GCP APIs (IAM, Service Usage, Cloud Resource Manager). +- **Resource Cleanup (optional, using `--clean` flag):** Removes all created roles, service accounts, and role assignments. + +## Features + +- Onboarding at the project level (organization-level roles are also handled if required). +- Automated creation and assignment of custom roles. +- Dry-run and quiet modes. +- Optional full cleanup of all resources created by the script. + +## Usage + +```sh +./cgns_onboarding_gcp.sh [OPTIONS] +``` + +### Options + +- `--service_account_project_id `: Project ID where the service account will be created. +- `--service_account_name `: Name of the service account. +- `--project_id `: Project ID where the service account will be used. +- `--create_key`: Create a key for the service account (optional). +- `--enable_services`: Enable required GCP APIs (optional). +- `--clean`: Clean up (delete) all resources created by the script (optional). +- `--quiet`: Suppress user interaction prompts (optional). +- `--dry_run`: Run in dry-run mode (no changes will be made). +- `--help`: Show usage information. + +### Example + +Onboard a project with a new service account and custom roles: + +```sh +./cgns_onboarding_gcp.sh \ + --service_account_project_id my-gcp-project \ + --service_account_name cloudguard-sa \ + --project_id my-gcp-project \ + --enable_services \ + --create_key +``` + +Clean up all resources created by the script: + +```sh +./cgns_onboarding_gcp.sh \ + --service_account_project_id my-gcp-project \ + --service_account_name cloudguard-sa \ + --project_id my-gcp-project \ + --clean +``` + +## Notes + +- The script will prompt for confirmation unless `--quiet` is specified. +- Use `--dry_run` to preview actions without making changes. +- Output may include sensitive credentials; handle with care. +- Organization-level permissions are required for some operations (such as custom role creation/assignment at the organization scope). diff --git a/cloudguard-network-application/gcp/cgns_onboarding.sh b/cloudguard-network-application/gcp/cgns_onboarding.sh new file mode 100644 index 00000000..284c18d6 --- /dev/null +++ b/cloudguard-network-application/gcp/cgns_onboarding.sh @@ -0,0 +1,1046 @@ +#!/bin/bash +#set -x + +PROJECT_SCOPE="project" +FOLDER_SCOPE="folder" + +# Roles +OWNER_ROLE="roles/owner" +IAM_SECURITY_ADMIN="roles/iam.securityAdmin" +IAM_ORG_ROLE_ADMIN="roles/iam.organizationRoleAdmin" +CLOUD_GUARD_NSI_ROLE_NAME="Custom_Cloud_Guard_NSI_Role" +CLOUD_GUARD_ORGANIZATION_NSI_ROLE_NAME="Custom_Cloud_Guard_Organization_NSI_Role" + + +ROLES_LIST=( + "$CLOUD_GUARD_NSI_ROLE_NAME" + "$CLOUD_GUARD_ORGANIZATION_NSI_ROLE_NAME" +) + +GCP_NSI_PRMISSIONS=( + "compute.autoscalers.create" + "compute.autoscalers.delete" + "compute.autoscalers.get" + "compute.autoscalers.update" + "compute.disks.create" + "compute.firewallPolicies.create" + "compute.firewallPolicies.delete" + "compute.firewallPolicies.get" + "compute.firewallPolicies.update" + "compute.firewallPolicies.use" + "compute.firewalls.create" + "compute.firewalls.delete" + "compute.firewalls.get" + "compute.firewalls.update" + "compute.forwardingRules.create" + "compute.forwardingRules.delete" + "compute.forwardingRules.get" + "compute.forwardingRules.use" + "compute.globalOperations.get" + "compute.healthChecks.create" + "compute.healthChecks.delete" + "compute.healthChecks.get" + "compute.healthChecks.useReadOnly" + "compute.instanceGroupManagers.create" + "compute.instanceGroupManagers.delete" + "compute.instanceGroupManagers.get" + "compute.instanceGroupManagers.use" + "compute.instanceGroups.delete" + "compute.instanceGroups.use" + "compute.instanceTemplates.create" + "compute.instanceTemplates.delete" + "compute.instanceTemplates.get" + "compute.instanceTemplates.useReadOnly" + "compute.instances.create" + "compute.instances.setMetadata" + "compute.instances.setTags" + "compute.networks.create" + "compute.networks.delete" + "compute.networks.get" + "compute.networks.setFirewallPolicy" + "compute.networks.updatePolicy" + "compute.networks.use" + "compute.regionBackendServices.create" + "compute.regionBackendServices.delete" + "compute.regionBackendServices.get" + "compute.regionBackendServices.use" + "compute.regions.list" + "compute.subnetworks.create" + "compute.subnetworks.delete" + "compute.subnetworks.get" + "compute.subnetworks.use" + "compute.subnetworks.useExternalIp" + "compute.zones.list" + "iam.serviceAccounts.actAs" + "networksecurity.interceptDeploymentGroups.create" + "networksecurity.interceptDeploymentGroups.delete" + "networksecurity.interceptDeploymentGroups.get" + "networksecurity.interceptDeploymentGroups.use" + "networksecurity.interceptDeployments.create" + "networksecurity.interceptDeployments.delete" + "networksecurity.interceptDeployments.get" + "networksecurity.interceptEndpointGroupAssociations.create" + "networksecurity.interceptEndpointGroupAssociations.delete" + "networksecurity.interceptEndpointGroupAssociations.get" + "networksecurity.interceptEndpointGroups.create" + "networksecurity.interceptEndpointGroups.delete" + "networksecurity.interceptEndpointGroups.get" + "networksecurity.interceptEndpointGroups.use" + "networksecurity.securityProfiles.create" + "resourcemanager.projects.get" +) + +GCP_NSI_PRMISSIONS_ORGANIZATION=( + "networksecurity.operations.get" + "networksecurity.securityProfileGroups.create" + "networksecurity.securityProfileGroups.delete" + "networksecurity.securityProfileGroups.get" + "networksecurity.securityProfileGroups.list" + "networksecurity.securityProfileGroups.update" + "networksecurity.securityProfileGroups.use" + "networksecurity.securityProfiles.create" + "networksecurity.securityProfiles.delete" + "networksecurity.securityProfiles.get" + "networksecurity.securityProfiles.list" + "networksecurity.securityProfiles.update" + "networksecurity.securityProfiles.use" +) + + +GCP_API_SERVICES=( + "iam.googleapis.com" + "serviceusage.googleapis.com" + "cloudresourcemanager.googleapis.com" +) + +# Function to join all API services into a single comma-separated string join_list_comma_separated "${GCP_NSI_PRMISSIONS[@]}" +join_list_comma_separated() { + local arr=("$@") + local IFS="," + echo "${arr[*]}" +} + + +#Message Colors +end="\033[0m" +red="\033[0;31m" +function red { + echo -e "${red}$*${end}" +} + +yellowb="\033[1;33m" +function yellowb { + echo -e "${yellowb}$*${end}" +} + +yellow="\033[0;33m" +function yellow { + echo -e "${yellow}$*${end}" +} + +green="\033[0;32m" +function green { + echo -e "${green}$*${end}" +} + +lightblue="\033[0;36m" +function lightblue { + echo -e "${lightblue}$*${end}" +} + +gcloud_wrapper() { + if [ "$dry_run" = "true" ]; then + echo "gcloud $*" + return 0 + fi + + # do not exit on error, capture it and forward to stdout to be handled by caller + set +e + GcloudOutput=$(gcloud "$@" --quiet --verbosity=error 2>&1) + GcloudRetVal=$? + set -e + +} + +enable_api_services() { + for service in "${GCP_API_SERVICES[@]}"; do + green "Enabling service $service" + gcloud_wrapper services enable "$service" --project "$service_account_project_id" + if [ $GcloudRetVal -ne 0 ]; then + yellow "\nWarning: Failed to enable service $service. Error from Gcloud: \n$GcloudOutput. Continuing..." + fi + done +} + +exit_with_error() { + red "\n --------- Error --------" + red "$1" + red "" + if [ "$dry_run" != "true" ]; then + exit 1 + fi +} + +exit_without_error() { + green "$1" + exit 0 +} + +check_if_user_would_like_to_proceed() { + local message="$1" + if [ "$quiet" = "true" ]; then + return 0 + fi + + while true; do + read -r -p "Would you like to proceed? (y/n): " choice + + case "$choice" in + [Yy]) + green "Proceeding\n" + break + ;; + [Nn]) + if [ -n "$message" ]; then + yellow "$message" + fi + exit_without_error "Exiting" + ;; + *) + red "Invalid choice. Please enter 'y' or 'n'." + ;; + esac + done +} + +default_inputs() { + scope="project" #Set default scope to project + dry_run="false" #Set default dry-run mode to false + clean="false" + quiet="false" + project_id="" + organization_id="" + service_account_project_id="" + enable_services="false" #Set default enable services to false + create_key="false" +} + + +usage() { + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " --service_account_project_id Project ID where the service account will be created" + echo " --service_account_name Name of the service account" + echo " --project_id Project ID where the service account will be used" + echo " --create_key Create a key for the service account" + echo " --enable_services Enable GCP services" + echo " --clean Clean up resources" + echo " --quiet Quiet mode" + echo " --dry_run Dry run mode" + echo " --help Display this help message" + echo "" + echo "Example:" + echo " $0 --service_account_project_id my-project --service_account_name my-service-account --project_id my-project-id --enable_services --create_key" + echo "" +} + +parse_input() { + + default_inputs + + while [[ $# -gt 0 ]]; do + case "$1" in + --service_account_project_id) + service_account_project_id="$2" + if [ -z "$2" ]; then + exit_with_error "Missing required service_account_project_id argument" + fi + shift 2 + ;; + --service_account_name) + service_account_name=$(echo "$2" | tr '[:upper:]' '[:lower:]') + if [ -z "$2" ]; then + exit_with_error "Missing required service_account_name argument" + fi + shift 2 + ;; + --project_id) + project_id="$2" + if [ -z "$2" ]; then + exit_with_error "Missing required project_id argument" + fi + shift 2 + ;; + --folder_id) + folder_id="$2" + if [ -z "$2" ]; then + exit_with_error "Missing required folder_id argument" + fi + shift 2 + ;; + --enable_services) + enable_services="true" + shift 1 + ;; + --create_key) + create_key="true" + shift 1 + ;; + --clean) + clean="true" + shift 1 + ;; + --quiet) + quiet="true" + shift 1 + ;; + --dry_run) + dry_run="true" + shift 1 + ;; + --help) + usage + exit 0 + ;; + *) + yellow "Invalid option: $1" + exit 1 + ;; + esac + done +} + +validate_inputs() { + if [ -z "$project_id" ] || [ -z "$scope" ] || [ -z "$service_account_name" ]; then + exit_with_error "Missing required arguments" + fi + + if [ "$scope" != "$PROJECT_SCOPE" ]; then + exit_with_error "Invalid scope argument. Can be '$PROJECT_SCOPE' given '$scope'" + fi +} + +set_scope() { + if [ "$scope" = "$PROJECT_SCOPE" ]; then + scopeVar="$PROJECT_SCOPE" + scopeIdVar="$project_id" + else + exit_with_error "Invalid scope argument. Can be '$PROJECT_SCOPE', given '$scope'" + fi +} + + +get_scope_iam_policy() { + # Get the IAM policy for the specified scope + if [ "$scopeVar" == "$PROJECT_SCOPE" ]; then + gcloud_wrapper projects get-iam-policy "$scopeIdVar" --format=json + fi + + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to get IAM policy for $scopeVar $scopeIdVar. Error from Gcloud: \n$GcloudOutput" + fi +} + +get_organization_scope_iam_policy() { + # Get the IAM policy for the specified scope + gcloud_wrapper organizations get-iam-policy "$organization_id" --format=json + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to get IAM policy for $scopeVar $scopeIdVar. Error from Gcloud: \n$GcloudOutput" + fi +} + + +# ----------------------------------------------------------------------------- +# Function: create_project_custom_role_for_nsi +# +# Description: +# Creates a custom IAM role in a specified GCP project for NSI onboarding. +# The function first checks if the custom role already exists. If it does, +# it skips creation. Otherwise, it creates the role with the specified +# permissions and provides appropriate success or error messages. +# +# Globals: +# CLOUD_GUARD_NSI_ROLE_NAME - Name of the custom role to create. +# GCP_NSI_PRMISSIONS - Array of permissions to assign to the role. +# project_id - GCP project ID where the role will be created. +# GcloudRetVal - Return value from the last gcloud_wrapper call. +# GcloudOutput - Output from the last gcloud_wrapper call. +# +# Arguments: +# None +# +# Returns: +# 0 if the role already exists or is created successfully. +# Exits with error if role creation fails. +# +# ----------------------------------------------------------------------------- +create_project_custom_role_for_nsi() { + local role_name=$CLOUD_GUARD_NSI_ROLE_NAME + local permissions + permissions="$(join_list_comma_separated "${GCP_NSI_PRMISSIONS[@]}")" + # Check if the role already exists + gcloud_wrapper iam roles describe "$role_name" --project="$project_id" --format=json + # Check if the role exists and is not deleted + if [ $GcloudRetVal -eq 0 ]; then + local is_deleted + is_deleted=$(echo "$GcloudOutput" | jq -r '.deleted // false') + if [ "$is_deleted" = "true" ]; then + # Role exists but is in deleted state, so we should create it again + GcloudRetVal=1 + yellow "Custom role $role_name exists but is in deleted state. Will undelete it." + fi + fi + if [ $GcloudRetVal -eq 0 ]; then + yellow "Custom role $role_name already exists in project $project_id. Skipping creation." + return 0 + fi + if [ "$is_deleted" = "true" ]; then + # Undelete the role if it was previously deleted + gcloud_wrapper iam roles undelete "$role_name" --project="$project_id" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to undelete custom role $role_name. Error from Gcloud: \n$GcloudOutput" + fi + green "\nUndeleting custom role $role_name" + return 0 + fi + # Create a custom role with the specified permissions + gcloud_wrapper iam roles create "$role_name" --project="$project_id" --title="$role_name" --permissions="$permissions" --description="Custom role for NSI onboarding" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to create custom role $role_name. Error from Gcloud: \n$GcloudOutput" + fi + green "\nCreating custom role $role_name with permissions: $permissions" +} + + +create_organization_custom_role_for_nsi() { + local role_name=$CLOUD_GUARD_ORGANIZATION_NSI_ROLE_NAME + local permissions + permissions="$(join_list_comma_separated "${GCP_NSI_PRMISSIONS_ORGANIZATION[@]}")" + # Check if the role already exists + gcloud_wrapper iam roles describe "$role_name" --organization="$organization_id" --format=json + # Check if the role exists and is not deleted + if [ $GcloudRetVal -eq 0 ]; then + local is_deleted + is_deleted=$(echo "$GcloudOutput" | jq -r '.deleted // false') + if [ "$is_deleted" = "true" ]; then + # Role exists but is in deleted state, so we should create it again + GcloudRetVal=1 + yellow "Custom role $role_name exists but is in deleted state. Will undelete it." + fi + fi + if [ $GcloudRetVal -eq 0 ]; then + yellow "Custom role $role_name already exists in organization $organization_id. Skipping creation." + return 0 + fi + if [ "$is_deleted" = "true" ]; then + # Undelete the role if it was previously deleted + gcloud_wrapper iam roles undelete "$role_name" --organization="$organization_id" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to undelete custom role $role_name. Error from Gcloud: \n$GcloudOutput" + fi + green "\nUndeleting custom role $role_name" + return 0 + fi + # Create a custom role with the specified permissions + gcloud_wrapper iam roles create "$role_name" --organization="$organization_id" --title="$role_name" --permissions="$permissions" --description="Custom role for NSI onboarding" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to create custom role $role_name. Error from Gcloud: \n$GcloudOutput" + fi + green "\nCreating custom role $role_name with permissions: $permissions" +} + + +################################################################# +# Function: get_project_organization_id +# Description: Retrieves the organization ID associated with the specified project. +# The function uses the Google Cloud SDK to query the project's ancestors +# and extracts the organization ID from the response. +# +# Parameters: +# None directly, but uses the global variable: +# - project_id: The Google Cloud project ID to query +# +# Globals Used: +# - project_id: Input project ID +# - GcloudRetVal: Return value from gcloud_wrapper function +# - GcloudOutput: Output from gcloud_wrapper function +# - organization_id: Variable to store the retrieved organization ID +# +# Returns: +# Sets the global variable organization_id with the retrieved organization ID +# +# Exit Codes: +# Calls exit_with_error if unable to retrieve organization ID +################################################################## +get_project_organization_id() { + if [ -n "$project_id" ]; then + gcloud_wrapper projects get-ancestors "$project_id" --format json + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to get organization ID for project $project_id. Error from Gcloud: \n$GcloudOutput" + fi + organization_id=$(echo "$GcloudOutput" | jq -r '.[] | select(.type == "organization") | .id') + green "\nOrganization ID is: $organization_id\n" + fi +} + +################################################################ +# Function: is_role_unassigned +# Description: Checks if a specific IAM role is not assigned to a service account. +# +# Arguments: +# $1 - role: The GCP IAM role to check (e.g., "roles/compute.viewer") +# $2 - iam_policy: JSON string containing the IAM policy for the scope +# +# Global variables used: +# service_account_email: Email of the service account to check +# +# Returns: +# 0 - If the role is NOT assigned to the service account +# 1 - If the role IS assigned to the service account +# +# Output: +# Prints colored message indicating whether the role is assigned or not +############################################################# +is_role_unassigned() { + local role="$1" # Role to check + local iam_policy="$2" # IAM policy for the scope + + # Check if the role is assigned to the service account + local role_assigned + role_assigned=$(echo "$iam_policy" | jq -r ".bindings[] | select(.role == \"$role\") | .members[] | select(. == \"serviceAccount:$service_account_email\")") + if [[ -n $role_assigned ]]; then + green "\nRole $role is assigned to the service account $service_account_email" + return 1 # Role assigned + else + green "\nRole $role is not assigned to the service account $service_account_email" + return 0 # Role not assigned + fi +} + + +add_role_assignment_if_needed() { + local role="$1" + local iam_policy="$2" + + if is_role_unassigned "$role" "$iam_policy"; then + green "Assigning role $role to service account $service_account_email for $scopeVar $scopeIdVar" + if [ "$scopeVar" == "$PROJECT_SCOPE" ]; then + gcloud_wrapper projects add-iam-policy-binding "$scopeIdVar" --member "serviceAccount:$service_account_email" --role "$role" --condition=None + fi + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to assign role $role to service account $service_account_email for $scopeVar: $scopeIdVar. Error from Gcloud: \n$GcloudOutput" + fi + fi +} + + +#for deploying nsi we need to add the role on the organization level +add_role_assignment_organization_if_needed() { + local role="$1" + local iam_policy="$2" + + if is_role_unassigned "$role" "$iam_policy"; then + green "Assigning role $role to service account $service_account_email for organization/$organization_id scope" + gcloud_wrapper organizations add-iam-policy-binding "organizations/$organization_id" --member "serviceAccount:$service_account_email" --role "$role" --condition=None + + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to assign role $role to service account $service_account_email for $scopeVar: $scopeIdVar. Error from Gcloud: \n$GcloudOutput" + fi + fi +} + +add_role_assignments_CGNS() { + local scope_iam_policy="$1" + local CLOUD_GUARD_PROJECT_NSI_ROLE="projects/$project_id/roles/$CLOUD_GUARD_NSI_ROLE_NAME" + local CLOUD_GUARD_ROLES_LIST=( + "$CLOUD_GUARD_PROJECT_NSI_ROLE" + ) + for role in "${CLOUD_GUARD_ROLES_LIST[@]}"; do + add_role_assignment_if_needed "$role" "$scope_iam_policy" + done +} + + + +add_role_assignments_organization_CGNS() { + local scope_iam_policy="$1" + local CLOUD_GUARD_ORGANIZATION_NSI_ROLE="organizations/$organization_id/roles/$CLOUD_GUARD_ORGANIZATION_NSI_ROLE_NAME" + # You can add more organization-level custom roles here if needed + local CLOUD_GUARD_ROLES_LIST=( + "$CLOUD_GUARD_ORGANIZATION_NSI_ROLE" + # Add more roles if required + ) + for role in "${CLOUD_GUARD_ROLES_LIST[@]}"; do + add_role_assignment_organization_if_needed "$role" "$scope_iam_policy" + done +} + + +add_role_assignments() { + get_scope_iam_policy + local scope_iam_policy="$GcloudOutput" + + green "\nAssigning project-level roles to service account..." + add_role_assignments_CGNS "$scope_iam_policy" + + get_organization_scope_iam_policy + local organization_iam_policy="$GcloudOutput" + + green "\nAssigning organization-level roles to service account..." + add_role_assignments_organization_CGNS "$organization_iam_policy" +} + + + +set_service_account_email() { + service_account_email=$(echo "$service_account_name@$service_account_project_id.iam.gserviceaccount.com" | tr '[:upper:]' '[:lower:]') +} + + +################################################################ +# Creates a service account in GCP if it doesn't already exist +# +# This function checks if a service account with the specified name exists in the given project. +# If it doesn't exist, it creates the service account. If it already exists, it notifies the user +# and prompts them to proceed or rename the service account. +# +# Variables used: +# - service_account_project_id: The GCP project ID where the service account should be created +# - service_account_email: The email address of the service account to check +# - service_account_name: The name of the service account to create if needed +# +# Returns: +# - Nothing on success +# - Exits with error if unable to list or create service accounts +############################################################### +create_service_account_if_required() { + gcloud_wrapper iam service-accounts list --project="$service_account_project_id" --filter="email:$service_account_email" --format="value(email)" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to list service accounts for project $service_account_project_id. Error from Gcloud: \n$GcloudOutput" + fi + if [ -z "$GcloudOutput" ]; then + green "Service account $service_account_name does not exists, Creating service account $service_account_name" + gcloud_wrapper iam service-accounts create "$service_account_name" --project "$service_account_project_id" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to create service account $service_account_name service_account_project_id $service_account_project_id. Error from Gcloud: \n$GcloudOutput" + fi + else + yellow "\nService account $service_account_name already exists. We will proceed with the existing service account." + check_if_user_would_like_to_proceed "Please rename the 'service_account_name' parameter to a different name." + fi +} + + +############################################################## +# Check if a role exists in IAM policy bindings +# +# This function checks if a specific role is present in the provided IAM policy bindings JSON. +# +# Args: +# role: The role name to check for (e.g., "roles/editor") +# bindings: JSON string containing IAM policy bindings +# +# Returns: +# 0: If the role does NOT exist in the bindings +# 1: If the role exists in the bindings +# +# Example: +# bindings=$(gcloud projects get-iam-policy PROJECT_ID --format=json | jq '.bindings') +# if role_exists_in_bindings "roles/editor" "$bindings"; then +# echo "Role does not exist" +# else +# echo "Role exists" +# fi +############################################################# +role_exists_in_bindings() { + local role="$1" + local bindings="$2" + + count=$(echo "$bindings" | jq -r "[.[] | select(.role == \"$role\")] | length") + if [ "$count" -gt 0 ]; then + return 1 + fi + return 0 +} + +##################################################### +## validate_project_user_permissions +# +# Validates whether a user has owner permissions on a GCP project. +# The function checks the IAM policy of the project and its ancestors to determine +# if the specified user has the owner role. +# +# Args: +# $1 (user_email): The email address of the user to check +# $2 (project_id): The GCP project ID to validate permissions on +# +# Returns: +# None +# +# Exits: +# 1: If unable to retrieve IAM policy information or if the user lacks owner permissions +# +# Dependencies: +# - gcloud_wrapper: Function to execute gcloud commands +# - role_exists_in_bindings: Function to check if a role exists in IAM bindings +# - exit_with_error: Function to exit with an error message +# - green: Function to output success messages +# +# Global variables used: +# - GcloudRetVal: Return value of the last gcloud command +# - GcloudOutput: Output of the last gcloud command +# - OWNER_ROLE: Role to check for (presumably "roles/owner") +##################################################### +validate_project_user_permissions() { + local user_email="$1" + local project_id="$2" + gcloud_wrapper projects get-ancestors-iam-policy "$project_id" --format=json + if [ $GcloudRetVal -ne 0 ]; then + gcloud_wrapper projects get-iam-policy "$project_id" --format=json + fi + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failure on get-ancestors-iam-policy project $project_id. Error from Gcloud: \n$GcloudOutput" + fi + local bindings + bindings=$(echo "$GcloudOutput" | jq -r 'if type == "array" then . else [.] end | [.[] | if .bindings then .bindings else .policy.bindings end | .[] | select(.members[] | contains("user:'"$user_email"'"))]') + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failure on get-ancestors-iam-policy project $project_id. Error from Gcloud: \n$GcloudOutput" + fi + if ! role_exists_in_bindings "$OWNER_ROLE" "$bindings"; then + green "User $user_email has $OWNER_ROLE role on project $project_id" + else + yellow "User $user_email does not have $OWNER_ROLE role on project $project_id in order to perform the onboarding actions\nyou must have equivalent permissions that allow you to: + \n- Create and manage service accounts \n- Create custom IAM roles at both + organization and project levels \n- Assign roles to the service account." + check_if_user_would_like_to_proceed + fi +} + + +validate_project_user_permissions_by_projectId(){ + local user_email="$1" + validate_project_user_permissions "$user_email" "$project_id" + if [ "$project_id" != "$service_account_project_id" ]; then + validate_project_user_permissions "$user_email" "$service_account_project_id" + fi +} + + + +validate_folder_user_permissions() { + local user_email="$1" + gcloud_wrapper resource-manager folders get-ancestors-iam-policy "$folder_id" --flatten='policy.bindings[].members' --format=json --filter=policy.bindings.members="user:$user_email" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failure on resource-manager folders get-ancestors-iam-policy $folder_id. Error from Gcloud: \n$GcloudOutput" + fi + local bindings + bindings=$(echo "$GcloudOutput" | jq -r "[.[] | .policy.bindings]") + if ! role_exists_in_bindings "$OWNER_ROLE" "$bindings"; then + green "User $user_email has $OWNER_ROLE role on folder $folder_id" + else + exit_with_error "User $user_email does not have $OWNER_ROLE role on folder $folder_id" + fi +} + +validate_organization_user_permissions() { + local user_email="$1" + gcloud_wrapper organizations get-iam-policy "$organization_id" --flatten='bindings[].members' --format=json --filter=bindings.members="user:$user_email" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failure on get-iam-policy organization $organization_id. Error from Gcloud: \n$GcloudOutput" + fi + local bindings + bindings=$(echo "$GcloudOutput" | jq -r "[.[] | .bindings]") + if ! role_exists_in_bindings "$OWNER_ROLE" "$bindings"; then + green "User $user_email has the $OWNER_ROLE role on organization $organization_id" + elif ! role_exists_in_bindings "$IAM_SECURITY_ADMIN" "$bindings" && ! role_exists_in_bindings "$IAM_ORG_ROLE_ADMIN" "$bindings"; then + green "User $user_email has the $IAM_SECURITY_ADMIN and the $IAM_ORG_ROLE_ADMIN roles on organization $organization_id" + else + yellow "User $user_email does not have the required $IAM_ORG_ROLE_ADMIN or $IAM_SECURITY_ADMIN role on organization $organization_id in order to perform the onboarding actions\nyou must have equivalent permissions that allow you to: + \n- Create and manage service accounts \n- Create custom IAM roles at both + organization and project levels \n- Assign roles to the service account." + check_if_user_would_like_to_proceed + fi +} + +validate_user_permissions() { + gcloud_wrapper config get-value account + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failure on getting currently active google cloud account that the gcloud CLI is using for authentication and API calls. Error from Gcloud: \n$GcloudOutput" + fi + local user_email + user_email=$(echo "$GcloudOutput" | tail -1) + + if [ "$scopeVar" = "$PROJECT_SCOPE" ]; then + validate_project_user_permissions_by_projectId "$user_email" + elif [ "$scopeVar" = "$FOLDER_SCOPE" ]; then + validate_folder_user_permissions "$user_email" + fi + # need to check organization permission for nsi + validate_organization_user_permissions "$user_email" +} + + +list_output() { + local inputList=("$@") + local message="" + for item in "${inputList[@]}"; do + message+=" $item\n" + done + echo "$message" +} + + +########################################################################## +# Function Name: user_information +# Description: Displays information about the actions the script will perform and asks for user confirmation to proceed. +# +# This function informs the user about: +# - Services to be enabled (if applicable) +# - Service account creation details +# - Roles to be assigned +# - Whether a service account key will be created +# +# After displaying the information, it prompts the user to confirm before proceeding. +# +# Arguments: +# None - Uses global variables for configuration +# +# Global Variables Used: +# create_key - Boolean flag for service account key creation +# scopeVar - Scope type (project, folder, or organization) +# scopeIdVar - ID of the scope +# enable_services - Boolean flag for enabling GCP services +# GCP_API_SERVICES - Array of GCP services to enable +# service_account_name - Name of the service account +# service_account_project_id - Project ID for the service account +# ROLES_LIST - Array of roles to be assigned +# +# Returns: +# None - Proceeds if user confirms, likely exits if user declines +########################################################################## +user_information() { + local roles_list + roles_list=$(list_output "${ROLES_LIST[@]}") + + lightblue "The script will execute the following steps:\n" + + if [ "$enable_services" = "true" ]; then + message="- Enable the following GCP services:\n" + local serviceList + serviceList=$(list_output "${GCP_API_SERVICES[@]}") + message+="$serviceList" + lightblue "$message" + fi + lightblue "- Create custom roles if they do not exist." + lightblue "- Create a service account '$service_account_name' in project '$service_account_project_id' if it does not exist.\n" + lightblue "- Assign the following roles to the service account:\n$roles_list\n" + + if [ "$create_key" = "true" ]; then + lightblue "- Create a key for the service account.\n" + fi + + check_if_user_would_like_to_proceed +} + +# Remove role assignment if it exists +remove_role_assignment_if_needed() { + local role="$1" + local iam_policy="$2" + + if ! is_role_unassigned "$role" "$iam_policy"; then + green "Removing $role role assignment from service account $service_account_email" + if [ "$scopeVar" == "$PROJECT_SCOPE" ]; then + gcloud_wrapper projects remove-iam-policy-binding "$scopeIdVar" --member "serviceAccount:$service_account_email" --role "$role" + elif [ "$scopeVar" == "$FOLDER_SCOPE" ]; then + gcloud_wrapper resource-manager folders remove-iam-policy-binding "$scopeIdVar" --member "serviceAccount:$service_account_email" --role "$role" + else # organization scope + gcloud_wrapper organizations remove-iam-policy-binding "$scopeIdVar" --member "serviceAccount:$service_account_email" --role "$role" + fi + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to remove role $role from service account $service_account_email for $scopeVar: $scopeIdVar. Error from Gcloud: \n$GcloudOutput" + fi + fi +} + +remove_organization_role_assignment_if_needed() { + local role="$1" + local iam_policy="$2" + + if ! is_role_unassigned "$role" "$iam_policy"; then + green "Removing $role role assignment from service account $service_account_email" + gcloud_wrapper organizations remove-iam-policy-binding "$organization_id" --member "serviceAccount:$service_account_email" --role "$role" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to remove role $role from service account $service_account_email for organization: $organization_id. Error from Gcloud: \n$GcloudOutput" + fi + fi +} + + +# Function to delete a custom role from a specified scope +delete_custom_role() { + local role_name="$1" + local scope_type="$2" # "project" or "organization" + local scope_id="$3" + local scope_flag="" + + if [ "$scope_type" = "project" ]; then + scope_flag="--project" + elif [ "$scope_type" = "organization" ]; then + scope_flag="--organization" + else + yellow "Invalid scope type: $scope_type. Must be 'project' or 'organization'." + return 1 + fi + + gcloud_wrapper iam roles describe "$role_name" "$scope_flag"="$scope_id" --format=json + if [ $GcloudRetVal -eq 0 ]; then + # Check if the role is already in a deleted state + local is_deleted + is_deleted=$(echo "$GcloudOutput" | jq -r '.deleted // false') + + if [ "$is_deleted" = "true" ]; then + yellow "Custom role $role_name in $scope_type $scope_id is already in deleted state. No action needed." + else + green "\nDeleting $scope_type-level custom role: $role_name" + gcloud_wrapper iam roles delete "$role_name" "$scope_flag"="$scope_id" + if [ $GcloudRetVal -ne 0 ]; then + yellow "Warning: Failed to delete custom role $role_name from $scope_type $scope_id. Error from Gcloud: \n$GcloudOutput" + else + green "Successfully deleted $scope_type-level custom role: $role_name" + fi + fi + else + yellow "Custom role $role_name does not exist in $scope_type $scope_id or cannot be accessed." + fi +} + + +delete_custom_roles() { + green "\nChecking for and deleting custom roles..." + + # Delete project-level custom role + delete_custom_role "$CLOUD_GUARD_NSI_ROLE_NAME" "project" "$project_id" + + # Delete organization-level custom role + if [ -n "$organization_id" ]; then + delete_custom_role "$CLOUD_GUARD_ORGANIZATION_NSI_ROLE_NAME" "organization" "$organization_id" + else + yellow "Organization ID not provided or not found. Skipping organization-level role deletion." + fi +} + + +delete_service_account() { + # Check if the service account exists before attempting to delete it + gcloud_wrapper iam service-accounts list --project="$service_account_project_id" --filter="email:$service_account_email" --format="value(email)" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to check if service account $service_account_email exists. Error from Gcloud: \n$GcloudOutput" + fi + + # Only attempt deletion if the service account exists + if [ -n "$GcloudOutput" ]; then + green "\nDeleting service account $service_account_email" + gcloud_wrapper iam service-accounts delete "$service_account_email" --project "$service_account_project_id" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failed to delete service account $service_account_email. Error from Gcloud: \n$GcloudOutput" + fi + green "Service account $service_account_email deleted successfully" + else + yellow "Service account $service_account_email does not exist. No need to delete." + fi +} + +############################################################## +# Rollback Check Point CloudGuard Network Security Integration (CGNS) for GCP. +# This function performs the following clean-up operations: +# 1. Retrieves the current IAM policy for the project scope +# 2. Gets the organization ID associated with the project +# 3. Removes any role assignments for CloudGuard service accounts at the project level +# 4. Retrieves the IAM policy for the organization scope +# 5. Removes any role assignments for CloudGuard service accounts at the organization level +# 6. Deletes any custom roles created during the CGNS onboarding +# 7. Deletes the CloudGuard service account +# +# This function should be used to completely clean up all CGNS components when +# uninstalling the CloudGuard Network Security integration. +######################################### +rollback_CGNS() { + get_scope_iam_policy + local scope_iam_policy=$GcloudOutput + get_project_organization_id + local CLOUD_GUARD_PROJECT_NSI_ROLE="projects/$project_id/roles/$CLOUD_GUARD_NSI_ROLE_NAME" + local CLOUD_GUARD_ORGANIZATION_NSI_ROLE="organizations/$organization_id/roles/$CLOUD_GUARD_ORGANIZATION_NSI_ROLE_NAME" + local CLOUD_GUARD_ROLES_LIST=( + "$CLOUD_GUARD_PROJECT_NSI_ROLE" + ) + for role in "${CLOUD_GUARD_ROLES_LIST[@]}"; do + remove_role_assignment_if_needed "$role" "$scope_iam_policy" + done + get_organization_scope_iam_policy + local organization_scope_iam_policy=$GcloudOutput + remove_organization_role_assignment_if_needed "$CLOUD_GUARD_ORGANIZATION_NSI_ROLE" "$organization_scope_iam_policy" + delete_custom_roles + delete_service_account +} + +rollback() { + if [ "$clean" = "true" ]; then + lightblue "\nClean mode is enabled." + lightblue "\nWe will delete all the resources created in the script." + check_if_user_would_like_to_proceed + + lightblue "\nStarts cleaning process" + rollback_CGNS + exit_without_error " \n--------- Clean Completed Successfully --------" + fi +} + +create_service_account_key() { + gcloud_wrapper iam service-accounts keys create "$service_account_name.json" --iam-account "$service_account_email" --project "$service_account_project_id" + if [ $GcloudRetVal -ne 0 ]; then + exit_with_error "Failure on iam service-accounts keys create $service_account_name.json for $service_account_email and project $service_account_project_id. Error from Gcloud: \n$GcloudOutput" + fi + + lightblue "\nService account key created successfully\n\n" + + lightblue "\n---------- COPY THE JSON BELOW ----------\n" + cat "$service_account_name.json" + lightblue "\n---------- COPY THE JSON ABOVE ----------\n" + + rm "$service_account_name.json" +} + +######################### +######### Main ########## +######################### + +main() { + parse_input "$@" + validate_inputs + set_scope + set_service_account_email + rollback + user_information + get_project_organization_id + validate_user_permissions + + if [ "$enable_services" = "true" ]; then + enable_api_services + else + green "Skip enable services" + fi + create_service_account_if_required + create_project_custom_role_for_nsi + create_organization_custom_role_for_nsi + add_role_assignments + if [ "$create_key" = "true" ]; then + create_service_account_key + fi + + + green " \n--------- CloudGuard app registered Successfully --------\n" + lightblue "\nPlease go back to the CloudGuard onboarding wizard to complete the onboarding process\n" + +} + +main "$@"