Your Secret is Safe with IG

Géry Ducatel
4 min readMay 15, 2021

--

Photo by Ben White on Unsplash

Of course no one shares secrets lightly. When you have been mischievous and you are harbouring burning secrets it is imperative that you confide only into your most trusted friends because real friends would not resurface those deeds unless they knew it was safe to do so. That is what IG can do for you and this article is there to tell you how.

First, some basics. IG provides a number of Secret Stores. Those are used to save and retrieve secrets as and when it is needed. The approach is to define secret stores in some initialisation phase. Secrets are then accessible on demand inside IG routes. Below is an example taken from the IG documentation:

{
"heap": [
{
"name": "SystemAndEnvSecretStore-1",
"type": "SystemAndEnvSecretStore",
"config": {
"format": "PLAIN"
}
},
{
"name": "AmService-1",
"type": "AmService",
"config": {
"url": "http://openam.example.com:8088/openam",
"agent": {
"username": "ig_agent",
"passwordSecretId": "agent.secret.id"
},
"secretsProvider": "SystemAndEnvSecretStore-1",
"version": "7",
"notifications": {
"enabled": true
}
}
}
],
"handler": {
"type": "Chain",
"config": {
"filters": [
{
"type": "SingleSignOnFilter",
"config": {
"amService": "AmService-1"
}
}
],
"handler": "ReverseProxyHandler"
}
},
"condition": "${matches(request.uri.path, '/home/systemandenvsecret')}",
"baseURI": "http://app.example.com:8081"
}

The example above defines a Secret Store called SystemAndEnvSecretStore1. This type of secret store will take your system environment variables and provide them inside your routes as secrets. There is an AM Service called AMService-1 which describes how to connect to AM. Finally the route shown here is a reverse proxy which needs users to authenticate if their URI is a match to a condition: (/home/systemandenvsecret). Crucially, the secret store delivers one secret to the AM Service (the agent password). IG uses this password to authenticate and check session validity of end users. This stuff is a great and a straightforward use of secret stores. In fact, it is secret management as IG intended.

But what if you need your own business logic to be scripted? What if your scripts need to obtain a secret? How will you fetch this secret so that your script can process it? IG wouldn’t let you down, would it? Well no.

In this scenario we need to write a script (IG has a Groovy scripting engine), provide the secret store to the script as a parameter, and extract the secret from inside the script programmatically. The secret store definition would now look like this:

[...] 
{
"name": "SecretsProvider-1",
"type": "SecretsProvider",
"config": {
"stores": [
{
"name": "SystemAndEnvSecretStore-2",
"type": "SystemAndEnvSecretStore",
"config": {
"format": "BASE64",
"mappings": [
{
"secretId": "my.secret",
"format": "BASE64"
}
]
}
}
]
}
}
[...]

There are two differences with the example we have seen above:

  1. The Secret Store is now wrapped up in a Secret Provider named SecretsProvider-1
  2. The Secret Store now includes a mapping definition

We will see how this is used below. First we need to include a scripted filter for our Groovy script. It will look something like that:

[...]
{
"name": "ScriptableFilter-1",
"type": "ScriptableFilter",
"config": {
"type": "application/x-groovy",
"file": "SecretBusinessLogic.groovy",
"args": {
"secretsProvider": "${heap['SecretsProvider-1']}",
"secretId": "my.secret"
}
}
}
[...]

Now you can see how the secrets provider wrapper is useful, you can pass it as a generic argument (i.e. it is the same for any secret store type you may choose to use). You also include the secret ID key (my.secret). The intention is to share a secret we refer to as “my.secret” to a script called SecretBusinessLogic.groovy. Everything is in place for the final act.

We need to get to the value of the secret so that we can use it, but the secret store needs to be told how. It can be achieved like this:

import org.forgerock.services.context.*;
import org.forgerock.openig.secrets.*;
import org.forgerock.secrets.*;
[...]
def enviable = Purpose.purpose(secretId, GenericSecret.class);
Object pwd = secretsProvider.getActiveSecret(enviable).get();
def bigSecret = pwd.revealAsUtf8(String.&valueOf);
logger.info("Secret: " + bigSecret);
[...]

In our script we declare a Purpose called enviable which is intended to refer to one secret alias (my.secret). We use the SecretsProvider (secretsProvider) object to obtain a GenericSecret object (pwd). The GenericSecret “reveal as UTF-8” function takes a method pointer operator (.&) because the argument of this function needs to be another function as per the JavaDoc.

Note that the secret here is captured only so that it can be seen in the standard output for debug purposes. Of course it is not the intended use. The reveal function should be used directly (i.e. not passed through a variable) and the secret would only be available in memory for a short period of time. And that is it, the secret has been passed to the script and can be used to unlock the expected logic.

Before we finish, I would like to share with you something else about IG, in fact this article was only needed to clear the way for the real secret which is this: did you know it is possible to write your very own secret store and add it to IG; how to do it is a closely guarded secret. No worries though because this will be the topic of a follow up article, see you soon!

--

--

Géry Ducatel
Géry Ducatel

Written by Géry Ducatel

Identity Management Consultant for ForgeRock.

No responses yet