Configuration management for ForgeRock Identity Cloud. Part 1

By Kevin Schneider
Originally posted on https://medium.com/@kevsuisse

Configuration management for ForgeRock Identity Cloud. Part 1

This series of posts explores how to best handle configuration for ForgeRock Identity Cloud. It aims to provide a solution that both enhances the developer experience and enables organisations to know exactly what they configure in ForgeRock Identity Cloud. It argues for a common approach to handle configuration objects to enable an ecosystem of tools.

Configuration Management in ForgeRock Identity Cloud

Why is configuration management necessary?

ForgeRock Identity Cloud already provides a range of features that help customers handle the configuration of their tenants. Tenants can be configured via the admin interface and environments are linked via the promotion process.

Configuration is split into two types. Dynamic configuration is everything that normally is dependent on the environment, such as application clients, user data etc. Static configuration on the other hand is the basic configuration of the tenant, such as journeys, services definitions, managed object definitions etc. Static configuration is the same across development, staging and production environments. Anything that is environment specific can be set up to use ESV placeholders, for example, to set a domain name per environment.

ForgeRock Identity Cloud’s promotion process promotes static configuration from dev to staging to production. The promotion can be kicked off via the admin UI or APIs which makes it easy to integrate into a configuration pipeline.

One major question this leaves open however is how the development environment gets configured in the first place. Developers could simply be given tenant admin rights and make their changes via the admin UI. The promotion API then gives some insights into what changes had been made. In simple setups with a small team, this approach might be all that is needed. However, it quickly starts to get more complicated:

  • It’s very hard to impossible to see the exact changes that have been made. For example, it is not possible to see that a scripted decision node has been changed to now use a different default outcome.
  • An accidental change can be made unknowingly, which is then promoted to higher environments.
  • The development experience is lacking. Scripts have to be developed in the in-browser IDE or in a local IDE and then manually copied over to the dev environment.
  • Setting up a new environment based on the dev environment involves a lot of copying and manual adaptions. This is particularly an issue when using sandboxes, as they are not part of the promotion process

Sandboxes

Sandboxes provide a way for developers to test changes without the risk of pushing them to higher environments as they are detached from the promotion process. They are therefore a great development tool and are an essential part of the pipeline. However, tooling needs to be in place to easily set the starting point to the dev environment and be able to move changes to the dev environment and back.

A GIT-based flow

The solution to these issues is to adopt the principles of handling configuration as code. This means that all static configuration objects are stored in a customer-owned GIT repository. Instead of just changing the development environment directly, changes are first pushed to the repository and are then (automatically) reviewed and automatically pushed to the development environment. That repository acts as the source of truth and feeds the static configuration of the DEV environment. ForgeRock Identity Cloud’s REST interface allows importing and exporting of all configurations via the API and open source tools have been developed to do this with a simple command line interface. Later parts of this blog series will discuss some of these tools in detail.

The benefits of this solution are numerous

  • Changes become immediately visible, and it is clear what is configured and what is not
  • A change review process can be implemented based on the commits to the repository
  • Multiple people can contribute to the configuration. The risk of overwriting each other’s changes is minimized
  • Multiple environments can be set up from the same repository. For example, multiple sandboxes and the dev environment, potentially using different release versions of the configuration
  • Development can happen in fully fledged IDEs and normal development best practices can be applied to the scripts

The main question now is how this configuration should be structured to reap all these benefits.

Directory structure for config repo

The following directory structure enables an easy and self-explanatory location for each file. When working on the files, any changes should immediately make it clear what configuration objects have changed. This is important as it allows for efficient reviews of changes and makes it easier for the reviewer to understand the change and spot any issues. It also enables different tools to work from the same structure.

The goals of the repository directory structure are therefore

  • Maintain order and content of configuration to enable the use of version control tools and workflows such as git tooling
  • Break monolithic configuration into its constituent parts for more granular updates and management
  • Represent configuration as readable and editable content (e.g. decode and break out inline scripts, HTML and CSS)

Separating native files from metadata wrappers

A guiding principle is that native files should be separate from the configuration wrapper. What does that mean?

Let’s take a look at email templates as an example.

Any individual template can consist of HTML,CSS style sheets and text templates. all of these elements are wrapped in a JSON structure which also provides metadata like the ID, name and description.

If you just store the JSON as a whole, it becomes very hard for changes to be made in the first place. But it is also very hard to see what has changed if for example the default link colour is changed in the CSS. Breaking the elements out of the wrapper into individual files allows one to effectively use an IDE with all its goodness such as autocompletion, format checking, linting etc. And when the change is committed, a reviewer can easily see that just the CSS has been affected and nothing else.

Where inline configuration is broken out into separate files, the top level configuration JSON is altered to use file instead of source for the content β€” e.g.

"onCreate": {
"globals": {},
"type": "text/javascript",
"source": "require('onCreateUser').setDefaultFields(object);"
}

is converted to

"onCreate": {
"globals": {},
"type": "text/javascript",
"file": "alpha_user.onCreate.js"
}

and the javascript source is moved to the corresponding file. A similar approach is taken for breaking out HTML, CSS etc.

What not to track

Certain configuration is best left untouched. While it is technically possible to customize these, there is a high chance of breaking the tenant or causing incompatibilities with future ForgeRock Identity Cloud updates. Examples of these are

  • IDM Authentication config
  • IDM repo.ds configuration
  • Out of the box scripts like the default OAuth 2.0 Token modification scripts

File naming convention

For most file names it makes sense to use the object name of the REST interface. For example, openidm/config/managed becomes managed.json

Where the file name is created as part of the object creation, for example for scripts, templates etc, the following conventions have proven useful.

File naming should first and foremost align with any company guidelines. If there isn’t a good guideline, file names in kebab-case are frequently used.

Use a prefix for all your files. This makes it much easier to separate out of the box files from custom, customer owned ones.

An example script file name following these suggestions is

acme-collect-otp.js

Realms

Within the static configuration, a further distinction can be made between realm dependant configuration and global configuration. Typically, IDM configuration is global and AM configuration realm dependent. Password policies and themes are the exceptions.

Tree view of an example repo

β”œβ”€β”€ access-config
β”‚ └── access.json
β”œβ”€β”€ cors
β”‚ └── cors-config.json
β”œβ”€β”€ endpoints
β”‚ └── acme-endpoint-test
β”‚ β”œβ”€β”€ acme-endpoint-test.js
β”‚ └── acme-endpoint-test.json
β”œβ”€β”€ email-provider
β”‚ └── external.email.json
β”œβ”€β”€ email-templates
β”‚ β”œβ”€β”€ forgottenUsername
β”‚ β”‚ β”œβ”€β”€ forgottenUsername.css
β”‚ β”‚ β”œβ”€β”€ forgottenUsername.en.html
β”‚ β”‚ β”œβ”€β”€ forgottenUsername.en.md
β”‚ β”‚ β”œβ”€β”€ forgottenUsername.fr.html
β”‚ β”‚ β”œβ”€β”€ forgottenUsername.fr.md
β”‚ β”‚ └── forgottenUsername.json
β”œβ”€β”€ esvs
β”‚ β”œβ”€β”€ secrets
β”‚ └── variables
β”‚ β”œβ”€β”€ esv-rcs-healthcheck-enabled.json
β”‚ β”œβ”€β”€ esv-rcs-healthcheck-schedule.json
β”œβ”€β”€ internal-roles
β”œβ”€β”€ kba
β”‚ └── selfservice.kba.json
β”œβ”€β”€ managed-objects
β”‚ β”œβ”€β”€ alpha_application
β”‚ β”‚ └── alpha_application.json
β”‚ β”œβ”€β”€ alpha_assignment
β”‚ β”‚ └── alpha_assignment.json
β”‚ β”œβ”€β”€ alpha_group
β”‚ β”‚ └── alpha_group.json
β”‚ β”œβ”€β”€ alpha_organization
β”‚ β”‚ └── alpha_organization.json
β”‚ β”œβ”€β”€ alpha_role
β”‚ β”‚ └── alpha_role.json
β”‚ β”œβ”€β”€ alpha_user
β”‚ β”‚ └── alpha_user.json
β”‚ β”œβ”€β”€ bravo_application
β”‚ β”‚ └── bravo_application.json
β”‚ β”œβ”€β”€ […]
β”œβ”€β”€ realms
β”‚ β”œβ”€β”€ alpha
β”‚ β”‚ β”œβ”€β”€ journeys
β”‚ β”‚ β”‚ β”œβ”€β”€ Login
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Login.json
β”‚ β”‚ β”‚ β”‚ └── nodes
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Data Store Decision - 2998c1c9-f4c8–4a00-b2c6–3426783ee49d.json
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Increment Login Count - bba3e0d8–8525–4e82-bf48-ac17f7988917.json
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Inner Tree Evaluator - 33b24514–3e50–4180–8f08-ab6f4e51b07e.json
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Page Node - a12bc72f-ad97–4f1e-a789-a1fa3dd566c8
β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Platform Password - 0c80c39b-4813–4e67-b4fb-5a0bba85f994.json
β”‚ β”‚ β”‚ β”‚ β”‚ └── Platform Username - 7354982f-57b6–4b04–9ddc-f1dd1e1e07d0.json
β”‚ β”‚ β”‚ β”‚ └── Page Node - a12bc72f-ad97–4f1e-a789-a1fa3dd566c8.json
β”‚ β”‚ β”œβ”€β”€ password-policy
β”‚ β”‚ β”‚ └── alpha_user-password-policy.json
β”‚ β”‚ β”œβ”€β”€ realm-config
β”‚ β”‚ β”‚ └── authentication.json
β”‚ β”‚ β”œβ”€β”€ scripts
β”‚ β”‚ β”‚ β”œβ”€β”€ acme-test-script.json
β”‚ β”‚ β”‚ └── scripts-content
β”‚ β”‚ β”‚ └── AUTHENTICATION_TREE_DECISION_NODE
β”‚ β”‚ β”‚ β”œβ”€β”€ acme-test-script.js
β”‚ β”‚ β”œβ”€β”€ services
β”‚ β”‚ β”‚ β”œβ”€β”€ SocialIdentityProviders.json
β”‚ β”‚ β”‚ β”œβ”€β”€ baseurl.json
β”‚ β”‚ β”‚ β”œβ”€β”€ oauth-oidc.json
β”‚ β”‚ β”‚ β”œβ”€β”€ policyconfiguration.json
β”‚ β”‚ β”‚ β”œβ”€β”€ selfServiceTrees.json
β”‚ β”‚ β”‚ └── validation.json
β”‚ β”‚ └── themes
β”‚ β”‚ β”œβ”€β”€ Contrast
β”‚ β”‚ β”‚ β”œβ”€β”€ Contrast.json
β”‚ β”‚ β”‚ β”œβ”€β”€ accountFooter.html
β”‚ β”‚ β”‚ β”œβ”€β”€ journeyFooter.html
β”‚ β”‚ β”‚ └── journeyHeader.html
β”‚ └── bravo
β”‚ β”œβ”€β”€ journeys
β”‚ β”œβ”€β”€ password-policy
β”‚ β”œβ”€β”€ realm-config
β”‚ β”œβ”€β”€ scripts
β”‚ β”œβ”€β”€ services
β”‚ └── themes
β”œβ”€β”€ schedules
β”‚ β”œβ”€β”€ rcsHealthCheck
β”‚ β”‚ β”œβ”€β”€ rcsHealthCheck.js
β”‚ β”‚ └── rcsHealthCheck.json
β”œβ”€β”€ sync
β”‚ └── connectors
β”œβ”€β”€ terms-conditions
β”‚ β”œβ”€β”€ 1.0
β”‚ β”‚ └── en.html
β”‚ └── terms-conditions.json
└── ui
└── ui-configuration.json

Structure in detail

Root level structure

sync

Contains the connector and mapping definitions
Subdirectories as follows

  • connectors contains the connector configuration, broken down into one JSON file per connector with the filename {connector-name}.json
  • mappings contains the sync mappings broken down into one JSON file per mapping with the filename {mapping-name}.json
  • rcs contains the remote connector server configuration provisioner.openicf.connectorinfoprovider.json

cors

Contains the combined CORS configuration for the tenant cors-config.json, with settings for both /am and /openidm endpoints

email-provider

The external email service configuration external.email.json

email-templates

One subdirectory {template-name} per email template. Each subdirectory contains the top level template configuration {template-name}.json, alongside separate files for

  • CSS {template-name}.css
  • HTML {template-name}.{lang}.html
  • Markdown {template-name}.{lang}.md.

access-config

The authorisation configuration access.json for /openidm endpoints

endpoints

A subdirectory per endpoint, each subdirectory containing a JSON file with the endpoint configuration {endpoint-name}.json alongside the file {endpoint-name}.js containing the endpoint JavaScript.

schedules

A subdirectory per schedule, each subdirectory containing a JSON file with the schedule configuration {schedule-name}.json, alongside the script file {schedule-name}.js where applicable.

kba

The Knowledge Based Authentication configuration for the tenant as a single JSON file selfservice.kba.json

managed-objects

One subdirectory {object-name} for each managed object configured in the tenant. Each subdirectory contains a JSON file {object-name}.json with the top level managed object configuration, alongside a separate javascript file {object-name}.{event-trigger}.json for each managed object trigger script

terms-conditions

A JSON file with the top level terms and conditions configuration terms-conditions.json, alongside a subdirectory {terms-version} for each version configured. Each subdirectory has an HTML file {language}.html for each language configured

ui

A JSON file with the hosted UI configuration

esvs

Subdirectories as follows

  • secrets- one JSON file{secret-name}.json per environment secret
  • variables- one JSON file{variable-name}.jsonper environment variable

Each JSON file has a placeholder for the valueBase64 value.

internal-roles

One JSON file {role-name}.json per authorisation role configured in the tenant. This does not include the system roles such as openidm-admin etc (system roles are detected and excluded based on their having no configured privileges).

Realm level structure

The configuration repo contains the top level directory realms with a subdirectory for both the alpha and bravo realm. Each realm directory has the following subdirectories:

scripts

This directory contains one JSON file per script defined in this realm. The scripts themselves are created under script-content/{script-type}.

journeys

This contains a subdirectory {journey-name} for each journey configured in the realm. Each of these subdirectories contains the file {journey-name}.json with the overall tree configuration, together with a nodes subdirectory containing one file per node in the tree. Node config filenames are formatted as {node-display-name} β€” {tree-node-id}.json. In the case of Page nodes, all child nodes are contained within a subdirectory named: {page-node-display-name} β€” {page-tree-node-id}.json.

password-policy

{realm}_user-password-policy.json contains the realm password policy

services

This directory contains one file {service-name}.json per access management service configured in the realm.

themes

There is one subdirectory {theme-name} per theme defined in the realm. Each theme directory has the file {theme-name}.json containing the top level theme configuration, alongside separate HTML files containing the header and footer contents.

Conclusion

Driving ForgeRock Identity Cloud configuration from a GIT repository enables customers to make use of the benefits of configuration as code. The proposed directory structure and handling of configuration enable tools to be developed that can further improve the developer experience, such as CI/CD pipelines, IDE integrations and more.

In the next article, I will detail how to use make use of the repository by describing a CI/CD pipeline approach.


More from Kevin Schneider

2 Likes