Adding Requester to Google Workspace Role

Overview

I had been tasked with investigating if Netwrix could be used to handle a specific use case revolving around self elevation of privileges within the Google Workspace suite. After poking around between both the Google API’s, Netwrix API’s, and Netwrix’s custom PowerShell activity type, I found it to be not only possible, but FAAAIRLY straightforward. Since I had to prod a bit at Netwrix to figure out some of the internal functions, I figured I would share both the redacted code for elevating into a Google workspace role, as well as some of what was learned along the way. Hopefully someone else can get some value from this post.

Guide

Here I will show you a fairly step-by-step process for getting this code to work in a Google workspace environment. I make no promises or guarantees on the safety of the code/instructions below and encourage you to do your due diligence when reviewing it. As always, test in a test environment and if you break prod, don’t come looking for me!

With that out of the way, lets start.

Prepping Google Workspace

Lets get our Google workspace API enabled and a service account created.

  1. Navigate to https://console.cloud.google.com/ and create a new project in your tenant.

  2. Once your project is created, go to the API dashboard (https://console.cloud.google.com/apis) so we can enable the required APIs. From here, select the Enable APIs and services button

  1. Search for Admin SDK API and select it from the list and select Enable from the API screen.

  1. Now lets go to the IAM management to create the service account. Navigate to https://console.cloud.google.com/iam-admin and select service accounts from the left navigation bar.

  1. Now create the service account with whatever name/description suites you and grant it at least viewer privileges.

  2. Next, create a new auth key from the keys tab of the service account. Be sure to select p12 as the key type!

  1. Download your p12 key somewhere safe for now. We will need it later

  2. Next, we need a valid service account in our workspace directory. So create a new user in the Google directory, and add them to the super admin role. This user has to be in the super admin role as it is the only role that has access to role management, and to do role management we must impersonate a user (the service account we are creating in this step).

  3. While in Workspace, navigate to the following domain wide delegation page to assign our service account access to specific permission scopes. From this page, select add new and add your service account client ID/Unique ID from step 5. Enter the following scopes in the OAuth scopes

https://www.googleapis.com/auth/admin.directory.rolemanagement,
https://www.googleapis.com/auth/admin.directory.user,
https://www.googleapis.com/auth/admin.directory.orgunit,
https://www.googleapis.com/auth/admin.directory.user.readonly,
https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly

Prepping the Netwrix Node(s)

In this section we will prep the Netwrix node(s) with the required PowerShell module and our P12 auth key. Repeat all steps on all nodes in your Netwrix cluster.

  1. run the below command to install PSGSuite. Alternatively, if your node does not have network access, simply pull the module from the github and add it to your module folder manually.
Install-Module PSGSuite -Scope AllUsers
  1. Copy your P12 auth key to the node. You can copy it anywhere as it will only need to be here temporarily. I would suggest the root of your drive for simplicity.

  2. Restart the SBPam proxy services. This will restart all Netwrix services and seems to be required. Without the service restart Netwrix’s flow for calling custom PowerShell code will not import required assemblies and types from the PSGSuite module.

The Netwrix Activity Code

I struggled a bit on how to keep this as clean and re-usable as possible since Netwrix does not directly allowing you to create re-usable functions within the activities. I considered creating a new function script in the SBPam script directory, but that seemed too unintuitive and difficult to troubleshoot if needed. I ended up landing on keeping all my code in the actual activity and controlling it from there. There are two activities I will list below, one is used to setup our PSGSuite config in the actionservice user scope, and the other is what will be published to technicians and users.

PSGSuite Configuration

Create a new activity with just a single custom powershell action within it. Then replace all the code with the below code. Replace the variables with information you’ve gotten from the Google IAM page. The AdminEmail should be the account that is within the superadmin role in the workspace directory.

Add-SbPAMActionLog -Type Info -Message "Running on $SbPAMRestApiUri"

$configs = @{
        SetAsDefaultConfig     = $true
        ConfigName             = "Netwrix"
        P12KeyPath             = "FULL_PATH_TO_AUTH_KEY.p12"
        AppEmail               = "SERVICEACCOUNT@DOMAIN.iam.gserviceaccount.com"
        CustomerID             = "CUSTOMERID"
        Preference             = "CustomerID"
        ServiceAccountClientID = "SERVICEACCOUNTCLIENTID"
        Domain                 = "DOMAIN"
        AdminEmail             = "WORKSPACE_DIRECTORY_SERVICE_ACCOUNT"
    }
Set-PSGSuiteConfig @configs -Verbose -Scope User

Add-SbPAMActionLog -Type Info -Message "Updated PSGSuite config on $SbPAMRestApiUri"

Assign this activity to yourself in an access policy and run it. If you have multiple nodes that are service meshed, run this a few times, checking the logs to confirm it runs on each node. Once it has run on every node in your cluster, you can unassign the activity from yourself and proceed to the actual activity that will move the user into the role. Once this has run, you can also remove the P12 from the Netwrix nodes (maybe test the whole process first though)

Workspace Role Elevation Activity

Create a new activity with a custom PowerShell script as the Pre-Session step and input the code below. You will need to just update the variable $RoleIDToElevateTo with the ID of the role you want to elevate to. You can acquire this from the URL when viewing the role in the workspace admin webpage.

Import-Module PSGSuite

$RoleIDToElevateTo = "11111111111111111"


#Confirm our user exists in Netwrix
if (![string]::IsNullOrEmpty($UserId)) {
    $targetUser = Get-SbPAMUser -Id $UserId
    $UPN = $targetUser.UserPrincipalName
    if ($null -ne $targetUser) { $login = (Get-LoginFromSamAccountname -User $targetUser) }
    if ([string]::IsNullOrEmpty($login)) {
        Write-Error "Unable to find user."
        exit 1
    }
    Add-SbPAMActionLog -Type Info -Message ("User $UPN initiated activity.")
}

#Get our Google roles
Add-SbPAMActionLog -Type Info -Message ("Querying Google for roles...")
$roles = Get-GSAdminRole
Add-SbPAMActionLog -Type Info -Message ("Retrieved $($roles.count) roles from Google.")

#Make sure you put in the ID of a valid role
if ($roles.roleID -notcontains $RoleIDToElevateTo) {
    Add-SbPAMActionLog -Type Error -Message ("No roleID in Google matching $RoleIDToElevateTo!")
    exit(1)
}

#Get our Google role assignments for our user
Add-SbPAMActionLog -Type Info -Message ("Querying Google for role assignments for user $UPN...")
$roleAssignments = @(Get-GSAdminRoleAssignment -UserKey $UPN -ErrorAction SilentlyContinue)
Add-SbPAMActionLog -Type Info -Message ("Retrieved $($roleAssignments.count) role assignments from Google for $UPN")

#Check if the user is already in this role or not
if ($RoleIDToElevateTo -iin $roleAssignments.RoleId) {
    Add-SbPAMActionLog -Type Warn -Message ("$UPN already has a role assignment for role $RoleIDToElevateTo!")
}

else {
    #Assign the role to the user
    Add-SbPAMActionLog -Type Info -Message ("Assigning $UPN to the role of $RoleIDToElevateTo ...")
    New-GSAdminRoleAssignment -AssignedTo $UPN -RoleId $RoleIDToElevateTo
    Add-SbPAMActionLog -Type Info -Message ("$UPN has been assigned to the role of $RoleIDToElevateTo")
}

Now, create another custom Powershell step under the Post-Session actions. Replace the default code with the below code, again, updating the $RoleIDToElevateTo with your desired role.

Import-Module PSGSuite

$RoleIDToElevateTo = "11111111111111111"

#Confirm our user exists in Netwrix
if (![string]::IsNullOrEmpty($UserId)) {
    $targetUser = Get-SbPAMUser -Id $UserId
    $UPN = $targetUser.UserPrincipalName
    if ($null -ne $targetUser) { $login = (Get-LoginFromSamAccountname -User $targetUser) }
    if ([string]::IsNullOrEmpty($login)) {
        Write-Error "Unable to find user."
        exit 1
    }
    Add-SbPAMActionLog -Type Info -Message ("User $UPN initiated activity.")
}

#Get our Google roles
Add-SbPAMActionLog -Type Info -Message ("Querying Google for roles...")
$roles = Get-GSAdminRole
Add-SbPAMActionLog -Type Info -Message ("Retrieved $($roles.count) roles from Google.")

#Make sure you put in the ID of a valid role
if ($roles.roleID -notcontains $RoleIDToElevateTo) {
    Add-SbPAMActionLog -Type Error -Message ("No roleID in Google matching $RoleIDToElevateTo!")
    exit(1)
}

#Get our Google role assignments for our user
Add-SbPAMActionLog -Type Info -Message ("Querying Google for role assignments for user $UPN...")
$roleAssignments = @(Get-GSAdminRoleAssignment -UserKey $UPN -ErrorAction SilentlyContinue)
Add-SbPAMActionLog -Type Info -Message ("Retrieved $($roleAssignments.count) role assignments from Google for $UPN")

#Check if the user is already in this role and remove them if so
if ($RoleIDToElevateTo -iin $roleAssignments.RoleId) {
    $roleToBeRemoved = $roleAssignments | Where-Object {$_.RoleId -eq $RoleIDToElevateTo}
    Add-SbPAMActionLog -Type Info -Message ("$UPN has a role assignment for role $RoleIDToElevateTo")
    Add-SbPAMActionLog -Type Info -Message ("Removing $UPN role assignment for role $RoleIDToElevateTo ...")
    Remove-GSAdminRoleAssignment -RoleAssignmentId $roleToBeRemoved.RoleAssignmentId
    Add-SbPAMActionLog -Type Info -Message ("Removed $UPN from role $RoleIDToElevateTo")
}

else {
    Add-SbPAMActionLog -Type Warn -Message ("$UPN was not found to be in role $RoleIDToElevateTo")
}

Now simply assign this policy to yourself and test that it works! The most likely issue will be related to the PSGSuite configuration, but those errors should bubble up fairly clearly into the log for the activity. Again, as stated above, test this in a test environment if you are interested in using it!

Tips

There are a few takeaways I have gotten from this task that I would like to share both as advice to others, and as suggestions/requests to the Netwrix team.

  • You must remember to restart the Netwrix services after installing PSGSuite! Not doing this caused me way more of a headache than I’d like to admit. Random errors related to non-loaded assemblies, missing types, etc. Meanwhile the module worked fine when run from the actual node! My best guess here is that Netwrix is not creating new PowerShell processes for these scripts, but calling them from an initially spawned process, thus not loading in newly added module dependencies.

  • The Add-SbPAMActionLog cmdlet can take the following message type parameters: Info, Warn, Error. This just wasn’t documented anywhere and even when traced in the calling code simply passes the data over to a rest endpoint without any validation from the PowerShell function. While not a big deal, knowing the types of messages to report to users are important!

  • Don’t call exit() from within your custom script. Any call to exit(), with or without a return code, will result in your activity reporting as an error. I had initially setup the scripts to use some earlier exit calls and was confused why Netwrix was throwing errors in my logs even when returning a 0 return code. I did not get to test how it handles a return call, but my guess is that might work better.

  • Troubleshooting these custom PowerShell scripts is really a pain through the Netwrix platform. I would love to hear some insight into how the devs here have the workflow setup for them. Naturally I tested in a normal IDE, but at some point I have to migrate it into the custom action step, assign it to a policy, run the policy, check for errors, and go back. Especially with some of the odd behavior I saw when first importing the PSGSuite module, this workflow slowed me down tremendously.

  • Not as much a tip as an odd issue this whole process has, but note that looking at historical views of this activity will only show the action logs from the removal action step. I do not know why the provisioning step logs just… disappear, but for us that’s not a deal breaker. If any dev wants to chime in why this behavior might happen, or if I am supposed to reference the first actionlog’s ID to maintain it, I’d love to know.
    This was resolved and updated in the code snippets. It seems to have been an issue related to referencing the ActionQueueActionId

Final Thoughts

This has taken me a bit to write up, so I hope someone can either get value from the provided code directly, or from the additional information it/I provide. If anything this does go to show that Netwrix functionality can be extended into a platform not officially supported. I’d love to see official support in the future, but for now, this works for our very specific use case.

Happy holidays to all those celebrating this upcoming weekend!

11 Likes

Hi David,

This is a fantastic writeup and I can’t wait to try it out myself.

Thanks so much for taking the time to write it up in such detail and sharing with the community!

All the best,
Martin

1 Like

Wow this is awesome. Kudos @DavidHenry for the great writeup!

2 Likes

@DavidHenry This is excellent! Thank you so much for engaging with our Community and sharing this with others :+1:

That shouldn’t be happening… So I will need to look into that.

Thanks for the feedback on documentation. We do need more documentation around the internal PowerShell modules. It is on our list!

Excellent write up and thanks for sharing!

Troubleshooting these custom PowerShell scripts is really a pain through the Netwrix platform. I would love to hear some insight into how the devs here have the workflow setup for them. Naturally I tested in a normal IDE, but at some point I have to migrate it into the custom action step, assign it to a policy, run the policy, check for errors, and go back. Especially with some of the odd behavior I saw when first importing the PSGSuite module, this workflow slowed me down tremendously.

A lot of testing is done by mocking the internal NPS calls or starting with some fixed values. Because of the dynamic nature of the values, it is a bit of a challenge to dev and troubleshoot these.

In the future I hope I can create more helpful articles around creating Custom Actions, and even get into creating Reusable Custom Actions

1 Like

I only noticed the historical log oddities as I was posting this so I have not done enough testing to confirm the behavior, so take that with a grain of salt. I did want to mention it for anyone who uses it though as it could impact the historical view. When I am back in the office I will test a bit further to see if this is directly related to THIS custom PowerShell step or ANY custom PowerShell step.

Also, any additional documentation would make a lot of these processes much simpler to create. As a large contributor to my companies internal knowledgebase I totally get how hard it can be to get good documentation created, but the value return is incredibly high, especially when dealing with public documentation.

In terms of reusable custom actions, that would be absolutely spectacular! I would love to be able to abstract the code into a generic function and call that from other custom functions as needed without having to dump a new module/script into the SBPam script directories on all Netwrix nodes. I’d be very curious to find out how these custom PowerShell activity steps are being saved within the Netwrix appliance as well. Are they currently saved into a specific directory on the Netwrix nodes as ps1 files (maybe in another folder with heavily locked down permissions?), or are they saved directly to the DB?

As for everyone else’s responses. I appreciate the kind words and support. Hopefully this community will see others also include their interesting and unique usages of the product for all of us to build off of!

5 Likes

I just did some testing and it seems to have been related to referencing the $ActionQueueActionId in the Add-SbPAMActionLog function. I’m not sure why this would be the case, but simply removing the reference to the ActionQueueActionId resolved the issue and I have updated the above scripts accordingly.

2 Likes

All custom powershell is saved to the database, that allows all the ActionServices to be able to have the right code without a need to copy files around.

2 Likes