Zero-downtime client secret rotation with Cloud Foundry UAA
It’s important that your Cloud Foundry Oauth2 Clients are able to continually interact with the UAA, for example to verify the validity of opaque tokens. But if the client secret needs to be rotated, it’s hard to get the timing just right. After all — there may be some delay until the new secret can be provided to the client application, or require a client application restart.
Fortunately, the UAA allows a client to have multiple client secrets at one time, to facilitate cutting over the client applications from the old secret to the new secret.
To demonstrate, run the UAA:
$ git clone https://github.com/cloudfoundry/uaa.git
$ cd uaa
$ ./gradlew run
# wait for the following line to appear
Press Ctrl-C to stop the container...
Then get a sample token to pass to the /introspect
endpoint. This will use the bootstrapped client apps described in oauth-clients.xml.
$ curl 'http://localhost:8080/uaa/oauth/token' -i -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=app&client_secret=appclientsecret&grant_type=client_credentials&token_format=opaque'# find the access_token in the response
# introspect the token $ curl 'http://localhost:8080/uaa/introspect' -i -u 'app:appclientsecret' \
-X POST \
-d 'token=<access_token>'# should return JSON which includes "active": true
As it happens we got a client_credentials
token for client app
, and then used app:appclientsecret
to auth ourselves to the /introspect
endpoint as well.
Let’s add a new client secret for client app
. To do this, we’ll need a higher level of permissions (such as clients.write
). Let’s get a client_credentials
token for client admin
. Note that it’s not possible for a client to change its own secret without specific authorities.
$ curl 'http://localhost:8080/uaa/oauth/token' -i -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=admin&client_secret=adminsecret&grant_type=client_credentials&token_format=opaque'# find the access_token in the response$ curl 'http://localhost:8080/uaa/oauth/clients/app/secret' -i -X PUT \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <admin client access_token>' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "app",
"secret": "new_secret",
"changeMode": "ADD"
}'# Should return 200 {"status":"ok","message":"Secret is added"}
Now you can use either secret appclientsecret
or new_secret
to auth to the introspect
endpoint.
$ curl 'http://localhost:8080/uaa/introspect' -i -u 'app:appclientsecret' -X POST \
-d 'token=<access_token>'$ curl 'http://localhost:8080/uaa/introspect' -i -u 'app:new_secret' -X POST \
-d 'token=<access_token>'
Then, when you’re ready, delete the old client secret.
$ curl 'http://localhost:8080/uaa/oauth/clients/app/secret' -i -X PUT \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <admin client access_token>' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "app",
"changeMode": "DELETE"
}'
Now secret appclientsecret
no longer works!
For more information about Cloud Foundry, visit https://www.cloudfoundry.org/
For more information about Cloud Foundry UAA, visit