Manual Sync of VMware Identity Manager (vIDM) via API

VMware’s Identity Manager. 🙁

A product which causes me more hassle than it’s worth (in my opinion!). I get why it’s there and when you have a mix of SAML / passwords / certs etc then its fine, but for simple AD/LDAP authentication it really comes unstuck.

I have encountered problems in both vRA 7.x and now with vRA 8.x where I have custom actions (XaaS / ABX ) which build out infrastructure – these being new business groups in 7.x and new project constructs in 8.x. As part of these XaaS functions new AD groups are built and mapped as part of the flow in order to control access to these elements.

Now here’s the problem. You have just created a new security group in AD and mapped it to a business group or project – but the vIDM has no clue this group exists. The result is that these groups / projects will be unusable until the vIDM does its next directory sync which, if the sync schedule is at the shortest interval, could be an hour away. This isn’t great if you want to provide customers instant access to their groups and generally a good product flow.

To get around this within code I build out all AD constructs as the first components (in vRA 8.x I use ldap3 to do this, see my other post for details on this – https://tg-test100.com/using-ldap3-python-module-to-manage-active-directory )

Once these are built, it is possible to login to the vIDM via the API and fire off a sync cycle which will then pull in these new groups so they can be mapped to vRA components.

Login to vIDM

The first step is to connect to the Identity Manager and grab an authentication token back from the response.

import requests
import json
import time

session = requests.Session()
session.headers.update({"content-type" : "application/json"})
session.headers.update({"accept" : "application/json"})
idm_login_url = 'https://<vIDM_IP_ADDRESS>/SAAS/API/1.0/REST/auth/system/login'
idm_login_body =  {
        'username': 'tgadmin',
        'password': 'password_placeholder',
        'issueToken': 'true'
    }
idm_login_body_json = json.dumps(idm_login_body)
response = session.post(idm_login_url, data = idm_login_body_json, verify=False)
print("Logging into IDM to obtain idm_token")
print(response.status_code)
print(response.content)
if response.status_code == 200:
    json_response = json.loads(response.content)
    idm_token = json_response['sessionToken']
    if idm_token:
       print("Token obtained")

The output….

>> Logging into IDM to obtain idm_token
>> 200
>> {"id":null,"sessionToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI5ODE0OTAxMC1hMzRkLTRmM2QtOTk0My1hYmM3ZDczNGQ3ODgiLCJwcm4iOiJ0Z2FkbWluQFRHVklETTAyIiwiZG9tYWluIjoiU3lzdGVtIERvbWFpbiIsInVzZXJfaWQiOiI4IiwiYXV0aF90aW1lIjoxNjA4MjA3Mzg2LCJpc3MiOiJodHRwczovLzE3Mi4xNi4xMC4xMTcvU0FBUy9hdXRoIiwiYXVkIjoiaHR0cHM6Ly8xNzIuMTYuMTAuMTE3L1NBQVMvYXV0aC9vYXV0aHRva2VuIiwiY3R4IjoiW3tcIm10ZFwiOlwidXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnRcIixcImlhdFwiOjE2MDgyMDczODYsXCJpZFwiOjE1fV0iLCJzY3AiOiJwcm9maWxlIGFkbWluIHVzZXIgZW1haWwiLCJpZHAiOiIwIiwiZW1sIjoiY29uZmlndXNlckB2bXdhcmUuY29tIiwiY2lkIjoiIiwiZGlkIjoiIiwid2lkIjoiIiwicnVsZXMiOnsiZXhwaXJ5IjoxNjA4MjA5MTg2LCJydWxlcyI6W3sicmVzb3VyY2VzIjpbIioiXSwiYWN0aW9ucyI6WyJhY3M6cmVhZFJ1bGVTZXRzIiwiZG06cmVhZCIsInVnOnJlYWQiLCJlbnQ6cmVhZCIsImN0ZzpyZWFkIiwidG50czpyZWFkIiwicnB0OioiXSwiY29uZGl0aW9ucyI6bnVsbH0seyJyZXNvdXJjZXMiOlsiKiJdLCJhY3Rpb25zIjpbIioiXSwiY29uZGl0aW9ucyI6bnVsbH1dLCJsaW5rIjoiaHR0cHM6Ly90Z3ZpZG0wMi50Zy5sb2NhbDo0NDMvYWNzL3J1bGVzL21lIn0sImV4cCI6MTYwODIzNjE4NiwiaWF0IjoxNjA4MjA3Mzg2LCJzdWIiOiI3NjU2ODUxNi03NzJmLTRmYWEtYmVjYi04OGI2MTM3ZWE3NGMiLCJwcm5fdHlwZSI6IlVTRVIifQ.AzaFB02dTDNAW4oUNlBrXGQUF1IUqwbo62PArxCj_xv9MLNbnrNPZcPoFj74r_NT-pyrRXkmOTPxPFImG4Pac5VoJJFYxdx0bOZujJu2H645Yx7biXUFAH36mej2fZX2lKi0nHMcyCr62B-Sjeml8S8HzgpLrAU3SR0LLxyHMlQYOZ143nBeG4YfBkEYWmY93Bc3_cjiuCFgG9WfTZA8RTjPkGlkChl9Bk9vExl5H2fCgr_SJJFp40myLgurbTdZYc4LMUIfBksoz4RmazHQVE3gjkR91j5n5HXqdfjn9S1_9JcnokN9qYiMdY95PJ5Az96xNbLlhJpPMqL8o3J0Pw"
,"firstName":null,"lastName":null,"admin":false,"serverUrl":null,"signingCert":null}
>> Token obtained

Obtaining your directory ID

To execute against your configured directory within the vIDM you will need its ID.

If you are looking for a dirty shortcut, just grab the directory ID from the browser using developer tools.

Open your directory and get trace the network connection

However, the better way to get this ID is programatically, especially if you have multiple directories and tennants configured within the IDM. This is achieved as follows:

# Setup the auth header using the token found above. 
# Setup the accept header to be very specific to allow listing of directories

session.headers.update({"accept" : "application/vnd.vmware.horizon.manager.connector.management.directory.list+json"})
basic_auth_header = "Bearer " + idm_token
session.headers.update({"authorization" : basic_auth_header})

# Setup the URL for querying
# Run a get request
idm_dirquery_url = 'https://<VIDM_ADDRESS>/SAAS/jersey/manager/api/connectormanagement/directoryconfigs'
response = session.get(idm_dirquery_url)
print(response.status_code)
print(response.content)

## Output. Iterate through the directories and extract the directoryId
## of interest for the next step
>> {"items":[{"type":"LOCAL_DIRECTORY","name":"System Directory","directoryId":
"6c161620-ec09-4406-aeca-8cf37184f2a3","userstoreId":"369df052-d38a-4783-9182-df
dd6695795a","countDomains":1,"deleteInProgress":false,"syncConfigurationEnabled"
:false,"_links":{"hw-dir-connectors":{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/6c161620-ec09-4406-aeca-8cf37184f2a3/connectors"},
"self":{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/6c161620-ec09-4406-aeca-8cf37184f2a3"}}},
{"type":"ACTIVE_DIRECTORY_LDAP","name":"TG.LOCAL","directoryId":"1f593d6b-134b-42a6-aeb5-f817f0e2919d",
"userstoreId":"1fcebfd0-a0b0-4b2c-b617-3000327b6a20","countDomains":1,"deleteInProgress":
false,"syncConfigurationEnabled":true,"_links":{"hw-sync":
{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/1f593d6b-134b-42a6-aeb5-f817f0e2919d/syncprofile/sync"},
"hw-dir-connectors":{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/1f593d6b-134b-42a6-aeb5-f817f0e2919d/connectors"},
"hw-dir-sync-executions":{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/1f593d6b-134b-42a6-aeb5-f817f0e2919d/syncexecutions"},
"self":{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/1f593d6b-134b-42a6-aeb5-f817f0e2919d"}}}],
"_links":{"self":{"href":"/SAAS/jersey/manager/api/connectormanagement/directoryconfigs"}}}

Running the Sync Job

Now I have the directory ID along with a valid login I can execute the sync:

Update your headers again before running the HTTP Post.

“accept” : “application/vnd.vmware.horizon.v1.0+json”
“content-type” : “application/vnd.vmware.horizon.manager.connector.management.directory.sync.profile.sync+json”

# Custom headers for the IDM
session.headers.update({"accept" : "application/vnd.vmware.horizon.v1.0+json"})
session.headers.update({"content-type" : "application/vnd.vmware.horizon.manager.connector.management.directory.sync.profile.sync+json"})
basic_auth_header = "Bearer " + idm_token
session.headers.update({"authorization" : basic_auth_header})

#Kick of IDM Sync, then sleep to let it complete. This depends how big your
#Directory is as to how long it will take
idm_sync_url = 'https://<VIDM_IP_ADDRESS>/SAAS/jersey/manager/api/connectormanagement/directoryconfigs/<DIRECTORY_ID>/syncprofile/sync'
idm_safeguard_body = {
    'ignoreSafeguards':'false'
}
idm_safeguard_body_json = json.dumps(idm_safeguard_body)
response = session.post(idm_sync_url, data = idm_safeguard_body_json)
if response.status_code == 200:
   print("Sync kicked off successfully, sleeping for 60 seconds")
   time.sleep(60)

And that is it.

I have found this to be a vital process when starting to layer extensibility into the vRA suite.

Hopefully it helps someone.

Side Note…vRA Cloud

Before I forget. If you are using vRA Cloud rather than an on-premise installation, this is still possible. If you have a locally installed vIDM connector which is mapped back to VMware Cloud Services so to allow centralised authentication, this same problem exists but can be overcome the same way…just adjust your URL in the code as follows:

HTTPS://<DOMAIN-IDENTIFIER>-CSP.WORKSPACEONEACCESS.COM

ie https://tg-local-csp.worksapceoneaccess.com/SAAS/API/1.0/REST/auth/system/login

References:

https://code.vmware.com/apis/57/idm

1 Comment

  1. Dobri Piperkov Permalink

    If you need to monitor the status of the update you will have to implement a polling mechanism to the same URI as the for the kick off sync but with GET request, Example:
    If you are using :
    idm_sync_url = ‘https:///SAAS/jersey/manager/api/connectormanagement/directoryconfigs//syncprofile/sync’
    idm_safeguard_body = {
    ‘ignoreSafeguards’:’false’
    }
    idm_safeguard_body_json = json.dumps(idm_safeguard_body)
    response = session.post(idm_sync_url, data = idm_safeguard_body_json)

    then after you see that the response status is 200 you can start polling at
    response = session.get(‘https:///SAAS/jersey/manager/api/connectormanagement/directoryconfigs//syncprofile/sync’)

    Reply

Leave a Reply