Openstack

Openstack leverages Token Authentication and we can use hooks to drastically simplify this as compared to the previous v103 Openstack Example. The following are sample request/response payloads from an Openstack server. We will use this information to build out the hooks needed.

The following shows an example of making a request to the Openstack token server and the response:

curl -i \
  -H "Content-Type: application/json" \
  -d '
{ "auth": {
    "identity": {
      "methods": ["password"],
      "password": {
        "user": {
          "name": "admin",
          "domain": { "id": "default" },
          "password": "adminpwd"
        }
      }
    }
  }
}' \
  "http://localhost:5000/v3/auth/tokens" ; echo

Example response:

HTTP/1.1 201 Created
X-Subject-Token: MIIFvgY...
Vary: X-Auth-Token
Content-Type: application/json
Content-Length: 312
Date: Fri, 11 May 2018 03:15:01 GMT

{
  "token": {
      "issued_at": "2018-05-11T03:15:01.000000Z",
      "audit_ids": [
          "0PKh_BDKTWqqaFONE-Sxbg"
      ],
      "methods": [
          "password"
      ],
      "expires_at": "2018-05-11T04:15:01.000000Z",
      "user": {
          "password_expires_at": null,
          "domain": {
              "id": "default",
              "name": "Default"
          },
          "id": "9a7e43333cc44ef4b988f05fc3d3a49d",
          "name": "admin"
      }
  }
}

The payload is significantly different then what the out of the box Token Authenticator supports so based on this, the first hook needed will be the get_token_request_args since it can be used to format the payload as above. This function can modify ANY parameter in requests.Session.request and in this case the payload needs to be set as above. Since the payload is json the parameter we will need to modify is the json parameter. It should be noted that setting the json parameter causes the request to automatically insert the Content-Type: application/json header which is needed as shown above.

Thus, all that is needed is to return the payload that to requests. In this case only the credential parameter is required as the username and password from the credential are required (Refer to Credential Fields for all the available fields). Any other parameters defined in requests.Session.request can be modified.

For example payload={“method”: “post”,”headers” : { “Content-Type”: “Application json”}, “json”: {…}} can be used to set the method and include additional headers if they were needed.

The next problem is that the token response is NOT in the payload but rather in the header. Additionally, the expiry information is a fixed date and thus that will need to be converted into seconds. Therefore, the modify_response hook will be needed to retrieve the token from the header and to calculate the token expiry. There are two input parameters needed for this case the response and the refresh_info. The response is a requests.response as described in the link.

The refresh_info includes the type of refresh and the value for the time to live between token refreshes. That is refresh_info.type indicates either “tokenAuthRefreshStatic” or “tokenAuthRefreshDynamic”. This info comes from the credential. The refresh_info.info.ttl is also from the credential if static refresh was selected. Otherwise it will default to 3600. The code puts the expiry timer handling in a try block such that even if an error occurs the token refresh will be set to the value in the refresh_info.info.ttl which has a default value of 3600 seconds.

Finally, the token itself is received in the X-Subject-Token header and thus needs to be retrieved from the response.headers as shown in the code.

The system then continues and since the default behavior for the Token Authenticator is that the token received is then used to make subsequent calls with the token being included in a header whose definition is defined in the credential. Since this behaviour is needed for openstack as long as the Authorization Header field is set to X-Auth-Token then no further hooks are required.

Complete Snippet Code

from silo.apps.errors import error_manager
with error_manager(self):

    from silo.low_code import *
    from silo.apps.collection import create_collections, save_collections

    # =====================================
    # =========== User Editable ===========
    # =====================================

    # List any custom substitutions that need to occur within the snippet arguments
    custom_substitution = {}

   from silo.auth import create_token_authenticator
    from silo.auth.base_token import *
    from datetime import datetime, timezone

    def get_token_request_args(credential):
        payload={
          "method" : "post",
          "json": {
           "auth": {
               "identity": {
                   "methods": ["password"],
                   "password": {
                       "user": {
                           "name": credential.fields["username"],
                           "domain": { "id": "default" },
                           "password": credential.fields["password"]
                       }
                   }
               }
           }
          }
        }
        return(payload)

    def process_open_stack_response(response,refresh_info):
        try:
            if refresh_info.type=="tokenAuthRefreshDynamic":
                exp_time=response.json()['token']['expires_at']
                exp_time = exp_time.replace('Z', '')
                exp_time = datetime.strptime(exp_time, '%Y-%m-%dT%H:%M:%S.%f')
                exp_time = exp_time.replace(tzinfo=timezone.utc)
                current_time = datetime.now(timezone.utc)
                seconds= int((exp_time-current_time).total_seconds())
            else:
                seconds=refresh_info.info.ttl
        except:
              seconds=refresh_info.info.ttl
        return token_response_fields(
              expires_in=seconds,
              token=response.headers["X-Subject-Token"],
        )

    Openstack = create_token_authenticator(
        name="Openstack",
        description="Openstack Authenticator.",
        get_token_request_args= get_token_request_args,
        process_response=process_open_stack_response
    )

    # =====================================
    # ========= End User Editable =========
    # =====================================


    collections = create_collections(self)
    snippet_framework(collections, custom_substitution, snippet_id, app=self)
    save_collections(collections, self)

Credential used for Openstack

../../../../_images/openstack-cred.png