Delegating Keystone trusts through the ReST API

10 Jun 2014

Recently I have been investigating the Trusts feature of Keystone version 3. During this exploration I have walked through many permutations of acquiring and using the trust based authentication tokens. What follows is an example of using the ReST API to delegate a trust from one user to another, and then use that trust to generate an authentication token for the purposes of accessing a Swift container owned by the first user.

###Setup

I am using a freshly updated Devstack on my Fedora 20 installation. I have a very simple local.conf file that sets all the passwords to openstack. This allows me some flexibility when using the command line tools. I am also using the httpie tool for making the ReST calls.

I have created a container for the demo user named job1, and set the environment variables OS_PASSWORD=openstack and OS_AUTH_URL=http://10.0.1.62:5000/v2.0. For reference the IP address of my machine is currently 10.0.1.62.

###The Process

####Collect IDs for the Trustor and Trustee

I start nice and simple by recording the IDs that will be needed for the trust delegation. In this case user demo will be the trustor and alt_demo the trustee.

$ keystone user-list
+----------------------------------+-------------------+---------+---------------------+
|                id                |        name       | enabled |        email        |
+----------------------------------+-------------------+---------+---------------------+
| cd7b3aa1f80f447f8f6b8c7d70cddc56 |      alt_demo     |   True  |                     |
| f0964f8ed640479fa1b24c334f8d9d8c |        demo       |   True  |   demo@example.com  |

####Collect Project ID

Next we will need the ID of the project that alt_demo should have access to based on the trust.

$ keystone tenant-list
+----------------------------------+--------------------+---------+
|                id                |        name        | enabled |
+----------------------------------+--------------------+---------+
| 24ea2aa9dc234982afa4b2ca23ac3d36 |        demo        |   True  |

####Create a JSON File for the Trust Delegation

In this case I would like to delegate a trust that allows impersonation and gives the trustee the role of Member. This access should allow the trustee to read and write the container while keeping the trustor’s identity. I’m allowing impersonation so that if the trustee writes to the container the ownership will remain with the trustor.

trust_create.json
{
  "trust": {
    "impersonation": true,
    "project_id": "24ea2aa9dc234982afa4b2ca23ac3d36",
    "roles": [
        {
            "name": "Member"
        }
    ],
      "trustee_user_id": "cd7b3aa1f80f447f8f6b8c7d70cddc56",
      "trustor_user_id": "f0964f8ed640479fa1b24c334f8d9d8c"
  }
}

####Get a Token for the Trustor

We need a token scoped to the project for the trustor to authenticate the delegation.

$ keystone --os-username=demo --os-tenant-name=demo token-get
+-----------+----------------------------------+
|  Property |              Value               |
+-----------+----------------------------------+
|  expires  |       2014-07-10T22:13:41Z       |
|     id    | 1acb40d200a64874b1dde102dfe14882 |
| tenant_id | 24ea2aa9dc234982afa4b2ca23ac3d36 |
|  user_id  | f0964f8ed640479fa1b24c334f8d9d8c |
+-----------+----------------------------------+

####Create the Trust

Using the JSON file we created earlier, we now create the trust. We will want note the id of the trust created, in this case 2d82fb8349d64c33babcd8f62b451aa7.

$ http http://10.0.1.62:5000/v3/OS-TRUST/trusts X-Auth-Token:1acb40d200a64874b1dde102dfe14882 < trust_create.json 
HTTP/1.1 201 Created
Content-Length: 675
Content-Type: application/json
Date: Thu, 10 Jul 2014 21:14:37 GMT
Vary: X-Auth-Token

{
    "trust": {
        "expires_at": null, 
        "id": "2d82fb8349d64c33babcd8f62b451aa7", 
        "impersonation": true, 
        "links": {
            "self": "http://10.0.1.62:5000/v3/OS-TRUST/trusts/2d82fb8349d64c33babcd8f62b451aa7"
        }, 
        "project_id": "24ea2aa9dc234982afa4b2ca23ac3d36", 
        "remaining_uses": null, 
        "roles": [
            {
                "id": "40d1d753969b4aa99b2be2a8414da4b5", 
                "links": {
                    "self": "http://10.0.1.62:5000/v3/roles/40d1d753969b4aa99b2be2a8414da4b5"
                }, 
                "name": "Member"
            }
        ], 
        "roles_links": {
            "next": null, 
            "previous": null, 
            "self": "http://10.0.1.62:5000/v3/OS-TRUST/trusts/2d82fb8349d64c33babcd8f62b451aa7/roles"
        }, 
        "trustee_user_id": "cd7b3aa1f80f447f8f6b8c7d70cddc56", 
        "trustor_user_id": "f0964f8ed640479fa1b24c334f8d9d8c"
    }
}

####Get a Token for Trustee

Now we will need a token for the trustee to consume the trust. I have specifically created a token that is not scoped to a project to show that there is no connection between the trustee and the trusted project.

$ keystone --os-username=alt_demo --os-tenant-name= token-get
+----------+----------------------------------+
| Property |              Value               |
+----------+----------------------------------+
| expires  |       2014-07-10T22:17:48Z       |
|    id    | d2f4a38aa84f40a798f041d19d044d3d |
| user_id  | cd7b3aa1f80f447f8f6b8c7d70cddc56 |
+----------+----------------------------------+

####Create a JSON File for Trust Consumption

A simple file containing the trustee token we just acquired and the trust ID we wish to consume.

    trust_consume.json
    {
      "auth": {
        "identity": {
          "methods": [
            "token"
            ],
          "token": {
            "id": "d2f4a38aa84f40a798f041d19d044d3d"
          }
        },
        "scope": {
          "OS-TRUST:trust": {
            "id": "2d82fb8349d64c33babcd8f62b451aa7"
          }
        }
      }
    }

####Consume the Trust

This operation is the same as acquiring any other authentication token with the exception that the token will be scoped to the newly created trust. I have cut out the catalog contents as they are quite long.

$ http http://10.0.1.62:5000/v3/auth/tokens X-Auth-Token:d2f4a38aa84f40a798f041d19d044d3d < trust_consume.json 
HTTP/1.1 201 Created
Content-Length: 6815
Content-Type: application/json
Date: Thu, 10 Jul 2014 21:20:32 GMT
Vary: X-Auth-Token
X-Subject-Token: f0a1133ee9be40e693fb682d45871d50

{
    "token": {
        "OS-TRUST:trust": {
            "id": "2d82fb8349d64c33babcd8f62b451aa7", 
            "impersonation": true, 
            "trustee_user": {
                "id": "cd7b3aa1f80f447f8f6b8c7d70cddc56"
            }, 
            "trustor_user": {
                "id": "f0964f8ed640479fa1b24c334f8d9d8c"
            }
        }, 
        "catalog": [
            ...
        ], 
        "expires_at": "2014-07-10T22:17:48.000000Z", 
        "extras": {}, 
        "issued_at": "2014-07-10T21:20:32.879083Z", 
        "methods": [
            "token"
        ], 
        "project": {
            "domain": {
                "id": "default", 
                "name": "Default"
            }, 
            "id": "24ea2aa9dc234982afa4b2ca23ac3d36", 
            "name": "demo"
        }, 
        "roles": [
            {
                "id": "40d1d753969b4aa99b2be2a8414da4b5", 
                "name": "Member"
            }
        ], 
        "user": {
            "domain": {
                "id": "default", 
                "name": "Default"
            }, 
            "id": "f0964f8ed640479fa1b24c334f8d9d8c", 
            "name": "demo"
        }
    }
}

####Acquire a Trust Based Authentication Token

After consuming the trust we can use the trust_consume.json file again to acquire an authentication token based solely on the trustee’s identity. We will get back a structure that is, more or less, the same as the result of the trust consumption. In this case though we are concerned with the value of X-Subject-Token as it contains our authentication token.

$ http http://10.0.1.62:5000/v3/auth/tokens X-Auth-Token:d2f4a38aa84f40a798f041d19d044d3d < trust_consume.json 
HTTP/1.1 201 Created
Content-Length: 6815
Content-Type: application/json
Date: Thu, 10 Jul 2014 21:29:23 GMT
Vary: X-Auth-Token
X-Subject-Token: 595714f8d9fc4a959ed47f1c9025820e

{
    "token": {
        "OS-TRUST:trust": {
            "id": "2d82fb8349d64c33babcd8f62b451aa7", 
                ...

####Determine the Storage URL

We need to use the storageURL as provided by Swift if we want to authenticate with the trust based token. This is important to note as this is the only root URL we will be able to use in conjuction with token based authentication.

$ swift --os-username=demo --os-tenant-name=demo stat -v
    StorageURL: http://10.0.1.62:8080/v1/AUTH_24ea2aa9dc234982afa4b2ca23ac3d36
    Auth Token: 24c6e577963f4c79b8cd37403add7a23
       Account: AUTH_24ea2aa9dc234982afa4b2ca23ac3d36
    Containers: 1
       Objects: 4
         Bytes: 77
X-Account-Storage-Policy-Policy-0-Bytes-Used: 77
   X-Timestamp: 1404835370.90232
X-Account-Storage-Policy-Policy-0-Object-Count: 4
    X-Trans-Id: tx3a5e4366defb4d2c84f3d-0053bf04ff
  Content-Type: text/plain; charset=utf-8
 Accept-Ranges: bytes

####Confirm that it Works

With our freshly minted trust based authentication token, we will now attempt to access the contents of Swift scoped to the demo user. We are not actually accessing an object, but merely reading the contents of Swift to show us the available containers. To access a container or object, append them to the storageURL (i.e. http://storageURL/container/object).

$ http http://10.0.1.62:8080/v1/AUTH_24ea2aa9dc234982afa4b2ca23ac3d36 X-Auth-Token:595714f8d9fc4a959ed47f1c9025820e
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 5
Content-Type: text/plain; charset=utf-8
Date: Thu, 10 Jul 2014 21:29:39 GMT
X-Account-Bytes-Used: 77
X-Account-Container-Count: 1
X-Account-Object-Count: 4
X-Account-Storage-Policy-Policy-0-Bytes-Used: 77
X-Account-Storage-Policy-Policy-0-Object-Count: 4
X-Timestamp: 1404835370.90232
X-Trans-Id: txbadf1cf3089e4ca9aa0ff-0053bf05c3

job1

####Revoke the Trust

Now we will revoke the trust using the trustor’s authentication token. This will invalidate the trust based token acquired by the trustee.

$ http DELETE http://10.0.1.62:5000/v3/OS-TRUST/trusts/2d82fb8349d64c33babcd8f62b451aa7 X-Auth-Token:1acb40d200a64874b1dde102dfe14882
HTTP/1.1 204 No Content
Content-Length: 0
Date: Thu, 10 Jul 2014 21:36:00 GMT
Vary: X-Auth-Token

####Confirm that Trust has been Revoked

Finally we confirm that the trust has indeed been revoked by attempting to access the Swift storageURL using the trust based token generated earlier.

$ http http://10.0.1.62:8080/v1/AUTH_24ea2aa9dc234982afa4b2ca23ac3d36 X-Auth-Token:595714f8d9fc4a959ed47f1c9025820e
HTTP/1.1 401 Unauthorized
Content-Length: 131
Content-Type: text/html; charset=UTF-8
Date: Thu, 10 Jul 2014 21:36:57 GMT
Www-Authenticate: Swift realm="AUTH_24ea2aa9dc234982afa4b2ca23ac3d36"
X-Trans-Id: txa17b6b5ee45b47339f0f9-0053bf0779

<html><h1>Unauthorized</h1><p>This server could not verify that you are authorized to access the document you requested.</p></html>

###Conclusion

This is a very simple example but it shows how trusts can be used to share Swift resources between users in different projects. I like the idea of trusts because it allows a developer to provide limited access control without having to get into the business of credential management. There is much more depth to the trust mechanism than what is shown here but as I have learned, Keystone is a deep well.

In my next installment I’ll get into using the python client interfaces to perform the same operation.

For more reading check out:

Official Trust API

Keystone Documentation

Identity API v2.0