-
Notifications
You must be signed in to change notification settings - Fork 86
User Specific Token
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.
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
andClient 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.
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
andPassword
. - 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.
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.
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>> |
---|
- Roles
- Secure Abacus
- For information about OAuth flows in UAA, see: https://docs.cloudfoundry.org/api/uaa/version/4.6.0/index.html#token
ABOUT | RESOURCE PROVIDER | ABACUS INTEGRATOR
*Abacus icon made by Freepik from www.flaticon.com