Real World Examples¶
Cloud Foundry CLI¶
The resource server is the Cloud Foundry API. The UAA authentication server is typically hosted at both login
and uaa
subdomains for historical reasons (they used to be two separate applications).
To view the API calls from the cf
CLI to the UAA authorization server and the Cloud Foundry API resource server:
export CF_TRACE=1
cf login
Passcode¶
$ cf login -a https://api.run.pivotal.io --sso
API endpoint: https://api.run.pivotal.io
Temporary Authentication Code ( Get one at https://login.run.pivotal.io/passcode )>
The UAA presents you with an authentication code:
When you paste it into the terminal the cf
CLI completes its authentication process.
Temporary Authentication Code ( Get one at https://login.run.pivotal.io/passcode )>
Authenticating...
OK
Alternately, you could go directly to the /passcode
URL to get the passcode to authenticate to any machine:
cf login -a https://api.run.pivotal.io --sso-passcode c6ERPWaGLP
Client Credentials¶
cf auth CLIENT_ID CLIENT_SECRET --client-credentials
Generate OAuth Access Token¶
Once a cf
user is authenicated - via password grant, auth code/passcode, or with client credentials - they can generate a new access token:
cf oauth-token
The output is in the format bearer <JWT access token>
.
The user can now use the bearer <JWT access token>
in their API calls with the Cloud Foundry API resource server:
cf_auth=$(cf oauth-token)
curl -H "Authorization: ${cf_auth}" https://api.run.pivotal.io/v2/apps
curl -H "Authorization: ${cf_auth}" https://api.run.pivotal.io/v2/domains
If we decode the <JWT access token>
we can see the UAA scopes that cf
CLI has been authorized:
Placing the mouse over the exp
expiry timestamp helpfully shows us when the access token will expire (8:31am). This is 10 minutes after the current time (8:21am).
Dual Scope¶
The access token scopes for cf
CLI are a combination of Cloud Controller API authorizations (cloud_controller.read
, cloud_controller.write
) and UAA API authorizations (openid
, uaa.user
, password.write
).
This means we can use the same access token to interact with a subset of the UAA API.
For example, openid
allows a user to look up their own profile:
curl -H "Authorization: ${cf_auth}" https://login.run.pivotal.io/userinfo
My output is:
{
"user_id": "xxxx",
"user_name": "drnic@starkandwayne.com",
"name": "",
"email": "drnic@starkandwayne.com",
"email_verified": true,
"previous_logon_time": 1530852395198,
"sub": "xxxx"
}
Another example is the password.write
scope which allows a user to change their password.
The cf passwd
command prompts the user for new credentials and then invokes the UAA API to attempt to change the password. In my case below, I get an error message from the UAA API:
$ CF_TRACE=1 cf passwd
Current Password>
New Password>
Verify Password>
Changing password...
REQUEST: [2018-07-07T08:37:25+10:00]
PUT /Users/f61c0d28-5d9c-4e15-a26f-f8f42129e2e4/password HTTP/1.1
Host: uaa.run.pivotal.io
RESPONSE: [2018-07-07T08:37:28+10:00]
HTTP/1.1 400 Bad Request
{"error_description":"Password must contain at least 1 special characters.","error":"invalid_password","message":"Password must contain at least 1 special characters."}
That was annoying. I had already changed 1password to the new value.
CLI Discovery of UAA¶
When a user target's a Cloud Foundry API, the cf
CLI uses the /v2/info
endpoint to discover the location of the UAA authorization and token endpoints. For example:
curl https://api.run.pivotal.io/v2/info
The relevant portions of the output are:
{
"authorization_endpoint": "https://login.run.pivotal.io",
"token_endpoint": "https://uaa.run.pivotal.io",
...
}
For most Cloud Foundry installations, endpoints such as https://login.run.pivotal.io and https://uaa.run.pivotal.io are actually the same UAA.
Local Storage of Tokens¶
As an implementation detail of the cf
CLI, the user's access and refresh tokens are stored in a local, user-only file ~/.cf/config.json
. Some authorization related portions of the file are:
{
"AccessToken": "bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InNoYTItMjAxNy0wMS0yMC1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI0OTYzNzIxYjcxMGI0NzljODA1NmY3NzczMGYyNjZkNyIsInN1YiI6ImY2MWMwZDI4LTVkOWMtNGUxNS1hMjZmLWY4ZjQyMTI5ZTJlNCIsInNjb3BlIjpbIm9wZW5pZCIsInVhYS51c2VyIiwiY2xvdWRfY29udHJvbGxlci5yZWFkIiwicGFzc3dvcmQud3JpdGUiLCJjbG91ZF9jb250cm9sbGVyLndyaXRlIl0sImNsaWVudF9pZCI6ImNmIiwiY2lkIjoiY2YiLCJhenAiOiJjZiIsImdyYW50X3R5cGUiOiJwYXNzd29yZCIsInVzZXJfaWQiOiJmNjFjMGQyOC01ZDljLTRlMTUtYTI2Zi1mOGY0MjEyOWUyZTQiLCJvcmlnaW4iOiJ1YWEiLCJ1c2VyX25hbWUiOiJkcm5pY0BzdGFya2FuZHdheW5lLmNvbSIsImVtYWlsIjoiZHJuaWNAc3RhcmthbmR3YXluZS5jb20iLCJyZXZfc2lnIjoiZDQxYmRjYjEiLCJpYXQiOjE1MzA5MjAzNTYsImV4cCI6MTUzMDkyMDk1NiwiaXNzIjoiaHR0cHM6Ly91YWEucnVuLnBpdm90YWwuaW8vb2F1dGgvdG9rZW4iLCJ6aWQiOiJ1YWEiLCJhdWQiOlsiY2xvdWRfY29udHJvbGxlciIsInBhc3N3b3JkIiwiY2YiLCJ1YWEiLCJvcGVuaWQiXX0.rUBvAqanamiFPpiS9GDJBaJOH0jcHHOseietRNwcTOc2zKxGDxJucrK41gyhG8DAQbVzxSyI8zvbc6RnoLPtCcOlDqMdyCo4kDjKIrfPZoYT3PqxNwe53hRRK7yEDoVU5hRZ9TGrc9sZUT5asjbRn_P60UStc-U-Z-ZORYRew9jlkTX02CMMKAvJyMnFBDibeN84G49q6Ts7VZVnOjXoAXSVUs3xKLcAp24xgAUW25MkBHQY5GGXQLthiT-tWGpHSNoWFAS2_KdcIku4ihgdBQt5sqcIgprcoCF1jdhFm0BPOD5SkHENQ39aaVn2VZvLp-YdpnKlnB64uI9XHF7jVw",
"AuthorizationEndpoint": "https://login.run.pivotal.io",
"RefreshToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6InNoYTItMjAxNy0wMS0yMC1rZXkiLCJ0eXAiOiJKV1QifQ.eyJqdGkiOiI4OTIyODgzNmI4Y2Y0YjhjOTJjZmYxMjU2NTMwMjU1Mi1yIiwic3ViIjoiZjYxYzBkMjgtNWQ5Yy00ZTE1LWEyNmYtZjhmNDIxMjllMmU0Iiwic2NvcGUiOlsib3BlbmlkIiwidWFhLnVzZXIiLCJjbG91ZF9jb250cm9sbGVyLnJlYWQiLCJwYXNzd29yZC53cml0ZSIsImNsb3VkX2NvbnRyb2xsZXIud3JpdGUiXSwiaWF0IjoxNTMwOTIwMzU2LCJleHAiOjE1MzE1MjUxNTYsImNpZCI6ImNmIiwiY2xpZW50X2lkIjoiY2YiLCJpc3MiOiJodHRwczovL3VhYS5ydW4ucGl2b3RhbC5pby9vYXV0aC90b2tlbiIsInppZCI6InVhYSIsImdyYW50X3R5cGUiOiJwYXNzd29yZCIsInVzZXJfbmFtZSI6ImRybmljQHN0YXJrYW5kd2F5bmUuY29tIiwib3JpZ2luIjoidWFhIiwidXNlcl9pZCI6ImY2MWMwZDI4LTVkOWMtNGUxNS1hMjZmLWY4ZjQyMTI5ZTJlNCIsInJldl9zaWciOiJkNDFiZGNiMSIsImF1ZCI6WyJjbG91ZF9jb250cm9sbGVyIiwicGFzc3dvcmQiLCJjZiIsInVhYSIsIm9wZW5pZCJdfQ.IiI4NdTU8G3SMcdD-Lo2kRaJeh0j0taFTBmr7KAhAjrtHTnyGpaJZ4DSUZd-4lOHW8Cjh66OeuHF9J8-hRFUw950Qz0GigoK0IflN5GqlSbtL_bFuV5ANjFgBjmNJF-GSa09iD0UDkYjvfsi7X7wCkpAvS6vv6ZmuVLOSRhTNRoEcCeSpk2NTrx8mzx069pLbFZvqOZNaIStLZhG_IX2Lcb-XsAVg5SVogSzoPyhYxghhiI3J-FnA8uPzsH4PxtOpWJ5B6DEL3ygmZ7Zr7EJZLXwIeItIHoGq10RtkeAL08wyPnHOqJNUh5wEveFkV4vPvKxylC8nTIGxqxaoUWX7w",
"SSHOAuthClient": "ssh-proxy",
"Target": "https://api.run.pivotal.io",
"UaaEndpoint": "https://uaa.run.pivotal.io",
"UAAGrantType": "",
"UAAOAuthClient": "cf",
"UAAOAuthClientSecret": ""
}
The cf oauth-token
command will pass the RefreshToken
to the UAA to request a new AccessToken
. Indeed, all cf
subcommands will automatically refresh the AccessToken
if it has expired.
UAA Client Definition¶
The UAA client used by the cf
CLI is named cf
, has no secret, and includes long list of requested scopes for access to a user's Cloud Foundry API data. A standard Cloud Foundry deployment will include the following UAA client configured:
cf:
access-token-validity: 600
authorities: uaa.none
authorized-grant-types: password,refresh_token
override: true
refresh-token-validity: 2592000
scope: network.admin,network.write,cloud_controller.read,cloud_controller.write,openid,password.write,cloud_controller.admin,scim.read,scim.write,doppler.firehose,uaa.user,routing.router_groups.read,routing.router_groups.write,cloud_controller.admin_read_only,cloud_controller.global_auditor,perm.admin
secret: ''
Authentication UX Determined by UAA¶
The user experience of the cf
CLI when authenticating users is derived from the UAA. The text Temporary Authentication Code ( Get one at https://login.run.pivotal.io/passcode )>
originated from the UAA:
curl https://login.run.pivotal.io/info -H 'Accept: application/json' | jq .
The output for Pivotal Web Services at the time of writing is:
{
"app": {
"version": "4.19.0"
},
"showLoginLinks": true,
"links": {
"uaa": "https://uaa.run.pivotal.io",
"passwd": "https://account.run.pivotal.io/forgot-password",
"login": "https://login.run.pivotal.io",
"register": "https://account.run.pivotal.io/sign-up"
},
"zone_name": "uaa",
"entityID": "login.run.pivotal.io",
"commit_id": "7897100",
"idpDefinitions": {
"pivotal-oktapreview-com": "https://login.run.pivotal.io/saml/discovery?returnIDParam=idp&entityID=login.run.pivotal.io&idp=pivotal-oktapreview-com&isPassive=true",
"pivotal-okta-com": "https://login.run.pivotal.io/saml/discovery?returnIDParam=idp&entityID=login.run.pivotal.io&idp=pivotal-okta-com&isPassive=true"
},
"prompts": {
"username": [
"text",
"Email"
],
"password": [
"password",
"Password"
],
"passcode": [
"password",
"Temporary Authentication Code ( Get one at https://login.run.pivotal.io/passcode )"
]
},
"timestamp": "2018-06-13T12:02:09-0700"
}
The cf
CLI uses the prompts and text from this /info
output with its own users.
Of very special note is links.passwd
... the browser URL for me to reset my password.
BOSH CLI¶
BOSH is a platform to run systems such as Cloud Foundry, or the UAA, on any cloud infrastructure (AWS, Azure, GCP, vSphere, OpenStack, VirtualBox, Docker, Kubernetes).
The resource server is the BOSH API. The BOSH API and UAA are typically colocated on the same VM. The BOSH API is on port 25555
and the UAA is on port 8443
(like our uaa-deployment up
UAA).
BOSH API uses UAA claims to limit its own functionality for users/clients.
export BOSH_ENVIRONMENT=10.10.1.4
export BOSH_CA_CERT='...'
export BOSH_CLIENT=admin
export BOSH_CLIENT_SECRET=password
bosh env
To view the API calls from the bosh
CLI to the UAA authorization server and the BOSH Director API resource server:
export BOSH_LOG_LEVEL=debug
Even if you are not yet a BOSH user, you can easily provision a single VM BOSH API using the a similar approach we've been running our UAA. The uaa-deployment up
tool originated with a BOSH/UAA version BUCC by Stark & Wayne.
You will need to tear down your local UAA as it uses the same local IP address managed via VirtualBox:
uaa-deployment down
To deploy a BOSH/UAA:
git clone https://github.com/starkandwayne/bucc ~/workspace/bucc
source <(~/workspace/bucc/bin/bucc env)
bucc up
Once the BOSH/UAA VM has been deployed, we can configure the bosh
CLI to target the API and authenticate as an admin
user via environment variables:
source <(~/workspace/bucc/bin/bucc env)
bosh env
The output will show that we are the client 'admin'
, but incorrectly suggests we have logged in as a UAA user admin
:
Using environment '192.168.50.6' as client 'admin'
Name bucc
UUID 9fc88b8f-27aa-42b1-9c45-7bf9aba5b242
Version 265.2.0 (00000000)
CPI warden_cpi
Features compiled_package_cache: disabled
config_server: enabled
dns: disabled
snapshots: disabled
User admin
It is very common for BOSH operators to begin only using a UAA client admin
to interact with their BOSH.
Yet, we now know we can create UAA users with appropriate scopes.
BOSH UAA Users¶
We can use the uaa
CLI to create UAA users for BOSH.
The bucc
CLI includes a helper to authenticate the uaa
CLI:
$ bucc uaa
Target set to https://192.168.50.6:8443
Access token successfully fetched and added to context.
To creata myself a BOSH user:
uaa create-user drnic \
--email drnic@starkandwayne.com \
--givenName "Dr Nic" \
--familyName "Williams" \
--password drnic_secret
To make myself a BOSH admin:
uaa add-member bosh.admin drnic
To login with bosh
CLI as drnic
user:
unset BOSH_CLIENT
unset BOSH_CLIENT_SECRET
bosh login
By providing drnic
and drnic_secret
as my username/password, I authenticate and authorized the bosh
CLI to access the BOSH API on my behalf. The output of bosh env
shows my user and authorized scopes:
Using environment '192.168.50.6' as user 'drnic' (openid, bosh.admin)
Name bucc
UUID 9fc88b8f-27aa-42b1-9c45-7bf9aba5b242
Version 265.2.0 (00000000)
CPI warden_cpi
Features compiled_package_cache: disabled
config_server: enabled
dns: disabled
snapshots: disabled
User drnic