@@ -14,7 +14,7 @@ MANAGE="manage"
1414READ_ONLY=" read-only"
1515# Roles required for onboarding
1616ROLES=(" Contributor" " User Access Administrator" )
17-
17+ CLIENT_SECRET_PRINTED_ON_FIRST_RUN= " Printed on the first run "
1818
1919# Function to display usage information
2020usage () {
@@ -24,17 +24,42 @@ usage() {
2424 echo " --subscription_id [required for '$SUBSCRIPTION_SCOPE ' scope] Specifies the subscription id"
2525 echo " --management_group_id [required for '$MANAGEMENT_GROUPS_SCOPE ' scope] Specifies the management group id"
2626 echo " --onboarding_mode [required] Specifies the onboard mode for CloudGuard_CGNS. Can be either 'read-only' or 'manage' [default: 'read-only']"
27- echo " --multi_tenant_app_id [optional ] Specifies CloudGuard_CGNS Azure application id - for CloudGuard_CGNS application managed"
28- echo " --single_tenant_app_mode [optional ] Specifies CloudGuard_CGNS Azure application - customer app registration handling"
27+ echo " --multi_tenant_app_id [required for CloudGuard-managed (multi-tenant) mode ] Specifies CloudGuard_CGNS Azure application id - for CloudGuard_CGNS application managed"
28+ echo " --single_tenant_app_mode [required for customer-managed (single-tenant) mode ] Specifies CloudGuard_CGNS Azure application - customer app registration handling"
2929 echo " --app_name [required for single tenant app mode] Specifies the name of the application to be created"
3030 echo " --dry_run [optional] Specifies whether to run the script in dry-run mode [default: 'false']"
3131 echo " --clean [optional] Specifies whether to delete all the resources that the script created [default: 'false']"
3232 echo " --quiet [optional] Specifies whether to quiet all the user interactions [default: 'false']"
3333 echo " --help Show this help message and exit"
3434}
3535
36+ # -----------------------------------------------------------------------------
37+ # Color Output Functions
38+ #
39+ # This script defines several functions and variables to print colored text
40+ # to the terminal using ANSI escape codes. Each function prints its arguments
41+ # in a specific color and resets the color at the end.
42+ #
43+ # Variables:
44+ # end - ANSI code to reset text formatting.
45+ # red - ANSI code for red text.
46+ # yellowb - ANSI code for bold yellow text.
47+ # yellow - ANSI code for yellow text.
48+ # green - ANSI code for green text.
49+ # lightblue - ANSI code for light blue (cyan) text.
50+ #
51+ # Functions:
52+ # red - Prints arguments in red.
53+ # yellowb - Prints arguments in bold yellow.
54+ # yellow - Prints arguments in yellow.
55+ # green - Prints arguments in green.
56+ # lightblue - Prints arguments in light blue (cyan).
57+ #
58+ # Usage:
59+ # Call the desired function with the text to print in color, e.g.:
60+ # red "This is an error message"
61+ # -----------------------------------------------------------------------------
3662
37- # Message Colors
3863end=" \033[0m"
3964red=" \033[0;31m"
4065function red {
@@ -170,8 +195,15 @@ parse_input() {
170195 done
171196}
172197
198+ # az_wrapper
199+ # ----------------------------
200+ # Helper function to execute Azure CLI (az) commands with optional dry-run support.
201+ # If the global variable 'dry_run' is set to "true", the function will print the command instead of executing it.
202+ # Otherwise, it runs the az command with '--only-show-errors', capturing both output and exit code.
203+ # The command's output is stored in the variable 'AzOutput', and the exit code in 'AzRetVal'.
204+ # Errors from the az command do not cause the script to exit; they are handled by the caller.
205+ # ---------------------------------------------------------------------------
173206
174- # Wrapper function for Azure CLI commands
175207az_wrapper () {
176208 if [ " $dry_run " = " true" ]; then
177209 echo " az $* "
@@ -260,6 +292,29 @@ rollback() {
260292 fi
261293}
262294
295+ # rollback_delete_customer_app
296+ # ----------------------------
297+ # Deletes an Azure AD application with the specified name, including its role assignments.
298+ #
299+ # Steps performed:
300+ # 1. Lists Azure AD applications matching the given display name ($app_name).
301+ # 2. Checks for errors in listing applications.
302+ # 3. Ensures only one application matches the name; exits with error if multiple found.
303+ # 4. If no application is found, logs a message and returns.
304+ # 5. Deletes all role assignments associated with the application.
305+ # 6. Deletes the Azure AD application by its App ID.
306+ # 7. Handles errors during deletion and logs success message upon completion.
307+ #
308+ # Globals:
309+ # $app_name - The display name of the Azure AD application to delete.
310+ # $AzRetVal - Return value from the last az_wrapper command.
311+ # $AzOutput - Output from the last az_wrapper command.
312+ # $NECESSARY_PERMISSIONS_STR - String describing necessary permissions for error messages.
313+ #
314+ # Returns:
315+ # 0 if the application is not found or deleted successfully.
316+ # Exits with error if multiple applications are found or if any operation fails.
317+ # -------------------------------------------------------------------------------
263318
264319
265320rollback_delete_customer_app () {
@@ -295,7 +350,6 @@ rollback_delete_customer_app() {
295350}
296351
297352
298-
299353# Function to prompt user for confirmation
300354check_if_user_would_like_to_proceed () {
301355 local message=" $1 "
@@ -325,7 +379,6 @@ check_if_user_would_like_to_proceed() {
325379}
326380
327381
328-
329382# Function to delete multi-tenant service principal role assignments
330383rollback_delete_multi_tenant_sp_role_assignments (){
331384 if service_principal_doesnt_exists " $multi_tenant_app_id " ; then
@@ -335,8 +388,21 @@ rollback_delete_multi_tenant_sp_role_assignments(){
335388 fi
336389}
337390
391+ # rollback_delete_role_assignments
392+ # -------------------------------------------------------------------------
393+ # Deletes all Azure role assignments for a specified application within a given scope.
394+ #
395+ # Arguments:
396+ # $1 - The principal name (application ID) whose role assignments should be deleted.
397+ #
398+ # Behavior:
399+ # - Lists all role assignments for the specified application within the onboarding scope.
400+ # - If listing fails, prints a warning message with the error details.
401+ # - Extracts the IDs of the role assignments.
402+ # - Deletes all role assignments by their IDs.
403+ # - If deletion fails, prints a warning message with the error details.
404+ # --------------------------------------------------------------------------
338405
339- # Function to delete role assignments
340406rollback_delete_role_assignments (){
341407 local app_to_delete=$1
342408 local role_assignments
@@ -399,7 +465,27 @@ set_scope() {
399465
400466
401467
402- # Function to validate user permissions
468+ # validate_user_permissions
469+ # ------------------------------------------------------------------------------
470+ # Validates whether the currently signed-in Azure user has sufficient permissions
471+ # to perform role assignments within a specified Azure scope.
472+ #
473+ # Arguments:
474+ # $1 - The Azure scope (e.g., subscription, resource group, or resource ID)
475+ #
476+ # Behavior:
477+ # - Retrieves the signed-in user's principal name using Azure CLI.
478+ # - Lists the role assignments for the user at the specified scope, including
479+ # inherited and group-based assignments.
480+ # - Checks if the user has permissions to assign roles by invoking
481+ # validate_user_can_assign_role.
482+ # - Outputs the result and exits with an error message if any Azure CLI
483+ # operation fails.
484+ #
485+ # Returns:
486+ # 0 if the user has sufficient permissions, otherwise exits with an error.
487+ # ------------------------------------------------------------------------------
488+
403489validate_user_permissions () {
404490 local scope_type=" $1 "
405491
@@ -478,24 +564,107 @@ get_app_id() {
478564 fi
479565}
480566
567+ # check_if_app_exists
568+ # ---------------------
569+ # Validates whether an Azure AD application with the specified name already exists.
570+ # If the application exists, prompts the user to decide whether to proceed with the
571+ # existing application or exit to create new application with different app_name.
572+ #
573+ # Globals:
574+ # $app_name - The display name of the Azure AD application to check for.
575+ # $AzRetVal - Return value from the last az_wrapper command.
576+ # $AzOutput - Output from the last az_wrapper command.
577+ # $application_id - Set to the App ID if a matching application is found.
578+ #
579+ # Behavior:
580+ # 1. Queries Azure AD for applications matching the specified display name.
581+ # 2. Counts the number of matching applications returned.
582+ # 3. If multiple applications are found with the same name, exits with an error.
583+ # 4. If exactly one application is found, warns the user and prompts for confirmation
584+ # to proceed with the existing application or exit to rename it.
585+ # 5. If no application is found, returns 1 to indicate the application doesn't exist.
586+ #
587+ # Returns:
588+ # 0 if an application with the specified name exists and user chooses to proceed.
589+ # 1 if no application with the specified name exists.
590+ # Exits with error if multiple applications are found or if Azure CLI operations fail.
591+ # ------------------------------------------------------------------------------------
592+ check_if_app_exists () {
593+ lightblue " \nValidating that application with the name: '$app_name ', doesn't exist"
594+ az_wrapper ad app list --filter " displayName eq '$app_name '" --query " [].appId" -o tsv
595+ application_id=" $AzOutput "
481596
482- # only single (non multi) tenant mode
483- create_cloudguard_app_registration () {
597+ local app_result_count
598+ app_result_count= $( echo " $application_id " | grep -c ' ^ ' )
484599
485- lightblue " \nCreating a new application for '$onboarding_scope ' scope, with role:'Reader', and name:'$app_name '"
486- # Create a new service principal with the Reader role
487- local sp_info
488- az_wrapper ad sp create-for-rbac -n " $app_name " --role " Reader" --scopes " $onboarding_scope " 2> /dev/null
489- sp_info=" $AzOutput "
600+ if [ " $app_result_count " -gt 1 ]; then
601+ exit_with_error " More than one application found with the name $app_name - Please rename the 'app_name' parameter to a different name."
602+ fi
490603
491- if [ $AzRetVal -ne 0 ]; then
492- exit_with_error " Failed to create app registration with role Reader. Error from Azure: \n$AzOutput "
604+ if [ -n " $application_id " ]; then
605+ yellow " \nApplication with the name: '$app_name ', already exist. We will proceed with the existing application."
606+ check_if_user_would_like_to_proceed " Please rename the 'app_name' parameter to a different name."
607+ return 0
493608 fi
494609
495- lightblue " \nApplication info: $sp_info "
496- application_id=$( echo " $sp_info " | jq -r " .appId" )
497- client_secret=$( echo " $sp_info " | jq -r " .password" )
610+ return 1
611+ }
498612
613+ # create_cloudguard_app_registration
614+ # --------------------------------------
615+ # Creates or reuses an Azure AD application registration for CloudGuard in single tenant mode.
616+ # This function handles both scenarios: creating a new application or using an existing one.
617+ #
618+ # Globals:
619+ # $app_name - The display name of the Azure AD application to create or reuse.
620+ # $onboarding_scope - The Azure scope (subscription/management group) for the application.
621+ # $application_id - Set to the App ID of the created or existing application.
622+ # $client_secret - Set to the client secret (password) of the application.
623+ # $sp_id - Set to the service principal ID associated with the application.
624+ # $AzRetVal - Return value from the last az_wrapper command.
625+ # $AzOutput - Output from the last az_wrapper command.
626+ # $CLIENT_SECRET_PRINTED_ON_FIRST_RUN - Constant indicating secret was shown previously.
627+ # $NECESSARY_PERMISSIONS_STR - String describing necessary permissions for error messages.
628+ #
629+ # Behavior:
630+ # 1. Checks if an application with the specified name already exists using check_if_app_exists.
631+ # 2. If application exists:
632+ # - Logs a message about proceeding with existing application.
633+ # - Sets client_secret to a placeholder indicating it was shown on first run.
634+ # 3. If application doesn't exist:
635+ # - Creates a new Azure AD application with service principal using az ad sp create-for-rbac.
636+ # - Assigns the "Reader" role to the application for the specified scope.
637+ # - Extracts the application ID and client secret from the creation response.
638+ # 4. Retrieves and stores the service principal ID for the application.
639+ #
640+ # Returns:
641+ # 0 on success.
642+ # Exits with error if application creation fails or service principal ID cannot be retrieved.
643+ #
644+ # Side Effects:
645+ # - Creates a new Azure AD application and service principal if one doesn't exist.
646+ # - Assigns "Reader" role to the application for the specified scope.
647+ # - Sets global variables: $application_id, $client_secret, $sp_id.
648+ # ------------------------------------------------------------------------------------
649+ create_cloudguard_app_registration () {
650+ if check_if_app_exists; then
651+ lightblue " \nApplication with the name: '$app_name ', appId: '$application_id ' already exists. Proceeding with existing application."
652+ client_secret=" $CLIENT_SECRET_PRINTED_ON_FIRST_RUN "
653+ else
654+ lightblue " \nCreating a new application for '$onboarding_scope ' scope, with role:'Reader', and name:'$app_name '"
655+ # Create a new service principal with the Reader role
656+ local sp_info
657+ az_wrapper ad sp create-for-rbac -n " $app_name " --role " Reader" --scopes " $onboarding_scope " 2> /dev/null
658+ sp_info=" $AzOutput "
659+
660+ if [ $AzRetVal -ne 0 ]; then
661+ exit_with_error " Failed to create app registration with role Reader. Error from Azure: \n$AzOutput "
662+ fi
663+
664+ lightblue " \nApplication info: $sp_info "
665+ application_id=$( echo " $sp_info " | jq -r " .appId" )
666+ client_secret=$( echo " $sp_info " | jq -r " .password" )
667+ fi
499668
500669 az_wrapper ad sp show --id " $application_id " --query id --output tsv
501670
@@ -543,7 +712,42 @@ service_principal_doesnt_exists() {
543712}
544713
545714
546- # Function to create role assignments for CloudGuard app
715+ # create_role_assignments_for_cloudguard_app
716+ # ---------------------------------------------
717+ # Creates role assignments for a CloudGuard application based on the onboarding mode.
718+ # This function assigns the minimum required "Reader" role and additional roles if
719+ # "manage" mode is specified.
720+ #
721+ # Arguments:
722+ # $1 - The application (service principal) ID to assign roles to.
723+ #
724+ # Globals:
725+ # $sp_id - Service principal ID associated with the application (set if not already available).
726+ # $onboarding_scope - The Azure scope (subscription/management group) for role assignments.
727+ # $onboarding_mode - The onboarding mode ("read-only" or "manage").
728+ # $ROLES - Array of roles required for "manage" mode ("Contributor", "User Access Administrator").
729+ # $AzRetVal - Return value from the last az_wrapper command.
730+ # $AzOutput - Output from the last az_wrapper command.
731+ # $NECESSARY_PERMISSIONS_STR - String describing necessary permissions for error messages.
732+ #
733+ # Behavior:
734+ # 1. If $sp_id is not set, retrieves the service principal ID for the given application.
735+ # 2. Always assigns the "Reader" role to the application for the specified scope.
736+ # 3. If onboarding_mode is "manage":
737+ # - Iterates through the ROLES array.
738+ # - Assigns each role ("Contributor", "User Access Administrator") to the application.
739+ # 4. Uses app_add_role_assignment_if_needed to check for existing assignments before creating new ones.
740+ #
741+ # Returns:
742+ # 0 on success.
743+ # Exits with error if service principal ID cannot be retrieved or role assignment fails.
744+ #
745+ # Side Effects:
746+ # - Sets the global variable $sp_id if it was not previously set.
747+ # - Creates role assignments in Azure for the specified application and scope.
748+ # - May create multiple role assignments depending on the onboarding mode.
749+ # ------------------------------------------------------------------------------------
750+
547751create_role_assignments_for_cloudguard_app () {
548752 local app_id=$1
549753 if [ -z " $sp_id " ]; then
@@ -565,8 +769,22 @@ create_role_assignments_for_cloudguard_app() {
565769 fi
566770}
567771
772+ # app_add_role_assignment_if_needed
773+ # ------------------------------------------------------------------------------------
774+ # Adds a role assignment to an Azure application if it does not already exist.
775+ #
776+ # Arguments:
777+ # $1 - The application (service principal) ID to assign the role to.
778+ # $2 - The scope at which the role assignment should be applied (e.g., subscription, resource group).
779+ # $3 - The name of the Azure role to assign.
780+ #
781+ # Behavior:
782+ # - Checks if the specified role assignment already exists for the given application and scope.
783+ # - If the assignment exists, logs a message indicating so.
784+ # - If the assignment does not exist, attempts to create it.
785+ # - Exits with an error message if listing or creating the role assignment fails.
786+ # ------------------------------------------------------------------------------------
568787
569- # Function to add role assignment if needed
570788app_add_role_assignment_if_needed () {
571789 local app_id=$1
572790 local scope=$2
0 commit comments