nomagic

nomagic.net

Once in a while everybody hopes for a little miracle in their projects. But there is no magic.

Enable 2 Factor Authentication in Cloud Foundry

How to enable 2FA with Google authenticator in Cloud Foundry UAA

Peter Kern

8 minute read

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.

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.

Login screen for PCFDev

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.

New login screen for the gomagic apps

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”.

New login screen for the gomagic apps

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.

Ignore this problem for now

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' \
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.

Account successfully verified

Connect your Google Authenticator

Now it is time to download the Google Authenticator App to your smartphone.

Setup MFA
Enter the time restriced login code from google authenticator
Successful login and access to the account settings

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
comments powered by Disqus

Recent posts

See more

Categories

About

Unicorns ... invite them to your release party but do not expect them to do your work.

Datenschutz

Weiterlesen