Thycotic Python API

What is Thycotic?

Thycotic is a security application that allows a company to secure and manage their passwords and privileged accounts.

Thycotic REST API

Not Documented Well

The REST API for Thycotic works very well, but the documentation is very vague could use more detail like:

  • What needs to be in the Token Request?
    • What is in the Header?
      • What Fields?
    • What is the token request URL/URI?
    • Is it a GET, a POST, or a PUT?

All of this and more was derived from sample code they have on their website.

  • One is in Perl
  • Other is in Powershell

Together, I was able to piece together the “tea leaves” and get a password (and lots of brute force)

The Setup

  • Since I made this a python class, you have to instantiate it first.
    • If you just run the code by itself, you can just run it and get results
      • You could also just rip is apart and make it your own.
    • Just modify the bottom by entering your secret ID and or what you want to search for.
    if __name__ == '__main__':
                username = raw_input('Please enter username: ')
                password = getpass.getpass("password: ")
                secureV = Secure(username, password)
                # Enter ID of secret this will search for ID 12345
                secureV.getSecretById('12345')
                # Enter search string to print name and secret ID. This will search for lan gen
                secureV.getSearchSecrets('lan gen')
    
            
  • Get an Oauth2 token by using a username and password.
  • Use that token or string to authenticate all subsequent requests.

How Do I Find the Secret ID?

  • Use the web interface
    • Search for the secret you want
    • Select it and view it

    • Then extract it from the URL. The below example Secret ID is 27510
    https://FQDN.foo.com/secretserver/SecretView.aspx?secretid=27510
            
  • The Search API function or method could also be used.
    • This is demonstrated later in this post.

Python Modules Required

  • requests – talks HTTP
  • json – translates what is returned from the web server to the python application into key, value pairs (or dictionaries)

Get a Token

First we need to get an authorization token before we can GET, POST, PUT any data.

  • Required Input
    • username
    • password
  • URI for Token requests
/oauth2/token
Or
Https://FQDn.foo.com/secretserver/oauth2/token
  • Token request is a POST

The Code Snippet

Below is the code for the class I created that will get an access token (or authentication token). It’s a little out of context, but for reference see the code in its entirety at the bottom of this post.

 #server fully qualified domain name
    serverFQDN = 'FQDN.foo.com'
    # Where your trycotic server application is installed
    appPath = '/secretserver'

    def __init__(self, username, password):

        self.username = username
        self.password = password
        url = "https://{}{}/oauth2/token".format(self.serverFQDN, self.appPath)

        payload = {'username': self.username,
                   'password': self.password,
                   'grant_type': 'password'

                   }
        headers = {
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded',
        }

        response = requests.request("POST", url, data=payload, headers=headers)

        self.token = json.loads(response.text)['access_token']

Get a Password for a Secret by ID

After a token has been acquired, GET, POST, and PUT requests are now possible.

In order to find out a password of a secret, the secret ID must be known. How do you find a secret ID? See the above instructions (there’s two ways).

Required Input

  • Need secret ID
  • Once you have that, just send it to getSecretByID(secretId)

The Code Snippet

  • The code GET request, retrieves the secret object based on ID
  • Loops through the secret “items” or attributes
  • Looks for “isPassword” equal to ‘true’
  • then returns or makes self.secretPw equal to “itemValue”
    • which is the password
 def getSecretById(self, secretId):
        url = "https://{}{}/api/v1/secrets/{}".format(self.serverFQDN, self.appPath, secretId)
        headers = {

            'authorization': "Bearer {}".format(self.token),
            # 'Accept-Encoding': 'gzip,deflate',
            'Accept': 'application/json',
            # 'Host': 'api.cisco.com'
        }

        response = requests.request("GET", url, headers=headers)

        secret = json.loads(response.text)

        for item in secret['items']:
            if item['isPassword']:
                self.secretPw = item['itemValue']
                break

Search for a Secret ID

If you do not know the secret ID and want to use the API to get it, use this function or method.

Required Input

  • searchText – What ever you want to search for as if the search was taking place in the web UI
    • Search for name of of secret
    • text in description
    • anything really as this searches all fields

To Run It

If you edit the bottom portion of the code before you run it, it will print the IDs of each secret name if finds in the search.

Example of Searching For a Secret ID

  • Edit this piece of the code
if __name__ == '__main__':
    username = raw_input('Please enter username: ')
    password = getpass.getpass("password: ")
    secureV = Secure(username, password)
    # Enter ID of secret
    secureV.getSecretById('12345')
    # Enter search string to print name and secret ID
    secureV.getSearchSecrets('lan gen')
  • This example searches for “lan gen”
 # Enter search string to print name and secret ID
    secureV.getSearchSecrets('lan gen')
  • Edit the “lan gen” portion of the code to search for the desired secret ID

The Code Snippet

This code prints to the python console the name of the secret and the secret ID

 def getSearchSecrets(self, searchText):
        searchFilter = '?filter.includeRestricted=true&filter.includeSubFolders=true&filter.searchtext={}'.format(
            searchText)

        url = "https://{}{}/api/v1/secrets{}".format(self.serverFQDN, self.appPath, searchFilter)
        headers = {
            'authorization': "Bearer {}".format(self.token),
            'Accept': 'application/json'
        }

        response = requests.request("GET", url, headers=headers)

        self.allSecrets = json.loads(response.text)

        for secret in self.allSecrets['records']:
            print('Name={}->ID={}'.format(secret['name'], secret['id']))

Printed Output of Secret IDs

Output of Printed Secret IDs

Name=LAN General Passwords->ID=27510
Name=LAN General Passwords->ID=27511

Could the “Request a Password by Secret ID ” and “Search for a Secret ID” be Combined?

Of course they could, but the attempt was to put some security in this app. It would not be the best idea for a junior engineer to run something like that and display all of your passwords on their monitor and walk away. Since they are a junior engineer, they often forget to lock their screen on their computer.

Hacker: Whoa! Look at them passwords.

(sound of a camera click)

All of the Code

import requests
import json
import getpass


class Secure(object):

    #server fully qualified domain name
    serverFQDN = 'FQDN.foo.com'
    # Where your trycotic server application is installed
    appPath = '/secretserver'

    def __init__(self, username, password):

        self.username = username
        self.password = password
        url = "https://{}{}/oauth2/token".format(self.serverFQDN, self.appPath)

        payload = {'username': self.username,
                   'password': self.password,
                   'grant_type': 'password'

                   }
        headers = {
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded'
        }

        response = requests.request("POST", url, data=payload, headers=headers)

        self.token = json.loads(response.text)['access_token']

    def getSecretById(self, secretId):
        url = "https://{}{}/api/v1/secrets/{}".format(self.serverFQDN, self.appPath, secretId)
        headers = {
            'authorization': "Bearer {}".format(self.token),
            'Accept': 'application/json'
        }

        response = requests.request("GET", url, headers=headers)

        secret = json.loads(response.text)

        for item in secret['items']:
            if item['isPassword']:
                self.secretPw = item['itemValue']
                break




    def getSearchSecrets(self, searchText):
        searchFilter = '?filter.includeRestricted=true&filter.includeSubFolders=true&filter.searchtext={}'.format(
            searchText)

        url = "https://{}{}/api/v1/secrets{}".format(self.serverFQDN, self.appPath, searchFilter)
        headers = {
            'authorization': "Bearer {}".format(self.token),
            'Accept': 'application/json'
        }

        response = requests.request("GET", url, headers=headers)

        self.allSecrets = json.loads(response.text)

        for secret in self.allSecrets['records']:
            print('Name={}->ID={}'.format(secret['name'], secret['id']))

if __name__ == '__main__':
    username = raw_input('Please enter username: ')
    password = getpass.getpass("password: ")
    secureV = Secure(username, password)
    # Enter ID of secret
    secureV.getSecretById('12345')
    # Enter search string to print name and secret ID
    secureV.getSearchSecrets('lan gen')


Running The Code

This will give display a prompt like…

Please enter username: joesecurity
password: ********

It will output the secret name and secret ID.

Resources

REST API Perl Scripts

REST API PowerShell Scripts – Getting Started

About Daniel Fredrick

Technology enthusiast, Programmer, Network Engineer CCIE# 17094

View all posts by Daniel Fredrick →

10 Comments on “Thycotic Python API”

  1. Hi Dan, thanks for the example! It was super helpful! However, I ran into a problem where the token response I got back is always in text/html, and therefore I couldn’t json.loads it. I ended up having to try to parse the XML (which I haven’t been successful). Curious if you have seen this?

    I am using their cloud instance btw, and it is version 10.5. Not sure if the version plays a difference. When you tried your code, was it against 10.5 or 10.3?

    1. No problem, I am made it because there was really no good examples out there.

      I am running 10.5 non-cloud version.

      What is your content-type that you specify in the header? Do you have an example of your request?

      Also, stay tuned, I will be posting “how to create new secrets in Thycotic via python API”.

      1. Hi Dan, thanks for the reply! I figured out what was my problem. Turns out that for the cloud instance, the correct uri to use is “https://domain.com/oauth2/token”, not “https://domain.com/SecretServer/oauth2/token”. The secretserver part is only for the on-prem product, not cloud product 🙂

        Looking forward to your further Thycotic example!

        1. Thanks for the info! I am glad you figured it out. When you have a non-cloud instance you have to explicitly specify the “application path”. The “default” is “SecretServer”. But it can be changed to anything. I am not sure we will change to the cloud from on premise anytime soon, but everybody is going to the “cloud”, so who knows.

  2. Hi Daniel,

    Great post. Super helpful. I have a question:
    I am trying to verify the API’s on postman. I am able to successfully log-in using the oauth2 api. Using the bearer token, I am trying to make some API’s calls. However, when I try to GET a secret using the secret ID, I keep getting 500 Internal server error with nothing informative in response except:

    {
    “message”: “An error has occurred.”
    }

    In short, none of the API requests are working besides authorization.
    Is there anything needed to be done on the server side for the API’s to work? Seems like the requests are not even hitting the server. Everything works perfect on the GUI.
    Would appreciate your help.

    1. Thanks! The API is really tough, but it sounds like you may not have access to that secret or do not have access to the API. I did not have to setup the API access as I am just an End user. The error messages are really awful, I know. Most of the time that I got messages like that it was syntax or permissions. If you get an more details about your error message or setup please post it so we all can learn!

  3. Hi All,

    I am getting below error at the time of generating token. I am using cloud secret server.

    {“error”:”Login Failed”}
    400

    Using following things :

    URL : https://domain.ss.com/oauth2/token

    payload ={
    ‘username’:username,
    ‘password’:password,
    ‘grant_type’:”password”
    }
    headers ={‘Accept’:’application/json’,
    ‘Content-Type’:’application/x-www-form-urlencoded’,
    }
    response=requests.request(“POST”, url, data=payload, headers=headers)

    ———-
    Thanks in advance !

    1. Late reply, I know. Did you figure it out? What I found is that if you are using AD domains, you have to include that in the username. For example, “myADdomain\username”.

  4. Thanks for writing this up. I know it’s been a few years since you have, but this is the closest I came to finding a way to access the API with Python. This has been a headache.

    I get the following exception: self.token = json.loads(response.txt)[‘access_token’] KeyError: ‘access_token’

    I am using python 3.8.2, Non cloud version of SS.

    1. Late reply, I know, but looks like you have a typo (which you have probably found already). It should be response.text, not response.txt

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.