I'm trying to work with Duo's python client (https://github.com/duosecurity/duo_client_python) and I believe I'm simply missing something with my novice python eyes. I somehow need to authenticate my requests to the API using an HMAC signature -- which I've not worked with before but seems trivial to generate (they even provide a python function). Doc here https://duo.com/docs/adminapi#authentication
I'm at a loss as to HOW to craft this signature for authentication, prior to passing my request to the API. From https://duo.com/docs/adminapi#authentication:
The API uses HTTP Basic Authentication to authenticate requests. Use your >Duo application’s integration key as the HTTP Username.
Generate the HTTP Password as an HMAC signature of the request. This will >be different for each request and must be re-generated each time.
It then goes into which parameters are added and necessary for the HMAC signature to be generated properly. I understand this part. My issue is how and when to pass the HMAC signature that I generate. As the Duo doc doesn't specifically go into that, I'm thinking that this is something I should already know [were I not such a python novice].
For example, simple auth calls work fine (as they need no authentication):
root@box:~# python -m duo_client.client --ikey XXXXXXXX --skey XXXXXXXX --host XXXXXXXX.duosecurity.com --method GET --path /auth/v2/check 200 OK { "response": { "time": 1496437236 } , "stat": "OK" }
However, calls using admin require authentication, and thus fail with:
root@box:~# python -m duo_client.client --ikey XXXXXXXX --skey XXXXXXXX --host XXXXXXXX.duosecurity.com --method GET --path /admin/v1/users signature=XXXXXXXX 401 Unauthorized { "code": 40103, "message": "Invalid signature in request credentials", "stat": "FAIL" }
Thanks in advance for any insight!
==EDIT==
So I thought I'd post the function that Duo provide for creating the signature, which appears similar to what is happening in the StackOverflow question I found that I thought might help (Python, HTTPS GET with basic authentication). From https://duo.com/docs/adminapi#authentication:
def sign(method, host, path, params, skey, ikey): """ Return HTTP Basic Authentication ("Authorization" and "Date") headers. method, host, path: strings from request params: dict of request parameters skey: secret key ikey: integration key """ # create canonical string now = email.Utils.formatdate() canon = [now, method.upper(), host.lower(), path] args = [] for key in sorted(params.keys()): val = params[key] if isinstance(val, unicode): val = val.encode("utf-8") args.append( '%s=%s' % (urllib.quote(key, '~'), urllib.quote(val, '~'))) canon.append('&'.join(args)) canon = '\n'.join(canon) # sign canonical string sig = hmac.new(skey, canon, hashlib.sha1) auth = '%s:%s' % (ikey, sig.hexdigest()) # return headers return {'Date': now, 'Authorization': 'Basic %s' % base64.b64encode(auth)}
I've used the above function in a simple script to print out (just so I can visualize) what should be getting passed within the script I'll create to server our needs -- I added the function to a script and used print by adding the following:
# Printing Signature Headers ### TESTING ### print sign('GET', 'XXXhostXXX', '/admin/v1/users', 'XXXparamXXX', 'XXXskeyXXX', 'XXXXikeyXXXX')
However, I'm getting this error:
root@box:~# ./duo.py Traceback (most recent call last): File "./duo.py", line 39, in print sign('GET', 'XXXhostXXX', '/admin/v1/users', 'XXXparamXXX', 'XXXskeyXXX', 'XXXikeyXXX') File "./duo.py", line 18, in sign for key in sorted(params.keys()): AttributeError: 'str' object has no attribute 'keys'
Am I just missing something here? I've been looking for what could cause that error but I thought I'd ask here as well.