Interfacing with Thycotic Secret Server from ServiceNow

A development approach

 

The Thycotic Secret Server API(s) are quirky (to put it politely) and trying to integrate from ServiceNow using the built-in OAuth 2.0 / RESTful API support wasn’t working. I needed a different approach and I, and hopefully you as well, find it to be a much cleaner and easier method of integration.

Step 1: RTFM

Firstly, you should get your hands on a copy of both the Thycotic Secret Server REST API guide as well as the Thycotic Secret Server WebServices (SOAP XML) guide. These are not publicly available and *must* be acquired by contacting Thycotic. Don’t ask me why they follow this archaic practice, but it is the way it is. Even if you don’t have a copy, you can follow this post as I’ll be discuss needed endpoints and data structures but it will only be a fraction of what’s available. You can login and access copies from their developer resources page.

Step 2: Enable WebServices

In your Secret Server web admin page, you need to make sure Admin -> Configuration -> Edit -> Enable Webservices is checked and you have a sensible timeout set. For testing, I generally set the timeout to unlimited and ease it back as it gets closer to production time.

Step 3: Test the APIs with cUrl

Before writing a single line of code in ServiceNow, check to make sure both the REST API and WS API are accessible and able to issue authentication tokens. I generally do this with cUrl but you’re welcome to do this in whatever language or service you want (Postman, for instance).

This is a 2-part test for both APIs that gets an authentication token and then uses that token to make a simple call.

REST API

# Request (REST) - Get OAuth 2.0 access token
curl -k -X 'POST' -d \
  'username=USERNAME&password=PASSWORD&grant_type=password' \
  'https://FQDN/secretserver/oauth2/token'
# Response
{  
  "access_token":"-- LONG ACCESS TOKEN --",
  "token_type":"bearer",
  "expires_in":128849018820
}
				

SOAP API

				
# Request (SOAP) - Get authentication token
curl -k -X 'POST' -d \
  'username=USERNAME&password=PASSWORD&domain=&organization=' \
  'https://FQDN/secretserver/webservices/sswebservice.asmx/Authenticate'

# Response
<?xml version='1.0' encoding='utf-8'?>
<AuthenticateResult xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
                    xmlns:xsd='http://www.w3.org/2001/XMLSchema'
                    xmlns='urn:thesecretserver.com'>
    <Errors />
    <Token>-- LONG ACCESS TOKEN --</Token>
</AuthenticateResult>
				

REST API

				
# Request (REST) - Get a list of secrets
curl -k -X 'GET' \
  -H 'Authorization: Bearer TOKEN' \
  'https://FQDN/SecretServer/api/v1/secrets?take=1'
# Response
{  
  "filter":{  
    "searchText":null,
    "searchField":null,
    "includeInactive":false,
    "includeRestricted":false,
    "secretTemplateId":null,
    "folderId":null,
    "includeSubFolders":false,
    "heartbeatStatus":null,
    "siteId":null
  },
  "skip":0,
  "take":1,
  "total":6,
  "pageCount":6,
  "currentPage":1,
  "batchCount":6,
  "prevSkip":0,
  "nextSkip":1,
  "hasPrev":false,
  "hasNext":true,
  "records":[  
    {  
      "id":4,
      "name":"The Gibson",
      "secretTemplateId":6003,
      "secretTemplateName":"Windows Account",
      "folderId":21,
      "siteId":1,
      "active":true,
      "checkedOut":false,
      "isRestricted":false,
      "isOutOfSync":false,
      "outOfSyncReason":"",
      "lastHeartBeatStatus":"Pending",
      "lastPasswordChangeAttempt":"0001-01-01T00:00:00",
      "responseCodes":null
    }
  ],
  "sortBy":[  

  ],
  "success":true,
  "severity":"None"
}
				

SOAP API

				
# Request (SOAP) - Get a list of secrets
curl -k -X 'POST' -d 'token=TOKEN&searchTerm=' \
  'https://FQDN/secretserver/webservices/sswebservice.asmx/SearchSecretsLegacy'
# Response
<?xml version="1.0" encoding="utf-8"?>
<SearchSecretsResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                     xmlns="urn:thesecretserver.com">
  <Errors />
  <SecretSummaries>
    <SecretSummary>
      <SecretId>4</SecretId>
      <SecretName>The Gibson</SecretName>
      <SecretTypeName>Windows Account</SecretTypeName>
      <SecretTypeId>6003</SecretTypeId>
      <FolderId>21</FolderId>
      <IsRestricted>false</IsRestricted>
    </SecretSummary>
  </SecretSummaries>
</SearchSecretsResult>
				

Step 4: Make an API call from ServiceNow

Now that you’ve tested connectivity and API access using cUrl, it’s time to implement the same calls into ServiceNow server-side Javascript (script include). To get started, you’ll want to create a new “Script Include” in ServiceNow and create a class extending AbstractAjaxProcessor. From there, we can build out a proper class with methods to get API tokens and execute Secret Server API calls with either REST or SOAP.

Skeleton class

/**
 * Represents a Thycotic Secret Server integration
 * @constructor
 */
SecretServerAPI.prototype = Object.extendsObject(AbstractAjaxProcessor, {
  // Class constants
  MID_PROXY: 'Secret Server Proxy MID Server Name',
  SS_URL: 'https://FQDN/secretserver',
  API_ENDPOINT: 'https://FQDN/secretserver/api/v1/',
  API_AUTH_ENDPOINT: 'https://FQDN/secretserver/oauth2/token',
  WS_ENDPOINT: 'https://FQDN/secretserver/webservices/sswebservice.asmx',
  API_USERNAME: 'SS_USERNAME',
  API_PASSWORD: 'SS_PASSWORD',
  
  // Class methods
  // ...
});
				

Adding class methods for our API script include.

The next two sections show how to build methods that interact with the Secret Server API using ServiceNow server-side Javascript. The next section shows getting an API authentication token using either REST or Web Services (SOAP). Note – a REST OAuth2 token can NOT be used to make SOAP calls and vice-versa.

In the examples, I use client.setMIDServer(this.MID_PROXY); in the call. This is only needed if you are access your Secret Server via a ServiceNow MID server / proxy. It can be omitted if ServiceNow has direct access to the Secret Server in use.

The token received from calling these methods will be used to make future API authenticated calls.

				

REST API “get token” method

/**
 * Gets an OAuth 2.0 access token from the Secret Server service. This
 * allows for future authenticated calls to the API.
 * @function
 * @public
 * @returns {String} An OAuth 2.0 token
 */
getOAuth2Token: function() {
  try {
    // Setup request
    var client = new sn_ws.RESTMessageV2();
    client.setHttpMethod('post');
    client.setEndpoint(this.API_AUTH_ENDPOINT);
    client.setRequestHeader('Content-Type',
      'application/x-www-form-urlencoded');
    client.setRequestBody('username=' + this.API_USERNAME +
      '&password=' + this.API_PASSWORD +
      '&grant_type=password');
    client.setHttpTimeout(10000);
    client.setMIDServer(this.MID_PROXY);
    // Make request, decode JSON response, and return the access token
    return (JSON.parse(client.execute().getBody())).access_token;
  } catch (ex) {
    gs.error('Exception: (' + ex + '), ' + ex.getMessage());
    return ex.getMessage();
  }
},
				

REST API “get secret details” method

/**
 * Fetches a single Secret Server secret (full details) by ID
 * @function
 * @private
 * @param {String} token  OAuth 2.0 access token to use
 * @param {Integer} secret_id  Secret ID to fetch
 * @returns {Object} An object describing secret details
 */
getSecretDetails: function(token, secret_id) {
  try {
    var client = new sn_ws.RESTMessageV2();
    client.setHttpMethod('get');
    client.setEndpoint(this.API_ENDPOINT + '/secrets/' + secret_id);
    client.setRequestHeader('Authorization', 'Bearer ' + token);
    client.setHttpTimeout(10000);
    client.setMIDServer(this.MID_PROXY);
    // Make request, decode JSON response, and return the object
    return JSON.parse(client.execute().getBody());
  } catch (ex) {
    gs.error('Exception: (' + ex + '), ' + ex.getMessage());
    return { items: [] };
  }
},
				

SOAP API “get token” method

/**
 * Gets an access token for SOAP / WS API from the Secret Server service.
 * This allows for future authenticated calls to the API.
 * @function
 * @public
 * @returns {String} A token
 */
getWSToken: function() {
  try {
    // Setup request
    // "REST" call to SOAP interface. Living on the edge...
    var client = new sn_ws.RESTMessageV2();
    client.setHttpMethod('get');
    client.setEndpoint(this.WS_ENDPOINT + '/Authenticate');
    client.setQueryParameter('username', this.API_USERNAME);
    client.setQueryParameter('password', this.API_PASSWORD);
    client.setQueryParameter('organization', '');
    client.setQueryParameter('domain', '');
    client.setHttpTimeout(10000);
    client.setMIDServer(this.MID_PROXY);
    // Make request, decode XML response, and return the access token
    return (new XMLHelper(client.execute().getBody())).toObject().Token;
  } catch (ex) {
    gs.error('Exception: (' + ex + '), ' + ex.getMessage());
    return ex.getMessage();
  }
},
				

SOAP API “get secret attachment” method

/**
 * Gets a secret's attachment value by Secret Item ID
 * @function
 * @public
 * @param {Integer} secret_id  Secret ID
 * @param {Integer} secret_item_id  Secret Item ID
 * @returns {Object} An attachment
 */
getSecretAttachment: function(secret_id, secret_item_id) {
  try {
    var wstoken = this.getWSToken();
    // Setup request
    var client = new sn_ws.RESTMessageV2();
    client.setHttpMethod('get');
    client.setEndpoint(this.WS_ENDPOINT + '/DownloadFileAttachmentByItemId');
    client.setQueryParameter('token', wstoken);
    client.setQueryParameter('secretId', secret_id);
    client.setQueryParameter('secretItemId', secret_item_id);
    client.setHttpTimeout(10000);
    client.setMIDServer(this.MID_PROXY);
    // Make request, decode XML response, and return the access token
    var attachment = (new XMLHelper(client.execute().getBody())).toObject();
    // File attachments are base64 encoded by default
    if( !attachment.Errors && attachment.FileAttachment )
      attachment.FileAttachment = GlideStringUtil.base64Decode(
        attachment.FileAttachment);
    return attachment;
  } catch (ex) {
    gs.error('Exception: (' + ex + '), ' + ex.getMessage());
    return ex.getMessage();
  }
},
				

Fin.

This concludes the tutorial on integrating ServiceNow with Secret Server API. You should now know how to make API calls and consume data from Secret Server. You should also now be able to add more calls and use more endpoints using this general model to meet your specific project needs. Thanks for reading!

				

Pin It on Pinterest

Share This