Accessing a Swift object using a Keystone trust in Python

05 Aug 2014

After my last effort using the ReST API to access Keystone’s trust feature I went on to experiment with the Python interface using the keystoneclient package. My goal was to allow a trustee user to access a trustor’s Swift container via the Python swiftclient package using a Keystone trust as the primary means of authenticating.

Setup

I started by setting up a stock DevStack on my Fedora 20 machine, logging in as the demo user, creating a container named container1, and uploading a file to an object named README. After more than a few hours messing with the openstack cli tools and the Python repl, I ended up with the following snippet:

Breakdown

First, I setup the authentication URLs for use with Keystone and Swift, we need both version endpoints because the trust feature is only available in version 3 of Keystone, but Swfit does not have support for 3 yet. I’ll use the v3 endpoint to create keystoneclient.v3.Client objects and the v2 to create swiftclient.client.Connection objects.

auth_url_v2 = 'http://localhost:5000/v2.0/'
auth_url_v3 = 'http://localhost:5000/v3/'

Lines 9 through 17 are standard Keystone client generation. I have setup my devstack to use openstack as the password for all accounts. By default DevStack starts with demo and alt_demo users, each belonging to a project of the same name.

Assuming the Keystone controller supports the OS-TRUST endpoint, each Client object will have a TrustManager which can be used to create new trusts. Using the demo client object I create a trust with the alt_demo user as the trustee, scoped to the demo project. I am using the role name of Member for this trust delegation because it is one of the default roles created by DevStack. This trust has no expiration, unlimited reuses, and does not impersonate the trustor.

trust = demo.trusts.create(trustor_user=demo.user_id,
                           trustee_user=alt_demo.user_id,
                           role_names=['Member'],
                           project=demo.tenant_id)

To allow the alt_demo user access to the demo user’s Swift container there are 2 pieces of information needed. The storage URL for Swift and a valid Keystone authentication token.

To get the storage URL I create a Swift connection using the demo user.

storageURL = swiftclient.Connection(authurl=auth_url_v2,
                                    user='demo',
                                    key='openstack',
                                    tenant_name='demo',
                                    auth_version="2.0").get_auth()[0]

Next, I create an authentication token for the alt_demo user scoped to the trust delegated from demo. This needs to be a new token scoped to the trust for Swift to allow us access using the storage URL.

auth_token = keystoneclient.Client(auth_url=auth_url_v3,
                                   username=alt_demo.username,
                                   token=alt_demo.auth_token,
                                   trust_id=trust.id).auth_token

Finally I create the Swift connection for the alt_demo user using only the storage URL and the newly minted authentication token. Then get the contents of container1 and it’s child object README.

alt_demo_swift = swiftclient.Connection(preauthurl=storageURL,
                                        preauthtoken=auth_token,
                                        auth_version="2.0")

result = alt_demo_swift.get_container('container1')
print(result)

result = alt_demo_swift.get_object('container1', 'README')
print(result)

When run from the terminal the results are:

$ python swift_access.py
({'content-length': '168', 'x-container-object-count': '1', 'accept-ranges': 'bytes', 'x-storage-policy': 'Policy-0', 'date': 'Tue, 05 Aug 2014 03:44:47 GMT', 'x-timestamp': '1407199642.22169', 'x-trans-id': 'tx97e263d906ad4829b3d46-0053e0532f', 'x-container-bytes-used': '72', 'content-type': 'application/json; charset=utf-8'}, [{'bytes': 72, 'last_modified': '2014-08-05T00:48:27.624150', 'hash': '179f2435401d53ac4a699cf015134016', 'name': 'README', 'content_type': 'application/octet-stream'}])
({'content-length': '72', 'accept-ranges': 'bytes', 'last-modified': 'Tue, 05 Aug 2014 00:48:28 GMT', 'etag': '179f2435401d53ac4a699cf015134016', 'x-timestamp': '1407199707.62415', 'x-trans-id': 'tx551849b8f89d492c847aa-0053e0532f', 'date': 'Tue, 05 Aug 2014 03:44:47 GMT', 'x-object-meta-orig-filename': 'README.rst', 'content-type': 'application/octet-stream'}, 'Example Pig job\n===============\n\nThis script trims spaces in input text\n')

Final Thoughts

I had a few stumbles while trying to get this working, but in the end I’m mostly pleased with the Python interface. Using the various Manager objects is very easy once the proper client is configured and this is one of the areas I needed to learn more about. Studying the OpenStack Identity API Reference, the Keystone Architecture Documentation, and the Identity section of the OpenStack Cloud Administrator Guide proved very effective in cementing the base Identity concepts. These documents cover version 2.0 of the Identity API and trusts are part of version 3, but the core concepts are the same.

Getting the role names correct is another area that I am still learning more about. There is much functionality that is controlled based on the client object’s credentials. I would like to find an easy way to query the client’s role names and then use those names for a delegation. This proved challenging for me as there are some roles which cannot be delegated(i.e. Service). Luckily the roles are well defined in DevStack so I was able to pick one that I knew was valid. I’m curious to learn more about the recommended role names for administrators.

I’m also interested to learn more about the Barbican project as I understand it is desgined for helping with shared secrets. I wonder if there is potential in using it for helping to distribute information like the Swift storage URLs or Keystone trust identifiers. Although neither of these pieces of information are particularly sensitive or damaging on their own, it would still be nice to find the most secure methods for transmission.

An interesting feature that will come for Keystone is endpoint scoped tokens, which would give even finer grained control over the trusts that could be delegated.

All in all using trusts like this has been a good experience and shows a great deal of opportunity for sharing all sorts of resources. Although this example uses Swift, by delegating Heat privileges a trustee could be allowed to spawn instances. I imagine with the proper tweaking this could be carried to any service.