Enable 2 Factor Authentication in Cloud Foundry
How to enable 2FA with Google authenticator in Cloud Foundry UAA
What?
Cloud Foundry comes with a feature rich multi tenant identity management service called UAA (User Account and Authentication). If you have already used Cloud Foundry this is the service where you log in to your foundation. Just in case you did not know the Cloud Foundry UAA is also OpenID Certified.
Why?
Authenticating users in Cloud Foundry with username/password credentials is pretty standard. But the UAA service is also capable of providing multi factor authentication. Because I want to learn more about the internals of Cloud Foundry I just tried to enable 2FA on my local PCFDev deployment.
After completing this guide you will be able to login in to your Cloud Foundry foundation with Google Authenticator or with Microsoft Authenticator. I tested the login with these two apps. Other 2FA apps may work as well.
How?
The necessary steps
- Checking access to the UAA API
- Getting a token for the admin client
- Creating a new identity zone (tenant)
- Create a mfa provider (google authenticator) for the new zone
- Activate the mfa provider
- Register and activate a new user
- Initialize google authenticator
- Login
- Have fun
Checking Access to the UAA API
To work with the UAA you access to the API of UAA. In PCFDev the UAA endpoint can be reached via
$ curl -k -X GET --url https://uaa.dev.cfdev.sh
> GET / HTTP/1.1
> Host: uaa.dev.cfdev.sh
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Location: https://uaa.dev.cfdev.sh/login
You can also can some more information about your current UAA version.
$ curl -k -X GET --url https://uaa.dev.cfdev.sh/info
{
"app": {
"version": "4.24.0"
},
"links": {
"uaa": "https://uaa.dev.cfdev.sh",
"passwd": "/forgot_password",
"login": "https://login.dev.cfdev.sh",
"register": "/create_account"
},
"zone_name": "uaa",
"entityID": "http://login.dev.cfdev.sh",
"commit_id": "16d9900",
"idpDefinitions": {},
"prompts": {
"username": [
"text",
"Email"
],
"password": [
"password",
"Password"
]
},
"timestamp": "2018-11-06T01:20:26+0000"
}
This means at the time of writing this blog post I had version 4.24.0 installed.
[To use the API we need to have look at the API documentation of our version.] (http://docs.cloudfoundry.org/api/uaa/)
Getting the token for the admin client
To access the UAA API you need have a valid access token for a client. This client need to have the necessary admin scopes to do our administrative API calls.
In PCFDev the client id is “admin” and the client secret is “admin-client-secret”.
curl -X POST \
https://uaa.dev.cfdev.sh/oauth/token \
-H 'Accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Host: uaa.dev.cfdev.sh' \
-d 'client_id=admin&client_secret=admin-client-secret&grant_type=client_credentials&token_format=opaque'
{
"access_token": "68973xxx4ff94e57xxxd5a852189xx",
"token_type": "bearer",
"expires_in": 43199,
"scope": "clients.read password.write clients.secret clients.write uaa.admin scim.write scim.read",
"jti": "6897363exxxxxxxx8c1d5a85218xxx"
}
Creating a new identity zone (tenant) for my test apps
The UAA supports multi tenancy. A tenant is called identity zone in UAA speak. After installation a default zone is available.
Because we do not want to break the default zone through our experiments we create a new zone called “gomagic” for our test apps.
The whole data structure for the request has been copied from the API docs. I only changed the name of the zone. For the first steps of learning this is good enough. For production … IT IS NOT!
$ curl -X POST \
https://uaa.dev.cfdev.sh/identity-zones/ \
-H 'Accept: application/json' \
-H 'Authorization: Bearer 68973xxx4ff94e57xxxd5a852189xx' \
-H 'Content-Type: application/json' \
-H 'Host: uaa.dev.cfdev.sh' \
-d '{
"id": "gomagic",
"subdomain": "gomagic",
"config": {
"clientSecretPolicy": {
"minLength": 0,
"maxLength": 255,
"requireUpperCaseCharacter": 0,
"requireLowerCaseCharacter": 0,
"requireDigit": 0,
"requireSpecialCharacter": 0
},
"tokenPolicy": {
"accessTokenValidity": 43200,
"refreshTokenValidity": 2592000,
"jwtRevocable": false,
"refreshTokenUnique": false,
"refreshTokenFormat": "jwt",
"activeKeyId": "active-key-1",
"keys" : {
"active-key-1" : {
"signingKey" : "key"
}
}
},
"samlConfig" : {
"assertionSigned" : true,
"requestSigned" : true,
"wantAssertionSigned" : true,
"wantAuthnRequestSigned" : false,
"assertionTimeToLiveSeconds" : 600,
"activeKeyId" : "legacy-saml-key",
"keys" : {
"legacy-saml-key" : {
"key" : "-----BEGIN RSA PRIVATE KEY-----\ntake the sample data from the api docs\n-----END RSA PRIVATE KEY-----\n",
"passphrase" : "password",
"certificate" : "-----BEGIN CERTIFICATE-----\ntake the sample data from the api docs\n-----END CERTIFICATE-----\n"
}
},
"entityID" : "cloudfoundry-saml-login",
"disableInResponseToCheck" : false,
"certificate" : "-----BEGIN CERTIFICATE-----\ntake the sample data from the api docs\n-----END CERTIFICATE-----\n",
"privateKey" : "-----BEGIN RSA PRIVATE KEY-----\ntake the sample data from the api docs\n-----END RSA PRIVATE KEY-----\n",
"privateKeyPassword" : "password"
},
"corsPolicy": {
"xhrConfiguration": {
"allowedOrigins": [
".*"
],
"allowedOriginPatterns": [],
"allowedUris": [
".*"
],
"allowedUriPatterns": [],
"allowedHeaders": [
"Accept",
"Authorization",
"Content-Type"
],
"allowedMethods": [
"GET"
],
"allowedCredentials": false,
"maxAge": 1728000
},
"defaultConfiguration": {
"allowedOrigins": [
".*"
],
"allowedOriginPatterns": [],
"allowedUris": [
".*"
],
"allowedUriPatterns": [],
"allowedHeaders": [
"Accept",
"Authorization",
"Content-Type"
],
"allowedMethods": [
"GET"
],
"allowedCredentials": false,
"maxAge": 1728000
}
},
"links": {
"logout": {
"redirectUrl": "/login",
"redirectParameterName": "redirect",
"disableRedirectParameter": false,
"whitelist": [
"https://console.dev.cfdev.sh/",
"https://apps.dev.cfdev.sh/"
]
},
"homeRedirect": "/",
"selfService": {
"selfServiceLinksEnabled": true,
"signup": "/create_account",
"passwd": "/forgot_password"
}
},
"prompts": [
{
"name": "username",
"type": "text",
"text": "Email"
},
{
"name": "password",
"type": "password",
"text": "Password"
},
{
"name": "passcode",
"type": "password",
"text": "Temporary Authentication Code ( Get one at https://login.dev.cfdev.sh/passcode )"
}
],
"idpDiscoveryEnabled": false,
"branding": {
"companyName": "nomagic.net",
"productLogo": "some-base64-encoded-image",
"squareLogo": "some-base64-encoded-image"
},
"accountChooserEnabled": false,
"userConfig": {
"defaultGroups": [
"openid",
"scim.me",
"cloud_controller.read",
"cloud_controller.write",
"cloud_controller_service_permissions.read",
"password.write",
"uaa.user",
"approvals.me",
"oauth.approvals",
"notification_preferences.read",
"notification_preferences.write",
"profile",
"roles",
"user_attributes",
"uaa.offline_token",
"actuator.read",
"cloud_controller.user"
]
},
"mfaConfig": {
"enabled": false,
"identityProviders": [
"uaa",
"ldap"
]
}
},
"name": "gomagic",
"version": 1,
"description": "The zone for the cool gomagic apps",
"created": 946684800000,
"active": true,
"last_modified": 1557340772000
}'
After creating the new zone you already can open the login page for this zone.
But we still do not have 2FA and still no users.
Create MFA Provider for the new zone
Multi Factor Authentication (MFA) providers need to be created per zone. For selecting the correct zone “gomagic” you need to use the request header “X-Identity-Zone”. If you accidentially provide an additional zone id in the request body, then the value of the zone id header is taken.
$ curl -X POST \
https://uaa.dev.cfdev.sh/mfa-providers \
-H 'Accept: application/json' \
-H 'Authorization: Bearer 68973xxx4ff94e57xxxd5a852189xx' \
-H 'Content-Type: application/json' \
-H 'Host: uaa.dev.cfdev.sh' \
-H 'X-Identity-Zone-Id: gomagic' \
-d '{
"name" : "gomagicGoogleAuthenticator",
"identityZoneId": "support-apps",
"config" : {
"providerDescription" : "Google MFA for gomagic apps zone"
},
"type" : "google-authenticator"
}'
Activate Google Authenticator in the new Identity Zone
The important thing here is the mfaConfig which enables the previously created mfa provider.
curl -X PUT \
https://uaa.dev.cfdev.sh/identity-zones/gomagic \
-H 'Accept: application/json' \
-H 'Authorization: Bearer 68973xxx4ff94e57xxxd5a852189xx' \
-H 'Content-Type: application/json' \
-H 'Host: uaa.dev.cfdev.sh' \
-H 'X-Identity-Zone-Id: gomagic' \
-d '{
"id": "gomagic",
"subdomain": "gomagic",
"config": {
"clientSecretPolicy": {
"minLength": 0,
"maxLength": 255,
"requireUpperCaseCharacter": 0,
"requireLowerCaseCharacter": 0,
"requireDigit": 0,
"requireSpecialCharacter": 0
},
"tokenPolicy": {
"accessTokenValidity": 43200,
"refreshTokenValidity": 2592000,
"jwtRevocable": false,
"refreshTokenUnique": false,
"refreshTokenFormat": "jwt",
"activeKeyId": "active-key-1",
"keys" : {
"active-key-1" : {
"signingKey" : "key"
}
}
},
"samlConfig" : {
"assertionSigned" : true,
"requestSigned" : true,
"wantAssertionSigned" : true,
"wantAuthnRequestSigned" : false,
"assertionTimeToLiveSeconds" : 600,
"activeKeyId" : "legacy-saml-key",
"keys" : {
"legacy-saml-key" : {
"key" : "-----BEGIN RSA PRIVATE KEY-----\ntake the sample data from the api docs\n-----END RSA PRIVATE KEY-----\n",
"passphrase" : "password",
"certificate" : "-----BEGIN CERTIFICATE-----\ntake the sample data from the api docs\n-----END CERTIFICATE-----\n"
}
},
"entityID" : "cloudfoundry-saml-login",
"disableInResponseToCheck" : false,
"certificate" : "-----BEGIN CERTIFICATE-----\ntake the sample data from the api docs\n-----END CERTIFICATE-----\n",
"privateKey" : "-----BEGIN RSA PRIVATE KEY-----\ntake the sample data from the api docs\n-----END RSA PRIVATE KEY-----\n",
"privateKeyPassword" : "password"
},
"corsPolicy": {
"xhrConfiguration": {
"allowedOrigins": [
".*"
],
"allowedOriginPatterns": [],
"allowedUris": [
".*"
],
"allowedUriPatterns": [],
"allowedHeaders": [
"Accept",
"Authorization",
"Content-Type"
],
"allowedMethods": [
"GET"
],
"allowedCredentials": false,
"maxAge": 1728000
},
"defaultConfiguration": {
"allowedOrigins": [
".*"
],
"allowedOriginPatterns": [],
"allowedUris": [
".*"
],
"allowedUriPatterns": [],
"allowedHeaders": [
"Accept",
"Authorization",
"Content-Type"
],
"allowedMethods": [
"GET"
],
"allowedCredentials": false,
"maxAge": 1728000
}
},
"links": {
"logout": {
"redirectUrl": "/login",
"redirectParameterName": "redirect",
"disableRedirectParameter": false,
"whitelist": [
"https://console.dev.cfdev.sh/",
"https://apps.dev.cfdev.sh/"
]
},
"homeRedirect": "/",
"selfService": {
"selfServiceLinksEnabled": true,
"signup": "/create_account",
"passwd": "/forgot_password"
}
},
"prompts": [
{
"name": "username",
"type": "text",
"text": "Email"
},
{
"name": "password",
"type": "password",
"text": "Password"
},
{
"name": "passcode",
"type": "password",
"text": "Temporary Authentication Code ( Get one at https://login.dev.cfdev.sh/passcode )"
}
],
"idpDiscoveryEnabled": false,
"branding": {
"companyName": "nomagic.net",
"productLogo": "some-base64-encoded-image",
"squareLogo": "some-base64-encoded-image"
},
"accountChooserEnabled": false,
"userConfig": {
"defaultGroups": [
"openid",
"scim.me",
"cloud_controller.read",
"cloud_controller.write",
"cloud_controller_service_permissions.read",
"password.write",
"uaa.user",
"approvals.me",
"oauth.approvals",
"notification_preferences.read",
"notification_preferences.write",
"profile",
"roles",
"user_attributes",
"uaa.offline_token",
"actuator.read",
"cloud_controller.user"
]
},
"mfaConfig": {
"enabled": true,
"providerName": "gomagicGoogleAuthenticator",
"identityProviders": [
"uaa",
"ldap"
]
}
},
"name": "gomagic",
"version": 1,
"description": "The zone for the cool gomagic apps",
"created": 946684800000,
"active": true,
"last_modified": 1557340772000
}'
Register your first user
On our newly created login page we can click “Create account”.
Unfortunately there is a problem while sending the activation link. But the account has been created and we still can activate our new user manually. We will fix this problem later on.
We need the id of the newly created user
We can get a list of all users in our identity zone. There should be only one user which is not yet activated.
curl -X GET \
https://uaa.dev.cfdev.sh/Users \
-H 'Accept: application/json' \
-H 'Authorization: Bearer 68973xxx4ff94e57xxxd5a852189xx' \
-H 'Host: uaa.dev.cfdev.sh' \
-H 'X-Identity-Zone-Id: gomagic' \
Retrieve the activation link for the user
curl -X GET \
'https://uaa.dev.cfdev.sh/Users/9cd978-2a8e-4e30-837d-6ed1f5/verify-link?redirect_uri=%2A' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer 68973xxx4ff94e57xxxd5a852189xx' \
-H 'Host: uaa.dev.cfdev.sh' \
-H 'X-Identity-Zone-Id: gomagic' \
In the response you will get a link to activate the account.
Connect your Google Authenticator
Now it is time to download the Google Authenticator App to your smartphone.
What next?
The next steps for me are
- develop a web app which uses the new identity zone
- learn more about uaa
- fix the problem with the activation link