Python SDK Cookbook: Working with Auth Tokens

Many moons ago I wrote about authentication tokens and the iControl REST interface. The TL;DR is that you pass a json blob with the login provider, a username, and a password via an HTTP POST to the /shared/authn/login endpoint and voila!...you get a token back (example below.) In this brief article, I'll show you how to check the tokens currently active in the system and how to update and delete them with the Python SDK.

Let's REST a Little

Before looking at the SDK code, I wanted to back up and look at an example by working with a simple requests session and the iControl REST interface. The json blob for the POST needs to be in this format:


{
  "loginProviderName": "tmos",
  "username": "admin",
  "password": "admin
}

Borrowing from the aforementioned article, I can define a simple function to format the json blob appropriately, make the POST attempt, and return the token:


>>> def get_token(b, url, creds):
...   payload = {}
...   payload['username'] = creds[0]
...   payload['password'] = creds[1]
...   payload['loginProviderName'] = 'tmos'
... 
...   token = b.post(url, json.dumps(payload)).json()['token']['token']
...   return token
... 

Then I can establish a session object and pass that along with the auth login endpoint URL and my amazingly secure credentials. Note that I do indeed get a token back WITHOUT having to instantiate a session with basic auth. This was an issue in an early version of iControl REST support but hasn't been an issue since at least 12.0.


>>> requests.packages.urllib3.disable_warnings()
>>> import json
>>> b = requests.session()
>>> b.headers.update({'Content-Type': 'application/json'})
>>> b.verify = False
>>> get_token(b, 'https://ltm3.test.local/mgmt/shared/authn/login', ('admin', 'admin'))
'COTRPJJ7LWDVSFMMKJVLQW3CMC'

The Python SDK Way

The above code only shows retrieving the token. It does nothing to actually use it for anything. I would have to then insert that token in the X-F5-Auth-Token header for subsequent requests for them to be authenticated. This is all handled automagically in the SDK however with a single kwarg in your ManagementRoot session instantiation:


>>> b1 = ManagementRoot('ltm3.test.local', 'admin', 'admin', token=True, debug=True)
>>> b1.debug_output
["curl -k -X GET https://ltm3.test.local:443/mgmt/tm/sys/ -H 'Accept-Encoding: gzip, deflate' -H 'Accept: */*' -H 'Connection: keep-alive' -H 'User-Agent: python-requests/2.21.0 f5-icontrol-rest-python/1.3.13' -H 'Content-Type: application/json' -H 'X-F5-Auth-Token: 7OZ6YOLTOPCEEALEPJSS6C75IU'"]
>>> b1.icrs.token
'7OZ6YOLTOPCEEALEPJSS6C75IU'

Note above I enabled the debug kwarg as well so the curl equivalent of the call made back to BIG-IP after receiving the token is logged. the X-F5-Auth-Token header is there at the end of the curl request.

Wait a Second, Am I Learning Anything New Here?

Those that have read my earlier work on auth tokens are probably wondering why they're still here as this is all old hat. Fair enough! But now that everyone else is up to speed, the main point of this article is what can you do with the token besides just use it? I'm glad you asked! Whereas the /shared/authn/login endpoint is used to generate a token, it's not a collection or a resource and a GET won't work. But /shared/authz/tokens is very much a collection. Let's take a look.

Display All the Tokens

You can grab all the tokens with a single call using the authz endpoint above and the get_collection method. First, I'll instantiate a session object against two different users, then grab the tokens.

>>> b1 = ManagementRoot('ltm3.test.local', 'admin', 'admin', token=True)
>>> b2 = ManagementRoot('ltm3.test.local', 'jrahm', 'admin', token=True)
>>> tokens = b2.shared.authz.tokens_s.get_collection()

Then I can iterate through the collection and print the token and the user it belongs to.

>>> for token in tokens:
...         print('User: {}, Token: {}'.format(token.userName, token.token))
   
User: admin, Token: GKO6OYQOPUJLN4SCGVH2VSCATY
User: jrahm, Token: ZEA3TTE7MVIDDDBABFH7LEAHBQ


Update the Timeout on a Token

To update the token's timeout, you need to load the resource. Once it's loaded let's key in on the timeout and user attributes before moving on.

>>> b1token = b1.shared.authz.tokens_s.token.load(name=b1.icrs.token)
>>> b1token.timeout
1200
>>> b1token.user
{'link': 'https://localhost/mgmt/shared/authz/users/admin'}

Both of these are critical in updating the timeout as you can see below. Simply updating the timeout attribute alone results in an SDK error:

>>> b1token.timeout = 3600
>>> b1token.update()
Traceback (most recent call last):
 File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/shared/authz.py", line 78, in _validate_user
  assert 'user' in kwargs
AssertionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
 File "<input>", line 1, in <module>
 File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/shared/authz.py", line 65, in update
  self._validate_params(**kwargs)
 File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/shared/authz.py", line 74, in _validate_params
  self._validate_user(**kwargs)
 File "/Users/rahm/Documents/PycharmProjects/f5-common-python/f5/bigip/shared/authz.py", line 82, in _validate_user
  "The 'user' parameter is required when updating."
f5.sdk_exception.MissingUpdateParameter: The 'user' parameter is required when updating.

Once updating the timeout attribute, you need to pass the user attribute as a kwarg in your update call.

>>> b1token.timeout = 3600
>>> b1token.update(user=b1token.user)
>>> b1token.refresh()
>>> b1token.timeout
3600

When you make an update from the SDK the local object is automatically refreshed, but just to make you feel good about it I refreshed the object again before confirming that the timeout was indeed updated.

Delete a Token

This is pretty simple. Once you've loaded the object, you can just use the delete method to remove the token altogether. Interestingly, you do not need to supply the user attribute here to delete the token, only to update the timeout.

>>> b1 = ManagementRoot('ltm3.test.local', 'admin', 'admin', token=True)
>>> b1token = b1.shared.authz.tokens_s.token.load(name=b1.icrs.token)
>>> b1token.delete()
>>> b1token.deleted
True

Delete All the Tokens (DANGER, Will Robinson!)

And for the grand finale in this writeup, we'll destroy all the things! For this example, I started with a session that is not token based so I get a response. If I had used one of my token-based sessions to destroy everything, I'd never the get the refreshed object since the token I was using was deleted.

>>> b = ManagementRoot('ltm3.test.local', 'admin', 'admin')
>>> b2 = ManagementRoot('ltm3.test.local', 'admin', 'admin', token=True)
>>> b3 = ManagementRoot('ltm3.test.local', 'admin', 'admin', token=True)
Then I print the tokens to make sure there are in the system.
>>> tokens = b.shared.authz.tokens_s.get_collection()
>>> for token in tokens:
...   print(token.token)
...   
JYHQZIUVDGWKLLOY7LP6NH7TJU
UNL6EPVUXU35JMMHFZDYWVXUJZ

Now, I can use the delete_collection() method with the options query parameter with the glob-style matching to remove all the tokens and follow that with another check of system tokens, of which there are now none!

>>> z = b.shared.authz.tokens_s.delete_collection(requests_params={'params': 'options=*'})
>>> tokens = b.shared.authz.tokens_s.get_collection()
>>> for token in tokens:
...   print(token.token)
...   

>>>

So there you have it! Honestly, a couple days ago I had no idea the management aspects of the token authentication were in the SDK. I only discovered it after an internal inquiry. I love it when I find new functionality in libraries I help to maintain! Happy coding out there....

Published Oct 17, 2019
Version 1.0
  • I found this article extremely helpful in getting started with automating some of our processes. Thanks for taking the time to post and the being thorough in your explanations and examples for auth tokens.