Skip to content
This repository has been archived by the owner on Jan 21, 2022. It is now read-only.

User Specific Token

Adriana Dimitrova edited this page Jun 20, 2018 · 5 revisions

Abstract

Abacus is a system composed of a number of micro-services, where each micro-service exposes a number of REST APIs, used by other micro-services and external clients to communicate and exchange data.

All endpoints are secured with OAuth. Abacus performs authentication of requests by verifying that incoming requests contain a valid (properly signed, intact, and not expired) OAuth token. Authentication is not enough, however, since any Cloud Foundry user can obtain a valid OAuth token from UAA. This is why authorization is also performed by Abacus by verifying that the request tokens contain the required set of scopes. Depending on the micro-service and the endpoint being called, these scopes may differ.

Following is an example of some of the Abacus scopes used:

  • abacus.usage.write - required for API endpoints which mutate data in Abacus.
  • abacus.usage.read - required for read-only API endpoints of Abacus.

Abacus micro-services obtain their tokens through the Client Credentials flow. In that flow, micro-services use a Client ID and Client Secret credentials to request a token with specific scopes from the UAA. The UAA checks whether the Client with that Client ID is allowed the given scopes and whether the Client Secret matches. If everything is as needed, the UAA responds with a valid OAuth token, containing the requested scopes.

The problem arises when Abacus Operators want to troubleshoot Abacus. They need some mechanism through which they can obtain a similar token, containing all required scopes, in order to be able to perform REST calls from their workstations.

Current Workflow

Initially, what Abacus Operators would do is use the same Client ID and Client Secret credentials as the Abacus micro-services in order to obtain a token from UAA. From a security standpoint this has a number of problems.

  • In case the Client ID is blocked due to misuse, the Abacus software will cease to work.
  • Should the Client ID and Client Secret credentials leak, a full redeployment of Abacus is required in order to change them.
  • There is no way for the Cloud Foundry operator to control the time span during which Abacus Operators may invoke Abacus endpoints. Once an Abacus Operator learns the credentials, they can be used until they are changed.
  • In the case of a destructive operation, there is no way to distinguish whether the call was performed by an Abacus micro-service or an Abacus Operator.

Solution

It seems that the Password Grant flow would be ideal for the problem at hand. This is an OAuth flow where Abacus Operators use their own Username and Password credentials to retrieve a valid OAuth token, which contains information pointing to the owner of the token. Using this flow has the following benefits.

  • In the worst case, an Abacus Operator would block their own Username and Password.
  • Should any credentials leak, those would be limited to that specific Abacus Operator. Additionally, tokens have an expiration period.
  • The Cloud Foundry operator has a way of controlling which Abacus Operators have access to which scopes for how long.
  • In case of a destructive operation, it is possible to ascertain the responsible Abacus Operator, since that information is contained in the token.

Example

The first thing we need to do is login to UAA as administrators in order to perform some initial configurations.

uaac target https://uaa.cloudfoundry
uaac token client get
  # Client ID:      admin
  # Client secret:  *****

Next, we need to define all scopes we will be managing.

uaac group add 'abacus.usage.write'
uaac group add 'abacus.usage.read'

We need to create a UAA Client which will be used for the Password Grant flow. We previously stated that the Abacus Operator's Username and Password credentials are used in this flow. Though that is true, in reality a Client ID and Client Secret credentials are needed as well.

However, the Client ID cannot be used on its own (it is configured explicitly for the Password Grant flow, and cannot be used in a Client Credential flow to obtain a token) and is instead used as a means to specify the scopes that can be requested and some metadata. This is why the name of the Client ID is publicly known and there is no Client Secret configured. This is exactly how the CF CLI performs a login operation. It uses a Client ID equal to cf and no Client Secret. This will become more clear as we progress.

The following command creates a UAA Client to be used only for the Password Grant flow and which allows the retrieval of tokens containing any of the abacus.usage.write and abacus.usage.read scopes, but not others.

uaac client add 'abacus-ops' --no-interactive \
  --name 'abacus-ops' \
  --scope 'abacus.usage.write,abacus.usage.read' \
  --authorized_grant_types 'password' \
  --autoapprove true \
  --authorities uaa.none \
  --access_token_validity 36000 \
  --refresh_token_validity 0 \
  --secret ''

With this last command we have everything set up.

Now, should an Abacus Operator request access to Abacus, the Cloud Foundry operator would need to grant that user the desired scopes.

uaac member add 'abacus.usage.write' <my_user_name>
uaac member add 'abacus.usage.read' <my_user_name>

Then, the Abacus Operator can request a token with the following command:

uaac token owner get
  # Client ID:      abacus-ops
  # Client secret:  <blank>
  # User name:      <my_user_name>
  # Password:       *******
  #
  # Successfully fetched token via owner password grant.

Next the Abacus Operator can use the following command to look up the token:

uaac context <my_user_name>

As an alternative, the token can be found in the ~/.uaac.yaml file. This is what the command above does.

cat ~/.uaac.yml | grep <my_user_name> -A 10

Once the Cloud Foundry operator decides that he/she does not want the given Abacus Operator to have access to Abacus anymore, they can execute the following command to revoke it:

uaac member delete 'abacus.usage.write' <my_user_name>
uaac member delete 'abacus.usage.read' <my_user_name>

This completes the whole operations scenario.

Tokens

In order for Abacus to track who made a call, it can find that information in the tokens.

Here is an example of a token used by Abacus micro-services and obtained via the Client Credentials flow.

{
  "jti": "d9cbdcaf2c284ee0bfa5d9b1428aae00",
  "sub": "abacus",
  "authorities": [
    "abacus.usage.read",
    "abacus.usage.write"
  ],
  "scope": [
    "abacus.usage.write",
    "abacus.usage.read"
  ],
  "client_id": "abacus",
  "cid": "abacus",
  "azp": "abacus",
  "grant_type": "client_credentials",
  "rev_sig": "eefe0915",
  "iat": 1505378122,
  "exp": 1505421322,
  "iss": "https://uaa.cloudfoundry",
  "zid": "uaa",
  "aud": [
    "abacus.usage",
    "abacus"
  ]
}

And here is an example token used by an Abacus Operator and obtained via the Password Grant flow.

{
  "jti": "5314af3224a34b85b47ebe81fc737e0c",
  "sub": "86137409-294f-48ba-bddd-20159d87ff63",
  "scope": [
    "abacus.usage.read",
    "abacus.usage.write"
  ],
  "client_id": "abacus-ops",
  "cid": "abacus-ops",
  "azp": "abacus-ops",
  "grant_type": "password",
  "user_id": "<my_user_id>",
  "origin": "ldap",
  "user_name": "<my_user_name>",
  "email": "<email>",
  "auth_time": 1505392215,
  "rev_sig": "f7bb2eb5",
  "iat": 1505392215,
  "exp": 1505428215,
  "iss": "https://uaa.cloudfoundry",
  "zid": "uaa",
  "aud": [
    "abacus-ops",
    "abacus.usage"
  ]
}

There are a lot of similarities but the second one clearly has more detailed information. Abacus will probably need to track both client_id and user_name so that the tracked information is complete in both OAuth cases.

One option in Abacus is to use user_name with preference over client_id.

const caller = token.user_name || token.client_id;

An alternative is to concat both, which might be more complete.

const caller = token.client_id + ':' + (token.user_name || 'non-user');
<<Secure Abacus Healthcheck>>

Related Links:

Clone this wiki locally