Brokering Identity Services Into Pivotal Cloud Foundry

Introduction

Pivotal Cloud Foundry (PCF) deployments are maturing across the corporate landscape. PCF’s out-of-the-box identity and access management (IAM) tool, UAA (User Accounts and Authentication), provides basic user management functions and OAuth 2.0/OIDC 1.0 support. UAA has come a long way since its inception and provides a solid foundation of IAM services for an isolated application ecosystem running on the Pivotal platform. As organizations experience ever more demanding requirements pushed on their applications, they start realizing the need for a full IAM platform that provides identity services beyond what UAA can offer. Integrating applications running on Pivotal with applications running outside the platform, providing strong and adaptive authentication journeys, managing identities across applications, enforcing security policies and more requires a full-service IAM platform like ForgeRock’s Identity Platform.

ForgeRock provides a Pivotal service broker implementation, the ForgeRock Service Broker. It runs as a small service inside Pivotal and brokers two services into the PCF platform: An OAuth 2.0 AM Service and an IG Route Service. While the OAuth 2.0 AM Service provides similar capabilities to UAA on the OAuth/OIDC side, the IG Route Service is based on IG (Identity Gateway) and can broker the full spectrum of services of the ForgeRock Identity Platform. PCF applications bound to the IG Route Service can seamlessly consume any of the countless services the ForgeRock Identity Platform provides: Intelligent authentication, authorization, federation, user-managed access, identity synchronization, user self-service, workflow, social identity, directory services, API gateway services and more.

This article provides an easy-to-follow path to:

  • Set up a PCF development environment (PCF Dev)
  • Install and configure IG in that environment
  • Install and configure the ForgeRock Service Broker in that environment
  • Deploy, integrate and protect a number of PCF sample applications using the IG Route Service and IG

Additionally, the guide provides steps how to run IG on PCF. If you have access to a full PCF instance, you can skip the PCF Dev part and dive right into the Service Broker deployment and configuration. You also need access to a ForgeRock Access Management instance 5.0 or newer.

1. Preparing a PCF Dev environment

As mentioned, if you have access to a full PCF instance, you can skip this part and go straight  to the Service Broker deployment and configuration.

1.1. Installing CF CLI

Before you install the server side of the PCF Dev environment, you must first install the Cloud Foundry Command Line Interface (CF CLI) utility, which is the main way you will interact with PCF throughout this process.

Follow the Pivotal documentation to install the flavor of the CLI you need for your workstation OS:

https://docs.run.pivotal.io/cf-cli/install-go-cli.html

1.2. Installing PCF Dev

Now that you are ready to roll with the CF CLI, it is time to download and install the PCF Dev components. This article is based on PCF Dev v0.30.0 for PCF 1.11.0. This version is based on a VirtualBox and has a number of default services installed, some of which you will need later on.

PCF Dev – PAS 2.0.18.0 is an alpha release of the NextGen PCF Dev using the native OS hypervisor, doubling the minimum memory requirements from 4G to 8G, having only a few PCF services installed by default, and taking up to 1h to start. It does however include a full BOSH Director, which is the graphical UI to manage “Tiles” in PCF vs having to use the CLI. As soon as this version is a bit more stable and bundles more services like the old one did, it might be worth upgrading. But for now, make sure you select and download v0.30.0:

https://network.pivotal.io/products/pcfdev

In order to use your own IP address and DNS name (-i and -d parameters of the cf dev start command) you need to set up a wildcard DNS record. In my case I setup *.pcfdev.mytestrun.com pointing to my workstation’s IP address where I am running PCF Dev.

Follow the command log below to install and start PCF Dev:

unzip pcfdev-v0.30.0_PCF1.11.0-osx.zip
./pcfdev-v0.30.0+PCF1.11.0-osx
cf dev start -i 192.168.37.73 -d pcfdev.mytestrun.com -m 6144
Warning: the chosen PCF Dev VM IP address may be in use by another VM or device.
Using existing image.
Allocating 6144 MB out of 16384 MB total system memory (6591 MB free).
Importing VM...
Starting VM...
Provisioning VM...
Waiting for services to start...
7 out of 58 running
7 out of 58 running
7 out of 58 running
7 out of 58 running
40 out of 58 running
56 out of 58 running
58 out of 58 running
 _______  _______  _______    ______   _______  __   __
|       ||       ||       |  |      | |       ||  | |  |
|    _  ||       ||    ___|  |  _    ||    ___||  |_|  |
|   |_| ||       ||   |___   | | |   ||   |___ |       |
|    ___||      _||    ___|  | |_|   ||    ___||       |
|   |    |     |_ |   |      |       ||   |___  |     |
|___|    |_______||___|      |______| |_______|  |___|
is now running.
To begin using PCF Dev, please run:
   cf login -a https://api.pcfdev.mytestrun.com --skip-ssl-validation
Apps Manager URL: https://apps.pcfdev.mytestrun.com
Admin user => Email: admin / Password: admin
Regular user => Email: user / Password: pass

1.3 Logging in to PCF Dev

Login to your fresh PCF Dev instance and select the org you want to work with. Use the pcfdev-org:

cf login -a https://api.pcfdev.mytestrun.com/ --skip-ssl-validation
API endpoint: https://api.pcfdev.mytestrun.com/
Email> admin
Password>
Authenticating...
OK
Select an org (or press enter to skip):
1. pcfdev-org
2. system
Org> 1
Targeted org pcfdev-org
Targeted space pcfdev-space

API endpoint:  https://api.pcfdev.mytestrun.com (API version: 2.82.0)
User:          admin
Org:            pcfdev-org
Space:          pcfdev-space

Authenticate using admin/admin if using PCF Dev or a Pivotal admin user if using a real PCF instance.

2. Install Sample Applications

To test the Service Broker and inter-application SSO, install 2 sample applications:

2.1. Spring Music

git clone https://github.com/cloudfoundry-samples/spring-music
cd spring-music

Modify manifest to reduce memory and avoid random route names:

vi manifest.yml

Enter or copy & paste the following content:

---
applications:
- name: music
  memory: 768M
  random-route: false
  path: build/libs/spring-music-1.0.jar

Push the app:

cf push

Waiting for app to start...
name:           music
requested state:   started
instances:      1/1
usage:          768M x 1 instances
routes:            music.pcfdev.mytestrun.com
last uploaded:  Tue 22 May 15:28:24 CDT 2018
stack:          cflinuxfs2
buildpack:      container-certificate-trust-store=2.0.0_RELEASE java-buildpack=v3.13-offline-https://github.com/cloudfoundry/java-buildpack.git#03b493f
                java-main open-jdk-like-jre=1.8.0_121 open-jdk-like-memory-calculator=2.0.2_RELEASE spring-auto-reconfiguration=1.10...
start command:  CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-2.0.2_RELEASE
                -memorySizes=metaspace:64m..,stack:228k.. -memoryWeights=heap:65,metaspace:10,native:15,stack:10 -memoryInitials=heap:100%,metaspace:100%
                -stackThreads=300 -totMemory=$MEMORY_LIMIT) && JAVA_OPTS="-Djava.io.tmpdir=$TMPDIR
                -XX:OnOutOfMemoryError=$PWD/.java-buildpack/open_jdk_jre/bin/killjava.sh $CALCULATED_MEMORY
                -Djavax.net.ssl.trustStore=$PWD/.java-buildpack/container_certificate_trust_store/truststore.jks
                -Djavax.net.ssl.trustStorePassword=java-buildpack-trust-store-password" && SERVER_PORT=$PORT eval exec
                $PWD/.java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -cp $PWD/. org.springframework.boot.loader.JarLauncher

     state    since                  cpu      memory          disk          details
#0   running   2018-05-22T20:29:00Z   226.8% 530.4M of 768M   168M of 512M

Note the routes: music.pcfdev.mytestrun.com

That’s the URL at which your application can be reached. You should be able to resolve the dynamically generated DNS name. You should also be able to hit the URL in a web browser.

Retrieve application logs:

cf logs music --recent

Live-tail application logs:

cf logs music

2.2. Cloud Foundry Sample NodeJS App

git clone https://github.com/cloudfoundry-samples/cf-sample-app-nodejs.git
cd cf-sample-app-nodejs

Modify manifest to reduce memory and avoid random route names:

vi manifest.yml

---
applications:
- name: node
  memory: 512M
  instances: 1
  random-route: false

Push the app:

cf push

Waiting for app to start...
name:           node
requested state:   started
instances:      1/1
usage:          512M x 1 instances
routes:            node.pcfdev.mytestrun.com
last uploaded:  Tue 22 May 15:46:02 CDT 2018
stack:          cflinuxfs2
buildpack:      node.js 1.5.32
start command:  npm start

     state    since                  cpu    memory      disk        details
#0   running   2018-05-22T20:46:35Z   0.0% 0 of 512M 0 of 512M   

Note the routes: node.pcfdev.mytestrun.com

That’s the URL at which your application can be reached. You should be able to resolve the dynamically generated DNS name. You should also be able to hit the URL in a web browser.

Retrieve application logs:

$ cf logs node —recent

Live-tail application logs:

cf logs node

2.3. Create Your Own JSP Headers App

Create your very own useful sample application to display headers. This will come in handy for future experiments with the IG Route Service.

mkdir headers
cd headers
mkdir WEB-INF
vi index.jsp

<%@ page import="java.util.*" %>
<html>
<head>
<title><%= application.getServerInfo() %></title>
</head>
<body>
<h1>HTTP Request Headers Received</h1>
<table border="1" cellpadding="3" cellspacing="3">
<%
Enumeration eNames = request.getHeaderNames();
while (eNames.hasMoreElements()) {
String name = (String) eNames.nextElement();
String value = normalize(request.getHeader(name));
%>
<tr><td><%= name %></td><td><%= value %></td></tr>
<%
}
%>
</table>
</body>
</html>
<%!
private String normalize(String value)
{
StringBuffer sb = new StringBuffer();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
sb.append(c);
if (c == ';')
sb.append("<br>");
}
return sb.toString();
}
%>
cf push headers


Waiting for app to start...
name:           headers
requested state:   started
instances:      1/1
usage:          256M x 1 instances
routes:            headers.pcfdev.mytestrun.com
last uploaded:  Tue 22 May 16:24:26 CDT 2018
stack:          cflinuxfs2
buildpack:      container-certificate-trust-store=2.0.0_RELEASE java-buildpack=v3.13-offline-https://github.com/cloudfoundry/java-buildpack.git#03b493f
                open-jdk-like-jre=1.8.0_121 open-jdk-like-memory-calculator=2.0.2_RELEASE tomcat-access-logging-support=2.5.0_RELEAS...
start command:  CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-2.0.2_RELEASE
                -memorySizes=metaspace:64m..,stack:228k.. -memoryWeights=heap:65,metaspace:10,native:15,stack:10 -memoryInitials=heap:100%,metaspace:100%
                -stackThreads=300 -totMemory=$MEMORY_LIMIT) &&  JAVA_HOME=$PWD/.java-buildpack/open_jdk_jre JAVA_OPTS="-Djava.io.tmpdir=$TMPDIR
                -XX:OnOutOfMemoryError=$PWD/.java-buildpack/open_jdk_jre/bin/killjava.sh $CALCULATED_MEMORY
                -Djavax.net.ssl.trustStore=$PWD/.java-buildpack/container_certificate_trust_store/truststore.jks
                -Djavax.net.ssl.trustStorePassword=java-buildpack-trust-store-password -Djava.endorsed.dirs=$PWD/.java-buildpack/tomcat/endorsed
                -Daccess.logging.enabled=false -Dhttp.port=$PORT" exec $PWD/.java-buildpack/tomcat/bin/catalina.sh run

     state    since                  cpu    memory        disk            details
#0   running   2018-05-22T21:24:48Z   0.0% 600K of 256M 84.6M of 512M  

2.4. More Sample Apps

git clone https://github.com/cloudfoundry-samples/cf-ex-php-info
git clone https://github.com/cloudfoundry-samples/cf-sample-app-rails.git

3. Running IG in Pivotal Cloud Foundry

You can run IG absolutely anywhere you want, but since you are going to use it inside PCF, running it in PCF may be a logic choice.

3.1. Install, Deploy, and Configure  IG in PCF

The steps below describe an opinionated deployment model for IG in PCF. Your specific environment may require you to make different choices to achieve an ideal configuration and behavior.

3.1.1. Download IG

Download IG 6 from https://backstage.forgerock.com/downloads/browse/ig/latest to a preferred working location. Login using your backstage credentials.

unzip IG-6.1.0.war
cf push ig --no-start

3.1.2. Enable Development Mode

cf set-env ig IG_RUN_MODE development

3.1.3. Create And Use Persistent Volume For Configuration Data

IG is configured using JSON files. This section is an easy way to create a share storage volume that can persist your IG configuration between restarts. If you run IG using its default configuration, it will lose all its configuration every time it restarts because the app is reset. Externalizing the config allows the configuration to reside outside the app and persist between restarts. In a real PCF environment (vs a PCF DEV environment) you would probably use a different shared storage like an NSF service or the like. But for development purposes, a local-volume will work great.

(https://github.com/cloudfoundry/local-volume-release)

cf create-service local-volume free-local-disk local-volume-instance
cf bind-service ig local-volume-instance -c '{"mount":"/var/openig"}'
cf set-env ig IG_INSTANCE_DIR '/var/openig'

3.1.4. Start IG applying all the configuration changes we have made

cf start ig

3.1.5. Logs

cf logs ig --recent

3.1.6. Apply Required Configuration

3.1.6.1. SSH into your IG instance

cf ssh ig
cd /var/openig
mkdir config
vi config/config.json

3.1.6.2. Apply configuration

Create /var/openig/config/config.json and populate with default configuration as documented here:

https://backstage.forgerock.com/docs/forgerock-service-broker/2/forgerock-service-broker-guide/#implementation-setting-up-openig

{
  "heap": [
     {
       "name": "ClientHandler",
       "type": "ClientHandler",
       "config": {
         "hostnameVerifier": "ALLOW_ALL",
         "trustManager": {
           "type": "TrustAllManager"
         }
       }
    },
    {
      "name": "_router",
      "type": "Router",
      "config": {
        "defaultHandler": {
          "type": "StaticResponseHandler",
          "config": {
            "status": 404,
            "reason": "Not Found",
            "headers": {
              "Content-Type": [
                "application/json"
              ]
            },
            "entity": "{ \"error\": \"Something went wrong, contact the sys admin\"}"
          }
        }
      }
    },
    {
      "type": "Chain",
      "name": "CloudFoundryProxy",
      "config": {
        "filters": [
          {
            "type": "ScriptableFilter",
            "name": "CloudFoundryRequestRebaser",
            "comment": "Rebase the request based on the CloudFoundry provided headers",
            "config": {
              "type": "application/x-groovy",
              "source": [
                "Request newRequest = new Request(request);",
                "org.forgerock.util.Utils.closeSilently(request);",
                "newRequest.uri = URI.create(request.headers['X-CF-Forwarded-Url'].firstValue);",
                "newRequest.headers['Host'] = newRequest.uri.host;",
                "logger.info('Receive request : ' + request.uri + ' forwarding to ' + newRequest.uri);",
                "Context newRoutingContext = org.forgerock.http.routing.UriRouterContext.uriRouterContext(context).originalUri(newRequest.uri.asURI()).build();",
                "return next.handle(newRoutingContext, newRequest);"
              ]
            }
          }
        ],
        "handler": "_router"
      },
      "capture": [
        "request",
        "response"
      ]
    }
  ],
  "handler": {
    "type": "DispatchHandler",
    "name": "Dispatcher",
    "config": {
      "bindings": [
        {
          "condition": "${not empty request.headers['X-CF-Forwarded-Url']}",
          "handler": "CloudFoundryProxy"
        },
        {
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "status": 400,
              "entity": "Bad request : expecting a header X-CF-Forwarded-Url"
            }
          }
        }
      ]
    }
  }
}

Then:

exit cf
restart ig

3.1.7. Access IG Studio

http://ig.pcfdev.mytestrun.com/openig/studio/

4. Install ForgeRock Service Broker

Download and install the service broker following the instructions in the doc:

https://backstage.forgerock.com/docs/forgerock-service-broker/2/forgerock-service-broker-guide/#implementation-installing-into-cloud-foundry

4.1. Deploy and Configure the Service Broker App

cf push forgerockbroker-app -p service-broker-servlet-2.0.1.war
cf set-env forgerockbroker-app SECURITY_USER_NAME f8Q7hyHKgz
cf set-env forgerockbroker-app SECURITY_USER_PASSWORD n3BpjwKW4m
cf set-env forgerockbroker-app OPENAM_BASE_URI https://idp.mytestrun.com/openam/
cf set-env forgerockbroker-app OPENAM_USERNAME CloudFoundryAgentAdmin
cf set-env forgerockbroker-app OPENAM_PASSWORD KZDJhN7Vr4
cf set-env forgerockbroker-app OAUTH2_SCOPES profile
cf set-env forgerockbroker-app OPENIG_BASE_URI https://ig.pcfdev.mytestrun.com
cf restage forgerockbroker-app

Note that OPENIG_BASE_URI is specified as https, not http! If specified as http, the following error occurred when binding the ig route service to an application:

cf bind-route-service pcfdev.mytestrun.com igrs --hostname spring-music-chatty-quokka
Binding route spring-music-chatty-quokka.pcfdev.mytestrun.com to service instance igrs in org pcfdev-org / space pcfdev-space as admin...
FAILED
Server error, status code: 502, error code: 10001, message: The service broker returned an invalid response for the request to http://forgerockbroker-app.pcfdev.mytestrun.com/v2/service_instances/4aa37a88-afc0-4e75-9474-d5e2ed3e7876/service_bindings/c8da2445-6689-4824-afd1-125795e2a848. Status Code: 201 Created, Body: {"route_service_url":"http://ig.pcfdev.mytestrun.com/4aa37a88-afc0-4e75-9474-d5e2ed3e7876/c8da2445-6689-4824-afd1-125795e2a848"}

To see the service broker app’s environment:

cf env forgerockbroker-app

To see the service broker app’s details:

cf app forgerockbroker-app

Create service broker:

cf create-service-broker forgerockbroker f8Q7hyHKgz n3BpjwKW4m http://forgerockbroker-app.pcfdev.mytestrun.com

Enable the service you plan on using. The ForgeRock Service Broker supports OAuth and IG. You can enable either or both.

cf enable-service-access forgerock-ig-route-service
cf enable-service-access forgerock-am-oauth2

Create the service instance(s) you will be using for your apps. You should only need one instance per service to handle any number of applications:

cf create-service forgerock-ig-route-service shared igrs
cf create-service forgerock-am-oauth2 shared amrs

4.2. Bind IG Route Service to the Sample Apps

Note how no apps are bound to the IG Route Service (igrs):

cf routes
Getting routes for org pcfdev-org / space pcfdev-space as admin ...
space          host                  domain                port  path  type  apps                  service
pcfdev-space  music                 pcfdev.mytestrun.com                     music
pcfdev-space  node                  pcfdev.mytestrun.com                     node
pcfdev-space  rails                 pcfdev.mytestrun.com                     rails
pcfdev-space  headers               pcfdev.mytestrun.com                     headrs
pcfdev-space  ig                    pcfdev.mytestrun.com                     ig
pcfdev-space  forgerockbroker-app   pcfdev.mytestrun.com                     forgerockbroker-app

Bind the Route Service to the apps:

cf bind-route-service pcfdev.mytestrun.com igrs --hostname music
cf bind-route-service pcfdev.mytestrun.com igrs --hostname node
cf bind-route-service pcfdev.mytestrun.com igrs --hostname rails
cf bind-route-service pcfdev.mytestrun.com igrs --hostname headers

Now the two sample apps are bound to our IG Route Service:

cf routes
Getting routes for org pcfdev-org / space pcfdev-space as admin ...
space          host                              domain                port  path  type  apps                  service
pcfdev-space  music                 pcfdev.mytestrun.com                     music              igrs
pcfdev-space  node                  pcfdev.mytestrun.com                     node               igrs
pcfdev-space  rails                 pcfdev.mytestrun.com                     rails              igrs
pcfdev-space  headers               pcfdev.mytestrun.com                     headers            igrs
pcfdev-space  ig                    pcfdev.mytestrun.com                     ig
pcfdev-space  forgerockbroker-app   pcfdev.mytestrun.com                     forgerockbroker-app

5. Define IG Routes for the Sample Apps

By default, no routes are defined in IG for our sample apps and the default behavior in IG (defined in config.json you created earlier) is to deny access to everything. So the next and very important step is now to define routes that re-enable access to our sample applications. Once the basic routes are defined, we can add authentication and authorization per application as we see fit:

  • Point your browser to the IG Studio: http://ig.pcfdev.mytestrun.com/openig/studio/
  • Select “Protect an Application” from the Studio home screen, then select “Structured.”
  • Select “Advanced options” and enter the app URL from the step where you pushed the app to PCF.
    • Since PCF does hostname-based routing (vs path-based) you have to change the Condition that selects your route accordingly. Into the Condition field, select “Expression” and enter:
      ${matches(request.uri.host, ‘^app-url’)}
      E.g.:
      ${matches(request.uri.host, ‘^music.pcfdev.mytestrun.com’)}
    • Pick a descriptive name and a unique ID for the application
    • Select “Create route”

  • Deploy your route.
  • You have now created a route with default configuration, which simply proxies requests through IG to the app. That means your app is available again like it was before you implemented IG and the Service Broker. The next step is to add value to your route like authentication or authorization.

5.1. Prepare for Authentication and Authorization

As a preparatory step to authentication and authorization, create an AM Service for your route, which is a piece of configuration pointing to your ForgeRock Access Management instance. Select “AM service” from the left side menu and provide the details of your AM instance:

You won’t need the agent section populated for the use cases here.

5.2. Broker Authentication to an Application

  • To add authentication to your route, select “Authentication” from the left side menu and move the slider “Enable authentication” to the right, then select “Single Sign-On” as your authentication option.
  • In the configuration dialog popping up, select your AM service:

    Then select “Save”.
  • Deploy your route.
  • In a browser, point your browser to your app URL, e.g. https://music.pcfdev.mytestrun.com/
  • Notice how you will be redirected to your Access Management login page for authentication. Provide valid login credentials and your sample app should load.
  • Repeat with the other apps. Note how you can now SSO between all the apps!
  • Now let’s add authorization to one of the routes and only allow members of a certain group access to that application. For that, we need some additional prep work in AM:
    • Create a J2EE agent IG can use to evaluate AM policies:

    • Create a new policy set with the name “PCF” or a name and ID of your liking:

      Add URL as the resource type.
    • Create a policy and name it after your application you are protecting. Specify your app URL as the resource, allow GET as an action, and specify the subject condition to require a group membership. In this example, we want membership in the “Engineering” group to be required for access to the “headers” application:

      Your policy summary page should look something like this:

  • Now come back to IG Studio and select the route of the app you created your policy for, in our case the “headers” app and select “Authorization” from the left side bar and move the slider “Enable authorization” to the right, then select “AM Policy Enforcement” as your way to authorize users.
  • Select your AM service, specify your realm and provide the name of the J2EE agent you created in an earlier step and the password. In the policy endpoint section specify the name of your policy set and the expression to retrieve your SSO token; the default should work: ${contexts.ssoToken.value}

  • Save and deploy your route.
  • Point your browser to the protected app and login using a user who is a member of the group you configured to control access. Notice how the app loads after logging in.
  • Now remove the user from the group and refresh the app. Notice how the page goes blank because the user is no longer authorized.

Conclusion

With this setup, applications can now be integrated, protected, SSO-enabled, and identity-infused within minutes. Provide profile self-service, password reset, strong and step-up authentication, continuous authentication, authorization, and risk evaluation to any application in the Pivotal Cloud Foundry ecosystem.

12 Steps to Zero Trust Success

A Google search for “zero trust” returns ~ 195Million results.  Pretty sure some are not necessarily related to access management and cyber security, but a few probably are.  Zero Trust was a term coined by analyst group Forrester back in 2010 and has gained popularity since Google started using the concept with their employee management project called BeyondCorp.


It was originally focused on network segmentation but has now come to include other aspects of user focused security management.

Below is a hybrid set of concepts that tries to cover all the current approaches.  Please comment below so we can iterate and add more to this over time.


  1. Assign unique, non-reusable identifiers to all subjects [1], objects [2] and network devices [3]
  2. Authenticate every subject
  3. Authenticate every device
  4. Inspect, verify and validate every object access request
  5. Log every object access request
  6. Authentication should contain 2 of something you have, something you are, something you know
  7. Successful authentication should result in a revocable credential [4]
  8. Credentials should be scoped and follow least privilege [5]
  9. Credentials should be bound to a user, device, transaction tuple [6]
  10. Network communications should be encrypted [7]
  11. Assume all services, API’s and applications are accessible from the Internet [8]
  12. Segment processes and network traffic in logical and operational groups


[1] – Users of systems, including employees, partners, customers and other user-interactive service accounts
[2] – API’s, services, web applications and unique data sources
[3] – User devices (such as laptops, mobiles, tablets, virtual machines), service devices (such as printers, faxes) and network management devices (such as switches, routers)
[4] – Such as a cookie, tokenId or access token which is cryptographically secure.  Revocable shouldn't necessarily be limited to being time bound. Eg revocation/black lists etc.
[5] – Credential exchange may be required where access traverses network or object segmentation.  For example an issued credential for subject 1 to access object 1, may require object 1 to contact object 2 to fulfil the request.  The credential presented to object 2 may differ to that presented to object 1.
[6] – Token binding approach such as signature based access tokens or TLS binding
[7] – Using for example standards based protocols such as TLS 1.3 or similar. Eg Google's ALTS.
[8] – Assume perimeter based networking (either software defined or network defined) is incomplete and trust cannot be placed simply on the origin of a request




The below is a list of companies referencing “zero trust” public documentation:

  • Akamai - https://www.akamai.com/uk/en/solutions/zero-trust-security-model.jsp
  • Palo Alto - https://www.paloaltonetworks.com/cyberpedia/what-is-a-zero-trust-architecture
  • Centrify - https://www.centrify.com/zero-trust-security/
  • Cisco - https://blogs.cisco.com/security/why-has-forresters-zero-trust-cybersecurity-framework-become-such-a-hot-topic
  • Microsoft - https://cloudblogs.microsoft.com/microsoftsecure/2018/06/14/building-zero-trust-networks-with-microsoft-365/
  • ScaleFT - https://www.scaleft.com/zero-trust-security/
  • zscaler - https://www.zscaler.com/blogs/corporate/google-leveraging-zero-trust-security-model-and-so-can-you
  • Okta - https://www.okta.com/resources/whitepaper-zero-trust-with-okta-modern-approach-to-secure-access/
  • ForgeRock  - https://www.forgerock.com/blog/zero-trust-importance-identity-centered-security-program
  • Duo Security - https://duo.com/blog/to-trust-or-zero-trust
  • Google’s Beyond Corp - https://beyondcorp.com/
  • Fortinet - https://www.fortinet.com/demand/gated/Forrester-Market-Overview-NetworkSegmentation-Gateways.html

12 Steps to Zero Trust Success

A Google search for “zero trust” returns ~ 195Million results.  Pretty sure some are not necessarily related to access management and cyber security, but a few probably are.  Zero Trust was a term coined by analyst group Forrester back in 2010 and has gained popularity since Google started using the concept with their employee management project called BeyondCorp.


It was originally focused on network segmentation but has now come to include other aspects of user focused security management.

Below is a hybrid set of concepts that tries to cover all the current approaches.  Please comment below so we can iterate and add more to this over time.


  1. Assign unique, non-reusable identifiers to all subjects [1], objects [2] and network devices [3]
  2. Authenticate every subject
  3. Authenticate every device
  4. Inspect, verify and validate every object access request
  5. Log every object access request
  6. Authentication should contain 2 of something you have, something you are, something you know
  7. Successful authentication should result in a revocable credential [4]
  8. Credentials should be scoped and follow least privilege [5]
  9. Credentials should be bound to a user, device, transaction tuple [6]
  10. Network communications should be encrypted [7]
  11. Assume all services, API’s and applications are accessible from the Internet [8]
  12. Segment processes and network traffic in logical and operational groups


[1] – Users of systems, including employees, partners, customers and other user-interactive service accounts
[2] – API’s, services, web applications and unique data sources
[3] – User devices (such as laptops, mobiles, tablets, virtual machines), service devices (such as printers, faxes) and network management devices (such as switches, routers)
[4] – Such as a cookie, tokenId or access token which is cryptographically secure.  Revocable shouldn't necessarily be limited to being time bound. Eg revocation/black lists etc.
[5] – Credential exchange may be required where access traverses network or object segmentation.  For example an issued credential for subject 1 to access object 1, may require object 1 to contact object 2 to fulfil the request.  The credential presented to object 2 may differ to that presented to object 1.
[6] – Token binding approach such as signature based access tokens or TLS binding
[7] – Using for example standards based protocols such as TLS 1.3 or similar. Eg Google's ALTS.
[8] – Assume perimeter based networking (either software defined or network defined) is incomplete and trust cannot be placed simply on the origin of a request




The below is a list of companies referencing “zero trust” public documentation:

  • Akamai - https://www.akamai.com/uk/en/solutions/zero-trust-security-model.jsp
  • Palo Alto - https://www.paloaltonetworks.com/cyberpedia/what-is-a-zero-trust-architecture
  • Centrify - https://www.centrify.com/zero-trust-security/
  • Cisco - https://blogs.cisco.com/security/why-has-forresters-zero-trust-cybersecurity-framework-become-such-a-hot-topic
  • Microsoft - https://cloudblogs.microsoft.com/microsoftsecure/2018/06/14/building-zero-trust-networks-with-microsoft-365/
  • ScaleFT - https://www.scaleft.com/zero-trust-security/
  • zscaler - https://www.zscaler.com/blogs/corporate/google-leveraging-zero-trust-security-model-and-so-can-you
  • Okta - https://www.okta.com/resources/whitepaper-zero-trust-with-okta-modern-approach-to-secure-access/
  • ForgeRock  - https://www.forgerock.com/blog/zero-trust-importance-identity-centered-security-program
  • Duo Security - https://duo.com/blog/to-trust-or-zero-trust
  • Google’s Beyond Corp - https://beyondcorp.com/
  • Fortinet - https://www.fortinet.com/demand/gated/Forrester-Market-Overview-NetworkSegmentation-Gateways.html

Using OpenAM as a Trusted File Authorization Engine

A common theme in the DevOps world, or any containerization style infrastructure, may be the need to verify which executables (or files in general) can be installed, run, updated or deleted within a particular environment, image or container.  There are numerous ways this could be done.  Consider a use case where exe’s, Android APK’s or other 3rd party compiled files […]

SiteMinder Policy conversion to OpenAM XACML

In this article I discuss migrating SiteMinder policies to OpenAM. I have laid out an approach and framework to make the conversion possible using XML parsing, XML codifying and custom metadata that is necessary to resolve embedded repository externalities in the SiteMinder policy definition. I have also written a Java-based program that uses the methodology discussed in this article to parse through the SiteMinder policy and resolve each policy construct to an OpenAM equivalent policy object. The program codifies the XACML output step-by-step and on completion, one is able to import the XACML policy set into OpenAM via the policy editor.

There are going to be some complications in your situation such as when converting whitelisted resource URI – that SiteMinder does not ‘trap’ in the policy definition- you will need to know which URI are not in your SM policy extract and allowed by default, and then go about creating or adding the same URI in a new or ‘converted’ OpenAM policy. You would then need to whitelist those URI by explicitly allowing access in OpenAM.

I begin by describing the general layout of an XPS domain export at a high level, and how different policy constructs in a SiteMinder policy map over to OpenAM.

Survey of SiteMinder Policy Objects

While there is not a one-to-one mapping for every policy construct, there is a logical mapping that is useful to achieve a first pass on the conversion. Some of the complications are elaborated on below.

CA.SM::Domain -> OpenAM Policy Set Definition

The CA.SM::Domain object is the parent object to several domain-specific properties such as Mode, Name and UserDirectoriesLink references. Nested directly under the Domain object are CA.SM::Realm,  CA.SM::Policy and CA.SM::Response objects.

CA.SM::Realm -> OpenAM Policy Resources

The Realm object includes properties that describe the AgentGroupLink and AuthScheme settings, session properties such as IdleTimeout, and among other things, the ResourceFilter that is the protected resource URI. Realm also contains a nested object, called the CA.SM::Rule with an associated XID that describes the Actions available for the ResourceFilter- such as GET, PUT and POST- and properties that describe regular expression matching, time restrictions, resource pattern and IsEnabled among other things.

CA.SM::Response -> OpenAM Response Attributes

Response object is a container for one or more name-value pairs, or ‘response attributes’ expected by the agent. These can be of type Web agent responses or RADIUS responses. This conversion article and the Java program are limited to handling only the Web agent responses currently.

The Response object has an associated XID, and includes properties that describe the authorization conditions when the response is applicable- such as AccessAccept and AccessChallenge- and properties for AgentTypeLink and Name.

CA.SM::ResponseAttr

Response also nests CA.SM::ResponseAttr objects, each with a unique XID, that each have properties for AgentTypeAttrLink- pointing to ReferenceObjects such as “WebAgent-HTTP-Header-Variable” indicating the means of transport for the response attributs- and also the Value of the response codified in the form of display-name=<%userattr=”profile-attribute-name”%>. Here it is assumed that the value is sourced from a user attribute, which is mostly the case, although several types of ResponseAttrs are possible, including user attribute as already mentioned, DN Attribute, Static values, Session Variable, etc.

CA.SM::Policy -> OpenAM Policy Definition

Nested under the Domain object, and at peer level to the Realm and Response objects is the CA.SM::Policy object that serves to tie together protected resource URI to the corresponding user audience, rules including actions, response objects and optionally, IP address restrictions and time restrictions for those URI.

CA.SM::PolicyLink

The Policy object container has one or more CA.SM::PolicyLink objects that can be thought of as associations between the protected resources and the responses configured for each of them. The PolicyLink objects each have CA.SM::ResponseLink and CA.SM::RuleLink properties.

CA.SM::ResponseLink and CA.SM::RuleLink

The ResponseLink is a pointer to the Response object definition, and the RuleLink is a pointer to the Rule definition stored inside a CA.SM::Realm object. When invoked for a specific Realm, the policy enforces the Rule Actions as well as the Response attributes in case the policy evaluation is successful.

CA.SM::UserPolicy

The Policy object also contains one or more CA.SM::UserPolicy objects that can be thought of as audience restriction conditions. The UserPolicy object has properties defining the FilterPath, which is a SQL query but one that could be used to locate a set of users this policy applies to. Another property is the UserDirectoryLink that is a pointer to the external repository that contains the user profile data, and specifically stores the XID of the ReferenceObject by the name CA.SM::UserDirectory.

References

Besides Domain objects, there are also reference objects at a peer level to Domain. The Reference object tree encapsulates all external information that the policy references.

CA.SM::UserDirectory

The user repository information is stored under the CA.SM::UserDirectory ReferenceObject that includes a NameSpace attribute- such as “ODBC:”- and Server name. As mentioned previously the UserPolicy object stores a reference to the UserDirectory object using a UserDirectoryLink.

The program relies on these user directory references to be resolved using a metadata container in OpenDJ.

CA.SM::AgentGroup

The AgentGroup object refers to the SiteMinder agent configurations that all protect the same resources. The attribute AgentTypeLink contains a value that is referenced from CA.SM::Response objects using the AgentTypeLink property tying the web agent to that particular response object. There could be one or more Agent Groups.

CA.SM::AgentTypeAttr

One or more of these Reference objects define in what form the agent expects the response objects to be returned. Two currently supported, and commonly used, values include “WebAgent-HTTP-Header-Variable” and “WebAgent-HTTP-Cookie-Variable”. These are available for use in accept or reject responses only in SiteMinder.

Some other Reference objects include CA.SM::AgentType, which describes the SM agent- basically a Web Agent. Another is the CA.SM::AuthScheme which includes details about the form of authentication such as Forms-based, Basic auth or API.

Conversion to OpenAM

Converting CA.SM::Domain and CA.SM::Realm objects

Simply parse out these objects from the SiteMinder policy extract and create OpenAM policy set XACML definitions.

A code sample to establish the proper order and parity between the XACML elements is shown here:

        Element target = doc.createElementNS("ns2", "ns2:Target");
 	Element anyOf_subject = getAnyOfSubjectTree(doc, xmlMap);
 	target.appendChild(anyOf_subject);

 	Element anyOf_resource = getAnyOfResourceTree(doc, xmlMap, resourceUri);
 	target.appendChild(anyOf_resource);
 		
  	Element anyOf_application = getAnyOfApplicationTree(doc, xmlMap);
       	target.appendChild(anyOf_application);
       	
       	Element anyOf_action = getAnyOfActionTree(doc, xmlMap, actions);
       	target.appendChild(anyOf_action);

A code sample for creating the Resource URI in XACML format is shown below:

private static Element getAnyOfResourceTree(Document doc, Map<String, String> xmlMap, List resourceUri) {
// many AnyOf elements to define subject, resources, actions, etc
  	Element anyOf = doc.createElementNS("ns2", "ns2:AnyOf");
  	for(String url : resourceUri) {
		Element allOf = doc.createElementNS("ns2", "ns2:AllOf");
		Element Match = doc.createElementNS("ns2", "ns2:Match");
		Match.setAttribute("MatchId", "urn:sun:opensso:entitlement:resource-match:application:"+xmlMap.get("PolicyId"));
	       	Element AttributeValue = doc.createElementNS("ns2", "ns2:AttributeValue");
                AttributeValue.setAttribute("DataType", "http://www.w3.org/2001/XMLSchema#string");
                AttributeValue.setTextContent("*://*:*" + url);
   	        Match.appendChild(AttributeValue);
	       	Element AttributeDesignator = doc.createElementNS("ns2", "ns2:AttributeDesignator");
	       	AttributeDesignator.setAttribute("Category", "urn:oasis:names:tc:xacml:3.0:attribute-category:resource");
	       	AttributeDesignator.setAttribute("AttributeId", "urn:oasis:names:tc:xacml:1.0:resource:resource-id");
	       	AttributeDesignator.setAttribute("MustBePresent", "true");
	       	AttributeDesignator.setAttribute("DataType", "http://www.w3.org/2001/XMLSchema#string");
	       	Match.appendChild(AttributeDesignator);
	       	allOf.appendChild(Match);
	       	anyOf.appendChild(allOf);
  	}
	return anyOf;
}

Converting the CA.SM::Rule.Actions objects

Here the challenge is to convert the ResourceFilters into ResourceURI objects in the XACML definition, and then add the relevant actions to those resource URI as defined in the CA.SM::Realm object definition. A sample method that adds basic actions to a policy is shown here:

private static Element getAnyOfActionTree(Document doc, Map<String, String> xmlMap, List actions) {
	// many AnyOf elements to define subject, resources, actions, etc
  		Element anyOf = doc.createElementNS("ns2", "ns2:AnyOf");
  		for(String action : actions) {
      		Element allOf = doc.createElementNS("ns2", "ns2:AllOf");
  		Element Match = doc.createElementNS("ns2", "ns2:Match");
  		Match.setAttribute("MatchId", "urn:sun:opensso:entitlement:action-match:application:"+xmlMap.get("PolicyId"));
	       	Element AttributeValue = doc.createElementNS("ns2", "ns2:AttributeValue");
	       	AttributeValue.setAttribute("DataType", "http://www.w3.org/2001/XMLSchema#string");
	       	AttributeValue.setTextContent(action);
		   Match.appendChild(AttributeValue);
	       	Element AttributeDesignator = doc.createElementNS("ns2", "ns2:AttributeDesignator");
	       	AttributeDesignator.setAttribute("Category", "urn:oasis:names:tc:xacml:3.0:attribute-category:action");
	       	AttributeDesignator.setAttribute("AttributeId", "urn:oasis:names:tc:xacml:1.0:action:action-id");
	       	AttributeDesignator.setAttribute("MustBePresent", "true");
	       	AttributeDesignator.setAttribute("DataType", "http://www.w3.org/2001/XMLSchema#string");
	       	Match.appendChild(AttributeDesignator);
	       	allOf.appendChild(Match);       	
	       	anyOf.appendChild(allOf);
  	}
  	return anyOf;
   }

Converting Policy bindings for CA.SM::Real,  CA.SM::Response and CA.SM::UserPolicy objects

This portion is the most complex in the conversion because linkages have to be parsed out of the SiteMinder policy extract and rendered as separate OpenAM policies in XACML format. In order to convert a CA.SM::Policy object the program needs to follow the following algorithm at the very at least and in order:

  1. Establish the linkages between the CA.SM::Realm objects and the CA.SM::Response objects using the PolicyLink binder object
  2. Resolve the Response object, dynamic key-value pairs, by either directly reading the data from an external repository, or converting the references attributes to LDAP specific attributes
  3. Resolve the Subject Restrictions manifest in the PolicyLink.UserPolicy by converting the SQL Query to an LDAP search filter
  4. Create one OpenAM policy under the previously established Policy Set per Realm-Response-UserPolicy linkage

These steps are necessary in order to preserve the linkage of specific resource URI being accessible over the policy decision and returning specific response attributes.

Parsing out the bindings between CA.SM::Realm and CA.SM::Response objects

Since not all CA.SM::Realm objects in the SiteMinder policy extract refer to all the CA.SM::Response objects it becomes necessary to collect those objects that are bound together and dispatch them into one OpenAM policy. This is because OpenAM ties together a group of protected resource URI to a group of subject conditions, environment conditions and response attributes.

Resolving the Response object into static or dynamic OpenAM Response Attributes

A design decision here could be that the response attributes will be pulled from a user repository such as OpenDJ, making the CA.SM::ResponseAttr.Value definitions very easy to resolve. After parsing the definitions that usually are of the form:

CL_Header=<%userattr="<%userattr="CL_Cookie_Value"%>|UID=<%userattr="DEF_PROFILE_ID"%>|State=<%userattr="ST"%>|City=<%userattr="ADDR_CITY"%>|AddressLine1=<%userattr="ADDR1"%>|AddressLine2=<%userattr="ADDR2"%>|CompanyName=<%userattr="COMPANY"%>

One could construct the response objects one by one, with the display attribute (left of the “=” sign) as key and user profile attribute within quotes above, as the value. Simplistic string split and parsing techniques could be used to achieve this. The mapping of user profile attributes shown here could also be stored as key-value pairs in a metadata container in OpenDJ:

 

meta

Here, the connection information is stored as an LDAP url in description, admin username in sn and password (hidden) in givenName. The mapping of the user attributes is stored in cn. Using this information the program resolves the key-value pairs from the source repository to that of the target repository schema and the resulting key-value pairs are codified as OpenAM Response objects into XACML, as shown:

urn:sun:opensso:entitlement:json-resource-attribute:com.sun.identity.entitlement.UserAttributes:<attr-name>

Resolve the Subject Restrictions manifest in the PolicyLink.UserPolicy

Converting subject conditions derived from CA.SM::UserPolicy objects such as this one:

userpolicy

..would involve the same framework presented above, except this time we will create an LDAP search filter from the SQL Query- again assuming we are seeking data from an LDAP repository- to ensure the policy is only fired when the subject condition is satisfied.

The program can also provide support for directly retrieving user profile attributes – either for Subject Restrictions presented above in the form of LDAP filters, or for dynamically creating the Response key-value pairs in XACML. The metadata can have connection information stored as shown above that could be used to achieve this external lookup.

 

OpenIG on Docker: The Perfect Couple

This blog post was first published @ http://identityrocks.blogspot.fr/, included here with permission.

“Docker containers wrap a piece of software in a complete filesystem that contains everything needed to run […]” [1]


OpenIG (Open Identity Gateway) provides an elegant yet flexible way to integrate your applications, devices, APIs with modern identity standards such as token types, authentication and authorization. The gateway as your integration component can be deployed and scaled along the applications and services it secures.

Whilst organizations adopt containerization as part of a “well-oiled” delivery pipeline, which includes the gateway, OpenIG in a container is also very beneficial for evaluation purposes. Here’s how you can evaluate in just a couple of minutes.

As prerequisites, you need docker and git on your system.

  1. Checkout the ForgeRock docker project to retrieve the Dockerfile and sample OpenIG configuration files
  • git clone https://stash.forgerock.org/scm/docker/docker.git
  • cd docker/openig
  • Build the docker image
  • docker build -t forgerock/openig:latest .
  • Run the docker image and mount the sample-config directory from your local git copy in the container
  • docker run –detach -p 8080:8080 –volume <LOCAL_PATH_TO_GIT>/docker/openig/sample-config:/var/openig –name openig -it forgerock/openig
To test the sample configuration, point your client (e.g. web browser, curl) to http://localhost:8080/simplethrottle for instance. The response is determined by the StaticResponseHandler setting in the 20-simplethrottle.json file.
A simplified version of this procedure however without the sample configuration but not necessitating usage of git and clone the full repo goes as follows. This is well suited to evaluate the upcoming user interface.
  1. Download the Dockerfile from https://stash.forgerock.org/projects/DOCKER/repos/docker/browse/openig/Dockerfile
  2. Build the docker image (as above)
  • docker build -t forgerock/openig:latest .
  • Run the docker image
  • docker run -d -p 8080:8080 –name openig -it forgerock/openig
Other useful commands:
  • Start container: docker stop openig
  • Stop container: docker start openig
  • Get shell prompt: docker exec -i -t openig /bin/bash
  • Remove container: docker rm openig

References

[1]Package your application into a standardized unit for software development”. Retrieved from https://www.docker.com/what-docker on Sep 6th, 2016.

 

In flight Authorization Management

This blog post was first published @ identityrelationshipmanagement.blogspot.co.uk, included here with permission.

Access request, or authorization management is far from new.  The classic use case is the use of a workflow process that, via approval, updates a profile or account with a persisted attribute/group/permission in a target system.  At run time, when a user attempts to perform an action on the target system, the system locally checks the profile of the user and looks for particular attributes that have been persisted.

A slight variation on this theme, is to provide a mechanism to alter (or at least request to alter) the persisted permissions at near run time.  An example of this, is to leverage OAuth2 and use of a tokeninfo endpoint that can convert access_token scope data into scope values, that are used by resource server to handle local authorization.  Dependent on the content of the scope values, the resource server could provide a route for those persisted entries to be updated – aka an access request.

In the above example, we have a standard OAuth2 client-server relationship on the right hand side – it just so happens we’re also using the device flow pin and pair paradigm that is described here. Ultimately the TV application retrieves user data using OAuth2 – one of the attributes we send back to the TV, is an attribute called waterShedContent – this is a boolean value that governs whether the user can access post 9pm TV shows or not.  If the value is false, the TV player does not allow access – but does then provide a link into OpenIDM – which can trigger a workflow to request access.

Above flow goes something like this:

  1. User performs OAuth2 consent to allow the TV player access to certain profile attributes (0 is just the onboarding process for the TV via pin/pair for example)
  2. OpenAM retrieves static profile data such as the waterShedContent attribute and makes available via the ../tokeninfo end point accessible using the OAuth2 access_token
  3. Client interprets the data received from the ../tokeninfo endpoint to perform local authorization (if waterShedContent == true || false for example) providing a link into OpenIDM that can trigger an access request
  4. The BPMN workflow in IDM searches for an approver and assigns them a basic boolean gateway workflow – either allow or deny.  An allow triggers an openidm.patch that updates the necessary attribute that is then stored in OpenDJ
  5. The updated attribute is then made available via the ../tokeninfo endpoint again – perhaps via a refresh_token flow and the updated attribute is available in the client
Triggering a remote workflow (step 3) is pretty trivial – simply call /openidm/workflow/processinstance?_action=create with the necessary workflow you want to trigger.  To work out who to assign the workflow to, I leveraged the new relationship management feature of IDM and used the execution.setVariable(‘approver’, approver) function within the workflow.  The approver was simply an attribute within my initial user object that I set when I created my managed/object.

The code for the PoC-level TV-player with the necessary OAuth2 and workflow request code is available here.