Demystifying iControl REST Part 6: Token-Based Authentication
iControl REST. It’s iControl SOAP’s baby, brother, introduced back in TMOS version 11.4 as an early access feature but released fully in version 11.5.
Several articles on basic usage have been written on iControl REST so the intent here isn’t basic use, but rather to demystify some of the finer details of using the API. This article will cover the details on how to retrieve and use an authentication token from the BIG-IP using iControl REST and the python programming language. This token is used in place of basic authentication on API calls, which is a requirement for external authentication. Note that for configuration changes, version 12.0 or higher is required as earlier versions will trigger an un-authorized error.
The tacacs config in this article is dependent on a version that I am no longer able to get installed on a modern linux flavor. Instead, try this Dockerized tacacs+ server for your testing.
The Fine Print
The details of the token provider are here in the wiki. We’ll focus on a provider not listed there: tmos. This provider instructs the API interface to use the provider that is configured in tmos. For this article, I’ve configured a tacacs server and the BIG-IP with custom remote roles as shown below to show BIG-IP version 12’s iControl REST support for remote authentication and authorization. Details for how this configuration works can be found in the tacacs+ article I wrote a while back.
BIG-IP tacacs+ configuration
auth remote-role { role-info { adm { attribute F5-LTM-User-Info-1=adm console %F5-LTM-User-Console line-order 1 role %F5-LTM-User-Role user-partition %F5-LTM-User-Partition } mgr { attribute F5-LTM-User-Info-1=mgr console %F5-LTM-User-Console line-order 2 role %F5-LTM-User-Role user-partition %F5-LTM-User-Partition } } } auth remote-user { } auth source { type tacacs } auth tacacs system-auth { debug enabled protocol ip secret $M$Zq$T2SNeIqxi29CAfShLLqw8Q== servers { 172.16.44.20 } service ppp }
Tacacs+ Server configuration
id = tac_plus { debug = PACKET AUTHEN AUTHOR access log = /var/log/access.log accounting log = /var/log/acct.log host = world { address = ::/0 prompt = "\nAuthorized Access Only!\nTACACS+ Login\n" key = devcentral } group = adm { service = ppp { protocol = ip { set F5-LTM-User-Info-1 = adm set F5-LTM-User-Console = 1 set F5-LTM-User-Role = 0 set F5-LTM-User-Partition = all } } } group = mgr { service = ppp { protocol = ip { set F5-LTM-User-Info-1 = mgr set F5-LTM-User-Console = 1 set F5-LTM-User-Role = 100 set F5-LTM-User-Partition = all } } } user = user_admin { password = clear letmein00 member = adm } user = user_mgr { password = clear letmein00 member = mgr } }
Basic Requirements
Before we look at code, however, let’s take a look at the json payload requirements, followed by response data from a query using Chrome’s Advanced REST Client plugin. First, since we are sending json payload, we need to add the Content-Type: application/json header to the query. The payload we are sending with the post looks like this:
{ "username": "remote_auth_user", "password": "remote_auth_password", "loginProviderName": "tmos" }
You submit the same remote authentication credentials in the initial basic authentication as well, no need to have access to the default admin account credentials. A successful query for a token returns data like this:
{ username: "user_admin" loginReference: { link: "https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/login" }- token: { uuid: "4d1bd79f-dca7-406b-8627-3ad262628f31" name: "5C0F982A0BF37CBE5DE2CB8313102A494A4759E5704371B77D7E35ADBE4AAC33184EB3C5117D94FAFA054B7DB7F02539F6550F8D4FA25C4BFF1145287E93F70D" token: "5C0F982A0BF37CBE5DE2CB8313102A494A4759E5704371B77D7E35ADBE4AAC33184EB3C5117D94FAFA054B7DB7F02539F6550F8D4FA25C4BFF1145287E93F70D" userName: "user_admin" user: { link: "https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/users/34ba3932-bfa3-4738-9d55-c81a1c783619" }- groupReferences: [1] 0: { link: "https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/user-groups/21232f29-7a57-35a7-8389-4a0e4a801fc3" }- - timeout: 1200 startTime: "2015-11-17T19:38:50.415-0800" address: "172.16.44.1" partition: "[All]" generation: 1 lastUpdateMicros: 1447817930414518 expirationMicros: 1447819130415000 kind: "shared:authz:tokens:authtokenitemstate" selfLink: "https://localhost/mgmt/shared/authz/tokens/4d1bd79f-dca7-406b-8627-3ad262628f31" }- generation: 0 lastUpdateMicros: 0 }
Among many other fields, you can see the token field with a very long hexadecimal token. That’s what we need to authenticate future API calls.
Requesting the token programmatically
In order to request the token, you first have to supply basic auth credentials like normal. This is currently required to access the /mgmt/shared/authn/login API location. The basic workflow is as follows (with line numbers from the code below in parentheses):
- Make a POST request to BIG-IP with basic authentication header and json payload with username, password, and the login provider (9-16, 41-47)
- Remove the basic authentication (49)
- Add the token from the post response to the X-F5-Auth-Token header (50)
- Continue further requests like normal. In this example, we’ll create a pool to verify read/write privileges. (1-6, 52-53)
And here’s the code (in python) to make that happen:
def create_pool(bigip, url, pool): payload = {} payload['name'] = pool pool_config = bigip.post(url, json.dumps(payload)).json() return pool_config def get_token(bigip, url, creds): payload = {} payload['username'] = creds[0] payload['password'] = creds[1] payload['loginProviderName'] = 'tmos' token = bigip.post(url, json.dumps(payload)).json()['token']['token'] return token if __name__ == "__main__": import os, requests, json, argparse, getpass requests.packages.urllib3.disable_warnings() parser = argparse.ArgumentParser(description='Remote Authentication Test - Create Pool') parser.add_argument("host", help='BIG-IP IP or Hostname', ) parser.add_argument("username", help='BIG-IP Username') parser.add_argument("poolname", help='Key/Cert file names (include the path.)') args = vars(parser.parse_args()) hostname = args['host'] username = args['username'] poolname = args['poolname'] print "%s, enter your password: " % args['username'], password = getpass.getpass() url_base = 'https://%s/mgmt' % hostname url_auth = '%s/shared/authn/login' % url_base url_pool = '%s/tm/ltm/pool' % url_base b = requests.session() b.headers.update({'Content-Type':'application/json'}) b.auth = (username, password) b.verify = False token = get_token(b, url_auth, (username, password)) print '\nToken: %s\n' % token b.auth = None b.headers.update({'X-F5-Auth-Token': token}) response = create_pool(b, url_pool, poolname) print '\nNew Pool: %s\n' % response
Running this script from the command line, we get the following response:
FLD-ML-RAHM:scripts rahm$ python remoteauth.py 172.16.44.15 user_admin myNewestPool1 Password: user_admin, enter your password: Token: 2C61FE257C7A8B6E49C74864240E8C3D3592FDE9DA3007618CE11915F1183BF9FBAF00D09F61DE15FCE9CAB2DC2ACC165CBA3721362014807A9BF4DEA90BB09F New Pool: {u'generation': 453, u'minActiveMembers': 0, u'ipTosToServer': u'pass-through', u'loadBalancingMode': u'round-robin', u'allowNat': u'yes', u'queueDepthLimit': 0, u'membersReference': {u'isSubcollection': True, u'link': u'https://localhost/mgmt/tm/ltm/pool/~Common~myNewestPool1/members?ver=12.0.0'}, u'minUpMembers': 0, u'slowRampTime': 10, u'minUpMembersAction': u'failover', u'minUpMembersChecking': u'disabled', u'queueTimeLimit': 0, u'linkQosToServer': u'pass-through', u'queueOnConnectionLimit': u'disabled', u'fullPath': u'myNewestPool1', u'kind': u'tm:ltm:pool:poolstate', u'name': u'myNewestPool1', u'allowSnat': u'yes', u'ipTosToClient': u'pass-through', u'reselectTries': 0, u'selfLink': u'https://localhost/mgmt/tm/ltm/pool/myNewestPool1?ver=12.0.0', u'serviceDownAction': u'none', u'ignorePersistedWeight': u'disabled', u'linkQosToClient': u'pass-through'}
You can test this out in the Chrome Advanced Rest Client plugin, or from the command line with curl or any other language supporting REST clients as well, I just use python for the examples well, because I like it. I hope you all are digging into iControl REST! What questions do you have? What else would you like clarity on? Drop a comment below.
- Vercellone_1225Nimbostratus
Jason R,
I've been trying to get this working with 11.5.1 Build 8.0.175 Hotfix HF8. The Authentication User Directory is 'Remote - Active Directory'. Regardless of the loginProviderName I send, the result is 404 Not Found: And, I cannot locate a loginReference uuid or link anywhere.
Is there some active directory specific documentation to which I can refer, or is token retrieval simply not viable in 11.5.1 Build 8.0.175 Hotfix HF8?
Jason V.
- forsanAltostratus
Hi,
I have two different environments where I'm testing this. I'm getting the RESTAPI to work fine with my TMOS 12.1 environment but in 11.6.1 I'm getting 401 error.
12.1
curl -sk --header "Content-Type:application/json" --request POST https://localhost/mgmt/shared/authn/login --data '{"username":"aduser", "password":"adpassword", "loginProviderName":"tmos"}' {"username":"aduser","loginReference":{"link":"https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/login"},"loginProviderName":"tmos","token":{"token":"4F7P62K5BL2UIN4ZSW3DMWTAKC","name":"4F7P62K5BL2UIN4ZSW3DMWTAKC","userName":"anfo","authProviderName":"tmos","user":{"link":"https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/users/4f446f2a-9922-469a-ac69-656d770e8408"},"groupReferences":[{"link":"https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/user-groups/21232f29-7a57-35a7-8389-4a0e4a801fc3"}],"timeout":1200,"startTime":"2016-09-14T14:25:47.986+0200","address":"127.0.0.1","partition":"[All]","generation":1,"lastUpdateMicros":1473855947986173,"expirationMicros":1473857147986000,"kind":"shared:authz:tokens:authtokenitemstate","selfLink":"https://localhost/mgmt/shared/authz/tokens/4F7P62K5BL2UIN4ZSW3DMWTAKC"},"generation":0,"lastUpdateMicros":0}[ curl -sk --header "Content-Type:application/json" --header "X-F5-Auth-Token: 4F7P62K5BL2UIN4ZSW3DMWTAKC" --request GET https://localhost/mgmt/shared/echo {"stage":"STARTED","stageEnumValues":["CREATED","STARTED","SHUTDOWN"],"generation":0,"lastUpdateMicros":0,"kind":"shared:echo:echoworkerstate","selfLink":"https://localhost/mgmt/shared/echo"}
11.6.1
curl -sk --header "Content-Type:application/json" --request POST https://localhost/mgmt/shared/authn/login --data '{"username":"aduser", "password":"adpassword", "loginProviderName":"tmos"}' {"username":"aduser","loginReference":{"link":"https://localhost/mgmt/shared/authn/providers/local/login"},"token":{"uuid":"af2323bb-295f-4c21-a2ad-bc874d96b6a3","name":"39EBC4D277ECE08D5AFE4EC57B05674F194291BC6959079F188816267CA22CD9E1E8AB3CD44DE8C846A3A9A0AD9BD93F4C65B9C0BCFA969B8AF465CE2BD5A0BE","token":"39EBC4D277ECE08D5AFE4EC57B05674F194291BC6959079F188816267CA22CD9E1E8AB3CD44DE8C846A3A9A0AD9BD93F4C65B9C0BCFA969B8AF465CE2BD5A0BE","userName":"aduser","user":{"link":"https://localhost/mgmt/shared/authz/users/aduser"},"groupReferences":[],"timeout":1200,"startTime":"2016-09-14T05:03:15.763-0700","address":"127.0.0.1","partition":"[All]","generation":1,"lastUpdateMicros":1473854595759258,"expirationMicros":1473855795763000,"kind":"shared:authz:tokens:authtokenitemstate","selfLink":"https://localhost/mgmt/shared/authz/tokens/af2323bb-295f-4c21-a2ad-bc874d96b6a3"},"generation":0,"lastUpdateMicros":0} curl -sk --header "Content-Type:application/json" --header "X-F5-Auth-Token: 39EBC4D277ECE08D5AFE4EC57B05674F194291BC6959079F188816267CA22CD9E1E8AB3CD44DE8C846A3A0AD9BD93F4C65B9C0BCFA969B8AF465CE2BD5A0BE" --request GET https://localhost/mgmt/tm/ltm/pool?$select=name {"code":401,"message":"Authorization failed: user=https://localhost/mgmt/shared/authz/users/aduser resource=/mgmt/tm/ltm/pool verb=GET uri:http://localhost:8100/mgmt/tm/ltm/pool?=name referrer:127.0.0.1 sender:127.0.0.1","referer":"127.0.0.1","restOperationId":12574003,"errorStack":["java.lang.SecurityException: Authorization failed: user=https://localhost/mgmt/shared/authz/users/aduser resource=/mgmt/tm/ltm/pool verb=GET uri:http://localhost:8100/mgmt/tm/ltm/pool?=name referrer:127.0.0.1 sender:127.0.0.1","at com.f5.rest.workers.ForwarderWorker.failPermissionValidation(ForwarderWorker.java:565)","at com.f5.rest.workers.ForwarderWorker.evaluateUserPermission(ForwarderWorker.java:633)","at com.f5.rest.workers.ForwarderWorker.evaluatePermission(ForwarderWorker.java:537)","at com.f5.rest.workers.ForwarderPassThroughWorker.onForward(ForwarderPassThroughWorker.java:202)","at com.f5.rest.workers.ForwarderPassThroughWorker.onGet(ForwarderPassThroughWorker.java:370)","at com.f5.rest.common.RestWorker.callDerivedRestMethod(RestWorker.java:1009)","at com.f5.rest.common.RestWorker.callRestMethodHandler(RestWorker.java:976)","at com.f5.rest.common.RestServer.processQueuedRequests(RestServer.java:889)","at com.f5.rest.common.RestServer.access$000(RestServer.java:43)","at com.f5.rest.common.RestServer$1.run(RestServer.java:165)","at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)","at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)","at java.lang.Thread.run(Thread.java:744)\n"]}
One thing i noticed is the difference in the user path response from the different versions.
12.1:
https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/login
11.6.1:
`https://localhost/mgmt/shared/authn/providers/local/login
I have tried to create a local user on the 11.6.1 system but I don't think this will help because the RESTAPI users does not work when changing the Auth type from Local to my case "Active Directory". The only local accounts that works then is the admin and root accounts.
Best Regards Andréas
- JRahmAdmin
@Vercellone and @forsan, this is not expected to work before 12.0
Hi, Jason,
Are you saying that getting an auth token isn't expected to work at all before 12.0, or just with a LoginReference. I'm getting an auth token without issue in 11.6 HF4.
However, I'm not having any luck setting the timeout to a non-standard value, using a variation of Casey Robertson' JSON. Is that JSON valid, and is it documented anywhere?
Cheers, Joel
- JRahmAdmin
tokens will work, but tokens with remote authentication like tacacs+ or ldap are not expected to work. I personally haven't dealt with non-standard timeouts and if not documented in the user guides, I'd question the reliability of doing so.
- Eldom9Nimbostratus
Finally I was able to run token based auth with big-ip 11.6.1 and ldap auth provider. I found the login provider reference while I was playing with
and then simply changed the tailing user-group to login:restcurl cm | grep providers | tail -1
example:
"loginReference":{"link": "https://localhost/mgmt/cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/login"}
Hope this works for others aswell.
- jerebrad_302050Nimbostratus
Im new to REST API scripting so Im probably making a simple mistake here, but Im using Postman to generate a POST request to get a token. Im hitting .
I have authorization set to basic and in Body Im sending:
{ "username":"admin", "password":"password", "loginProvidername":"tmos" }
with the actual password for the admin account.
I keep getting:
{ "code": 401, "message": "Cannot use 'local' auth provider name when system auth source type is not 'local'", "originalRequestBody": "{\"username\":\"admin\",\"generation\":0,\"lastUpdateMicros\":0}", "referer": "10.238.90.21", "restOperationId": 37917423, "kind": ":resterrorresponse" }
I've Googled myself cross-eyed and haven't come across anything helpful. We use RADIUS authentication for access to our F5s. Based on this error message I think that has something to do with this. It seems that it doesn't like me using a local account when the box is configured for RADIUS, but I can't figure out how to change the post to use RADIUS. I've tried using a known good RADIUS account and that fails with the same message. The local account works with basic authentication when doing a GET to something like /mgmt/tm/ltm, but not when I do a POST to the /mgmt/shared/authn/login.
I am testing this against version 13.1.0.8.
- Daniel_Edgar_11Nimbostratus
Please try "loginProviderName":"tmos" instead (note the capital "N" in the JSON property)
- jerebrad_302050Nimbostratus
Thanks Dan, that was the issue! I can't believe it, after all the time I spent trying different things and scouring the internet for an answer!
- fanta_377379Nimbostratus
Thanks for the article. It works for me.