11 April,19 at 11:51 AM
Introduction
The Centrify Developers website (https://developer.centrify.com) provides resources for developers to accelerate their productivity. A very popular asset that builds on the Centrify Identity Platform's APIs is the Centrify PowerShell samples in GitHub (https://github.com/centrify/centrify-samples-powershell). In this article, we walk you through an example and discuss the steps we took to get the information needed to accomplish this goal.
What you need
To follow along with this post you'll need:
What we need to accomplish
Given an Active Director OU with computer objects, have the script populate a static set in Centrify infrastructure service.
Background
Dissecting the Requirements and Constraints
(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
General Challenges
Toolbox
Script Building Blocks
Prompting for Information and Data Quality
We will be using the Read-Host commandlet to gather information from the user, however, you can quickly think about many possibilities. Ultimately, we can settle for something like:
We will revisit data quality topics as we continue the analysis.
Authenticating to Centrify Infrastructure Service
The developer website devotes several resources to this topic here: https://developer.centrify.com/docs/starting-the-authentication-process however, there are two things that I've struggled with: the REST API concepts and JSON notation. Fortunately that's where my browser searches were useful. I can't pretend that I understand everything in the developer site, but with an implementation probably I'd get better at it.
Armed with the knowledge, I started inpecting the PowerShell samples.
Import-Module C:\Downloads\Samples\module\Centrify.Samples.PowerShell.psm1
The first thing I noticed was the warning. This is because the module is unsigned. This means that my script needs to have the 'execution policy' relaxed before using it.
Once I exposed the module in PowerShell ISE, a commandlet immediately caught my attention:
The Centrify-InteractiveLogin-GetToken looks like a good candidate for this script.
I noticed that if I ran it without any parameters, it would prompt me for a username, but it would try cloud.centrify.com. The -endpoint parameter specifies the URL of the service in question.
Here's the output when you test it correctly:
Centrify-InteractiveLogin-GetToken -endpoint https://example.my.centrify.com -username admin@tenant.suffix Mechanism 0 => Email... @example.com Mechanism 1 => SMS... XXX-0734 Mechanism 2 => Security Question Choose mechanism: 1 Mechanism 0 => Password Name Value ---- ----- BearerToken TRUNCATEDVERYLONGSTRING Endpoint https://example.my.centrify.com
Notice that I was prompted for MFA, and then for the password. This is consistent with the policy settings of that service address that will prompt for MFA, then for the password:
The commandlet returns an object with 2 attributes:
A 'BearerToken' or string that contains authorization information that can be used in subsequent requests.
The 'endpoint' or service address (URL) for the token.
Input quality #1 - need to make sure that we "ping" the service address (or endpoint) supplied by the user.
After seeing this implementation of authentication against Infrastructure Service, I re-read the "Authentication Quick Start" and understood the process much better. The Centrify-InteractiveLogin-GetToken abstracts many of the elements of this API and make it easy to use.
I also noticed another commandlet called Centrify-InvokeREST that looked very familiar to the "Invoke-RestMethod" commandlet. This one takes endpoint and token (at this point these are a must have), but also takes
Looks like this will be quite useful.
Querying Information from Centrify Infrastructure Service
My next stop in the Developer's website was the 'Using Queries' section: https://developer.centrify.com/docs/use-queries getting-started topic since I figured I need to be able to obtain information about the systems that I needed to update.
The site pointed me to this method "/redrock/query" that takes two parameters in JSON format: script (the SQL query) and args (parameters like page size, etc) ; to keep things simple, we'll only be using the script argument. Here's the format from the example:
{"Script":"Select ID, Username from User ORDER BY Username COLLATE NOCASE"}
I learned that you can create an object in PowerShell called a hashtable to store key/value pairs (which translate very well into JSON).
Here are the building-blocks for this method
Two problems solved. For the Hashtable, I found these references:
Let's now try to get the same results as the query above using the /redrock/query API.
$endpoint = 'https://example.my.centrify.com' $user = 'user@example.com' $token = Centrify-InteractiveLogin-GetToken -endpoint $endpoint -username $user $api = '/redrock/query' $obj = @{} $obj.script = "select ID from server" $query = Centrify-InvokeREST -endpoint $endpoint -token $token -method $api -objectContent $obj
The contents of the query object don't reveal the result. You have to go into the object JSON response to expose the rows with the results:
PS C:\> $query success : True Result : @{IsAggregate=False; Count=4; Columns=System.Object[]; FullCount=4; Results=System.Object[]; ReturnID=} Message : MessageID : Exception : ErrorID : ErrorCode : InnerExceptions : PS C:\> $query.Result.Results.Row.ID 257b977d-9dfd-4498-bd92-8ed8ca949a82 28b58c16-1c3d-42a2-98f7-e218b88bb58d 5d85c7c2-2cbe-4494-8b5d-568fe3a9fb2a a02937f4-de5b-4c2b-805a-a61f90b661e0
/redrock/query is a very good API to learn the data structures in Centrify Infrastructure Service. With my new found knowledge, I started testing many of the "Resource Management" APIs: https://developer.centrify.com/v1.2/reference
For example, here's how to add a UNIX system called dummy:
$sys =@{} $sys.Name = "dummy" $sys.FQDN = "dummy.example.com" $sys.ComputerClass = 'Unix' $sys.SessionType = "Ssh" $srvadd = Centrify-InvokeREST -endpoint $endpoint -token $token -method '/ServerManage/AddResource' -objectContent $sys
The response is:
PS C:\> $srvadd success : True Result : 352d883d-fdb3-4ee1-ac0b-bf87006e7114
There are other (empty) attributes sent, but in this case we have an indication of success and the ID of the newly-created system.
Now we have a methodology:
***************************************************
***************************************************
We have determined that we need to update a resource to be added to a set.
Problem: When reviewing the update resource (link) we realized that there is no method to update a system with set membership.
Using the GUI as a detective tool
Where to go next? This takes us to the Google Chrome Developer Tools. We can use those tools in conjunctionw with the graphical interface to determine the API calls behind a GUI action.
In my case, I added a system to a set manually and discovered the following:
There is another API called /collections that contains:
{"id":"a0382f4c-6083-4ed9-845b-9873edbea237","add":[{"MemberType":"Row","Table":"Server","Key":"352d883d-fdb3-4ee1-ac0b-bf87006e7114"}]}I tried to look for a table or view called "Collections" in Core Services > Reports and was unable to find one.
$obj = @{} $obj.ObjectType= $setType $obj.Name= $setName $obj.NoBuiltins= 'true' $coll = Centrify-InvokeREST -endpoint $endpoint -token $token -method "/Collection/GetObjectCollectionsAndFilters" -objectContent $obj -Verbose $true $collresults = $coll.Result.Results.Row.CountThen we need to compare until we find the matching set:
If ($collresults -gt 1) { # uses the where clause if multiple results $collid = $coll.Result.Results.Row.Where({$PSItem.Name -eq $obj.Name}).ID } Else { $collid = $coll.Result.Results.Row.ID }
The main directives
Most of the action will happen inside a foreach loop. The key here is to iterate through a list of AD computer object FQDNs, check if they exist in Centrify Infrastructure Service and add them to the target set specified by the user.
Because it's possible the system was added with an IP address, the routine has expansion capabilities.
The GetServerbyFqdn function uses the /redrock/query API to search the converted to uppercase FQDNs against the Server table.
foreach ($computer in $computers) { # cleanup values $sysid = $null $obj = $null $setAdd = $null # determine if system is in CPS Write-Host 'Determining if' $computer 'is in Privilege Service...' $sysid = (Centrify-GetServerIdByFqdn -endpoint $endpoint -fqdn $computer -token $token -ErrorAction SilentlyContinue) # if there are results, process If ($sysid -ne $null) { Write-Host $computer $sysid 'found in Centrify Infrastructure Service. Adding to' $settype 'set' $setname in $endpoint # JSON payload (leaning carriage returns) $obj = ('{"id":"').replace("`n","")+''+$setid+'"'+ (',"add":[{"MemberType":"Row","Table":"Server","Key":"').replace("`n","")+''+$sysid+'' + '"}]}' $obj = ConvertFrom-Json $obj # adds to set $setAdd = Centrify-InvokeRest -Endpoint $endpoint -Method "/Collection/UpdateMembersCollection" -ObjectContent $obj -Token $token -Verbose:$enableVerbose If ($setAdd.success -ne 'True') { Write-Host 'Unable to add to set' $setAdd.ErrorCode } Else { Write-Host $computer $sysid 'succesfully added to' $settype 'set' $setname 'in' $endpoint } } Else { Write-Host $computer 'was not found as FQDN in CPS. Will be listed at end.' $unresolved += $computer } }
Sample Output
PS C:\> AddADOUComputerToSetInteractive.ps1 What's the target service URL (e.g. myurl.my.centrify.com)?: vault.centrify.vms Username for https://vault.centrify.vms: user@vault.demo Mechanism 0 => Security Question Mechanism 0 => Password Target OU: Computers Target SET: Test Determining if centos7.centrify.vms is in Privilege Service... centos7.centrify.vms 5d85c7c2-2cbe-4494-8b5d-568fe3a9fb2a found in Centrify Infrastructure Service. Adding to Server set in https://vault.centrify.vms centos7.centrify.vms 5d85c7c2-2cbe-4494-8b5d-568fe3a9fb2a succesfully added to Server set in https://vault.centrify.vms Determining if engcen6.centrify.vms is in Privilege Service... engcen6.centrify.vms 257b977d-9dfd-4498-bd92-8ed8ca949a82 found in Centrify Infrastructure Service. Adding to Server set in https://vault.centrify.vms engcen6.centrify.vms 257b977d-9dfd-4498-bd92-8ed8ca949a82 succesfully added to Server set in https://vault.centrify.vms centos69.centrify.vms was not found as FQDN in CPS. Will be listed at end. The systems not found are: centos69.centrify.vms
In this case, the program found an OU called Computers (not the default computers container) and 2 systems were added to the target set (Test) given that they were onboarded with a matching FQDN on AD. The unmatched system was entered using IP addresses.
Adjustments and improvements
Conclusion
Using this project as a motivation, I have learned a lot about the power of the APIs and the excellent resource available in the Centrify Developers site. If you are in IT ops, Security or Automation, these tools are invaluable to increase productivity.
Attachments: AddADComputerToFromOUInteractive.zip