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 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):

  1. 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)
  2. Remove the basic authentication (49)
  3. Add the token from the post response to the X-F5-Auth-Token header (50)
  4. 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.

Updated Jan 06, 2024
Version 2.0