Storing OpenDJ server keys on the Nitrokey HSM

ForgeRock Logo The Nitrokey HSM provides a PKCS#11 hardware security module the form of a USB key. The design is based on open hardware and open software.

This is a low cost option to familiarize yourself with an actual hardware HSM, and to test your procedures. With it, you can demonstrate that OpenDJ servers can in fact use the HSM as a key store.

In addition to the documentation that you can access through https://www.nitrokey.com/start, see https://raymii.org/s/articles/Get_Started_With_The_Nitrokey_HSM.html for a helpful introduction.

The current article demonstrates generating and storing keys and certificates on the Nitrokey HSM, and then using they keys to protect OpenDJ server communications. It was tested with a build from the current master branch. Thanks to Fabio Pistolesi and others for debugging advice.

This article does not describe how to install the prerequisite tools and libraries to work with the Nitrokey HSM on your system. The introduction mentioned above briefly describes installation on a couple of Linux distributions, but the software itself seems to be cross-platform.

When you first plug the Nitrokey HSM into a USB slot, it has PINs but no keys. The following examples examine the mostly empty Nitrokey HSM when initially plugged in:

# List devices:
$ opensc-tool --list-readers
# Detected readers (pcsc)
Nr. Card Features Name
0 Yes Nitrokey Nitrokey HSM (010000000000000000000000) 00 00

# List slots, where you notice that the Nitrokey HSM is in slot 0 on this system:
$ pkcs11-tool --list-slots
Available slots:
Slot 0 (0x0): Nitrokey Nitrokey HSM (010000000000000000000000) 00 00
 token label : SmartCard-HSM (UserPIN)
 token manufacturer : www.CardContact.de
 token model : PKCS#15 emulated
 token flags : rng, login required, PIN initialized, token initialized
 hardware version : 24.13
 firmware version : 2.5
 serial num : DENK0100751

The following example initializes the Nitrokey HSM, using the default SO PIN and a user PIN of 648219:

$ sc-hsm-tool --initialize --so-pin 3537363231383830 --pin 648219
Using reader with a card: Nitrokey Nitrokey HSM (010000000000000000000000) 00 00
Version : 2.5
Config options :
 User PIN reset with SO-PIN enabled
SO-PIN tries left : 15
User PIN tries left : 3

The following example tests the PIN on the otherwise empty Nitrokey HSM:

$ pkcs11-tool --test --login --pin 648219
Using slot 0 with a present token (0x0)
C_SeedRandom() and C_GenerateRandom():
 seeding (C_SeedRandom) not supported
 seems to be OK
Digests:
 all 4 digest functions seem to work
 MD5: OK
 SHA-1: OK
 RIPEMD160: OK
Signatures (currently only RSA signatures)
Signatures: no private key found in this slot
Verify (currently only for RSA):
 No private key found for testing
Unwrap: not implemented
Decryption (RSA)
No errors

The following example generates a key pair on the Nitrokey HSM:

$ pkcs11-tool \
 --module opensc-pkcs11.so \
 --keypairgen --key-type rsa:2048 \
 --id 10 --label server-cert \
 --login --pin 648219
Using slot 0 with a present token (0x0)
Key pair generated:
Private Key Object; RSA
  label: server-cert
  ID: 10
  Usage: decrypt, sign, unwrap
Public Key Object; RSA 2048 bits
  label: server-cert
  ID: 10
  Usage: encrypt, verify, wrap

The following examples show what is on the Nitrokey HSM:

$ pkcs15-tool --dump
Using reader with a card: Nitrokey Nitrokey HSM (010000000000000000000000) 00 00
PKCS#15 Card [SmartCard-HSM]:
 Version : 0
 Serial number : DENK0100751
 Manufacturer ID: www.CardContact.de
 Flags :

PIN [UserPIN]
 Object Flags : [0x3], private, modifiable
 ID : 01
 Flags : [0x812], local, initialized, exchangeRefData
 Length : min_len:6, max_len:15, stored_len:0
 Pad char : 0x00
 Reference : 129 (0x81)
 Type : ascii-numeric
 Path : e82b0601040181c31f0201::
 Tries left : 3

PIN [SOPIN]
 Object Flags : [0x1], private
 ID : 02
 Flags : [0x9A], local, unblock-disabled, initialized, soPin
 Length : min_len:16, max_len:16, stored_len:0
 Pad char : 0x00
 Reference : 136 (0x88)
 Type : bcd
 Path : e82b0601040181c31f0201::
 Tries left : 15

Private RSA Key [server-cert]
 Object Flags : [0x3], private, modifiable
 Usage : [0x2E], decrypt, sign, signRecover, unwrap
 Access Flags : [0x1D], sensitive, alwaysSensitive, neverExtract, local
 ModLength : 2048
 Key ref : 1 (0x1)
 Native : yes
 Auth ID : 01
 ID : 10
 MD:guid : b4212884-6800-34d5-4866-11748bd12289

Public RSA Key [server-cert]
 Object Flags : [0x0]
 Usage : [0x51], encrypt, wrap, verify
 Access Flags : [0x2], extract
 ModLength : 2048
 Key ref : 0 (0x0)
 Native : no
 ID : 10
 DirectValue : 

$ pkcs15-tool --read-public-key 10
Using reader with a card: Nitrokey Nitrokey HSM (010000000000000000000000) 00 00
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlxvdwL6PyRnOs58L0X8d
2z8/WcgA/beoR+p08nymN8KZ4KlWKUo93AKMcFBUW8Bl8zFC80P9ZlNIXM8NSmPr
cBR9Nmpi0nUQDgfTi8vIU51tD84UcYetxX9rSHbh+CKqUmmSk6f7JPIyT6RonrOo
QJQyFmIi4oV9/d0Op8WVCbL7omYaPFwYbdUPetM1MfVyLNpkhzVdvZJE0F46hXF8
Sspqjh4f9KkJWdozIOND8ZTFvxP5Cs1y/kvvuhfjWVAtii52E4LKXRr53SA5Spl2
v1oNu5sqoaEd/SNxjj/52iH6zeGm61I7wbcIgvcHCI5CKONKceSL3PkIYzHeJMu2
SQIDAQAB
-----END PUBLIC KEY-----

The following example self-signs a public key certificate and writes it to the Nitrokey HSM. The example uses openssl, and configures an engine to use the Nitrokey HSM, which implements PKCS#11. The configuration for the OpenSSL engine is stored in a file called hsm.conf. On an Ubuntu 17.04 laptop, the PKCS#11 library installed alongside the tools is /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so as shown below:

$ cat hsm.conf
# PKCS11 engine config
openssl_conf = openssl_def

[openssl_def]
engines = engine_section

[req]
distinguished_name = req_distinguished_name

[req_distinguished_name]
# empty.

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/openssl-1.0.2/engines/libpkcs11.so
MODULE_PATH = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
PIN = 648219
init = 0

# Check the engine configuration. In this case, the PKCS11 engine loads fine:
$ OPENSSL_CONF=./hsm.conf openssl engine -tt -c(rdrand) Intel RDRAND engine
 [RAND]
     [ available ]
(dynamic) Dynamic engine loading support
     [ unavailable ]
(pkcs11) pkcs11 engine
 [RSA]
     [ available ]

# Create a self-signed certificate and write it to server-cert.pem.
# Notice that the key is identified using slot-id:key-id:
$ OPENSSL_CONF=./hsm.conf openssl req \
 -engine pkcs11 -keyform engine -new -key 0:10 \
 -nodes -days 3560 -x509 -sha256 -out "server-cert.pem" \
 -subj "/C=FR/O=Example Corp/CN=opendj.example.com"
engine "pkcs11" set.
No private keys found.

The openssl command prints a message, “No private keys found.” Yet, it still returns 0 (success) and writes the certificate file:

$ more server-cert.pem
-----BEGIN CERTIFICATE-----
MIIC/jCCAeYCCQD1SEBmUy8aCzANBgkqhkiG9w0BAQsFADBBMQswCQYDVQQGEwJG
UjEVMBMGA1UECgwMRXhhbXBsZSBDb3JwMRswGQYDVQQDDBJvcGVuZGouZXhhbXBs
ZS5jb20wHhcNMTcwODE0MTEzNzA0WhcNMjcwNTE0MTEzNzA0WjBBMQswCQYDVQQG
EwJGUjEVMBMGA1UECgwMRXhhbXBsZSBDb3JwMRswGQYDVQQDDBJvcGVuZGouZXhh
bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXG93Avo/J
Gc6znwvRfx3bPz9ZyAD9t6hH6nTyfKY3wpngqVYpSj3cAoxwUFRbwGXzMULzQ/1m
U0hczw1KY+twFH02amLSdRAOB9OLy8hTnW0PzhRxh63Ff2tIduH4IqpSaZKTp/sk
8jJPpGies6hAlDIWYiLihX393Q6nxZUJsvuiZho8XBht1Q960zUx9XIs2mSHNV29
kkTQXjqFcXxKymqOHh/0qQlZ2jMg40PxlMW/E/kKzXL+S++6F+NZUC2KLnYTgspd
GvndIDlKmXa/Wg27myqhoR39I3GOP/naIfrN4abrUjvBtwiC9wcIjkIo40px5Ivc
+QhjMd4ky7ZJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIX0hQadHtv1c0P7ObpF
sDnIWMRVtq0s+NcXvMEwKhLHHvEHrId9ZM3Ywn0P3CFd+WXMWMNSVz51cPn6SKPS
pdN9CVq6B26cFrvzWrsD06ohP6jCkXEBSshlE/k71FcnukBuJHNzj8O6JMwfyWeE
xT53WIvgz9t02B/ObZSYFlUNX+WApPCbILTHazEzYws3AN0hZPmv4Ng1Vt71nNiT
EZskTxvKsmuEuG5E8j79zO0TYvOrGCISzS3PFRrl7G83vNaSyzBIhTYF2Ilt2g7B
jnNc1/k8R/TXwskJR8gL7EFZyakQ6xUiboFDf6PWa4KMLJVNX5HsVGyLvP9FiVkY
82A=
-----END CERTIFICATE-----

The following example writes the certificate to the Nitrokey HSM:

# Transform the certificate to binary format:
$ openssl x509 -in server-cert.pem -out server-cert.der -outform der

# Write the binary format to the Nitrokey HSM, with the label (aka alias) "server-cert":
$ pkcs11-tool \
 --module opensc-pkcs11.so \
 --login --pin 648219 \
 --write-object server-cert.der --type cert \
 --id 10 --label server-cert
Using slot 0 with a present token (0x0)
Created certificate:
Certificate Object, type = X.509 cert
  label:      Certificate
  ID:         10

With the keys and certificate loaded on the Nitrokey HSM, prepare to use it with Java programs. If the Java environment is configured to access the HSM, then you can just use it. In testing, however, where you are trying the HSM, and the Java environment is not configured to use it, you can specify the configuration:

# Edit a configuration file for Java programs to access the Nitrokey HSM:
$ cat /path/to/hsm.conf
name = NitrokeyHSM
description = SunPKCS11 with Nitrokey HSM
library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
slot = 0

# Verify that the Java keytool command can read the certificate on the Nitrokey HSM:
$ keytool \
  -list \
  -keystore NONE \
  -storetype PKCS11 \
  -storepass 648219 \
  -providerClass sun.security.pkcs11.SunPKCS11 \
  -providerArg /path/to/hsm.conf

Keystore type: PKCS11
Keystore provider: SunPKCS11-NitrokeyHSM

Your keystore contains 1 entry

server-cert, PrivateKeyEntry,
Certificate fingerprint (SHA1): B9:A2:88:5F:69:8E:C6:FB:C2:29:BF:F8:39:51:F6:CC:5A:0C:CC:10

An OpenDJ server needs to access the configuration indirectly, as there is no setup parameter to specify the HSM configuration file. Add your own settings to extend the Java environment configuration as in the following example:

$ cat /path/to/java.security
# Security provider for accessing Nitrokey HSM:
security.provider.10=sun.security.pkcs11.SunPKCS11 /path/to/hsm.conf

# Unzip OpenDJ server files and edit the configuration before running setup:
$ cd /path/to && unzip 

# Set the Java args to provide access to the Nitrokey HSM.
# Make opendj/template/config/java.properties writable, and edit.
# This allows the OpenDJ server to start as needed:
$ grep java.security /path/to/opendj/template/config/java.properties
start-ds.java-args=-server -Djava.security.properties=/path/to/java.security

# Set up the server:
$ OPENDJ_JAVA_ARGS="-Djava.security.properties=/path/to/java.security" \
 /path/to/opendj/setup \
 directory-server \
 --rootUserDN "cn=Directory Manager" \
 --rootUserPassword password \
 --hostname opendj.example.com \
 --ldapPort 1389 \
 --certNickname server-cert \
 --usePkcs11keyStore \
 --keyStorePassword 648219 \
 --enableStartTLS \
 --ldapsPort 1636 \
 --httpsPort 8443 \
 --adminConnectorPort 4444 \
 --baseDN dc=example,dc=com \
 --ldifFile /path/to/Example.ldif \
 --acceptLicense

To debug, you can set security options such as the following:

OPENDJ_JAVA_ARGS="-Djava.security.debug=sunpkcs11,pkcs11 -Djava.security.properties=/path/to/java.security"

The following example shows an LDAP search that uses StartTLS to secure the connection:

$ /path/to/opendj/bin/ldapsearch --port 1389 --useStartTLS --baseDn dc=example,dc=com "(uid=bjensen)" cn

Server Certificate:

User DN  : CN=opendj.example.com, O=Example Corp, C=FR
Validity : From 'Mon Aug 14 13:37:04 CEST 2017'
             To 'Fri May 14 13:37:04 CEST 2027'
Issuer   : CN=opendj.example.com, O=Example Corp, C=FR



Do you trust this server certificate?

  1) No
  2) Yes, for this session only
  3) Yes, also add it to a truststore
  4) View certificate details

Enter choice: [2]: 4


[
[
  Version: V1
  Subject: CN=opendj.example.com, O=Example Corp, C=FR
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun RSA public key, 2048 bits
modulus:
19075725396235933137769598662661614197862047561628746980441589981485944705910796672312984856468967795133561692335016063740885234669000544938180872617609018349362382746691431903457463096067521727428890407876216335060234859298584617093111442598717549413985534234195585205628275977771336192817217401466821950358077667360760303781781546092776529804134165206111430903307470063770954498312408782707671718644473532565867636087296875111917369665456339790081809729622515754260638402122026793085096606980136589008904235094266835122846140853242190316629669042978441585862504373498978113550866427439699045924980942028978028000841
  public exponent: 65537
  Validity: [From: Mon Aug 14 13:37:04 CEST 2017,
               To: Fri May 14 13:37:04 CEST 2027]
  Issuer: CN=opendj.example.com, O=Example Corp, C=FR
  SerialNumber: [    f5484066 532f1a0b]

]
  Algorithm: [SHA256withRSA]
  Signature:
0000: 85 F4 85 06 9D 1E DB F5   73 43 FB 39 BA 45 B0 39  ........sC.9.E.9
0010: C8 58 C4 55 B6 AD 2C F8   D7 17 BC C1 30 2A 12 C7  .X.U..,.....0*..
0020: 1E F1 07 AC 87 7D 64 CD   D8 C2 7D 0F DC 21 5D F9  ......d......!].
0030: 65 CC 58 C3 52 57 3E 75   70 F9 FA 48 A3 D2 A5 D3  e.X.RW>up..H....
0040: 7D 09 5A BA 07 6E 9C 16   BB F3 5A BB 03 D3 AA 21  ..Z..n....Z....!
0050: 3F A8 C2 91 71 01 4A C8   65 13 F9 3B D4 57 27 BA  ?...q.J.e..;.W'.
0060: 40 6E 24 73 73 8F C3 BA   24 CC 1F C9 67 84 C5 3E  @n$ss...$...g..>
0070: 77 58 8B E0 CF DB 74 D8   1F CE 6D 94 98 16 55 0D  wX....t...m...U.
0080: 5F E5 80 A4 F0 9B 20 B4   C7 6B 31 33 63 0B 37 00  _..... ..k13c.7.
0090: DD 21 64 F9 AF E0 D8 35   56 DE F5 9C D8 93 11 9B  .!d....5V.......
00A0: 24 4F 1B CA B2 6B 84 B8   6E 44 F2 3E FD CC ED 13  $O...k..nD.>....
00B0: 62 F3 AB 18 22 12 CD 2D   CF 15 1A E5 EC 6F 37 BC  b..."..-.....o7.
00C0: D6 92 CB 30 48 85 36 05   D8 89 6D DA 0E C1 8E 73  ...0H.6...m....s
00D0: 5C D7 F9 3C 47 F4 D7 C2   C9 09 47 C8 0B EC 41 59  \..

When using an HSM with an OpenDJ server, keep in mind the following caveats:

  • Each time the server needs to access the keys, it accesses the HSM. You can see this with the Nitrokey HSM because it flashes a small red LED when accessed. Depending on the HSM, this could significantly impact performance.
  • The key manager provider supports PKCS#11 as shown. The trust manager provider implementation does not, however, support PKCS#11 at the time of this writing, though there is an RFE for that (OPENDJ-4191).
  • The Crypto Manager stores symmetric keys for encryption using the cn=admin data backend, and the symmetric keys cannot currently be stored in a PKCS#11 module.

ForgeRock Identity Platform 5.0 docs

By now you have probably read the news about the ForgeRock Identity Platform 5.0 release.

This major platform update comes with many documentation changes and improvements:

Hope you have no trouble finding what you need.

This blog post was first published @ marginnotes2.wordpress.com, included here with permission.

ForgeRock Identity Platform 5.0 docs

ForgeRock Logo By now you have probably read the news about the ForgeRock Identity Platform 5.0 release.

This major platform update comes with many documentation changes and improvements:

Hope you have no trouble finding what you need.


OpenDJ: Configuration over REST

ForgeRock LogoIn recent builds of OpenDJ directory server, the REST to LDAP configuration changed quite a bit… for the better.

The draft release notes tell only part of the story:

The changes let you configure multiple endpoints each with multiple versions, resource type inheritance, subresource definitions, and protection with OAuth 2.0. This version of REST to LDAP also brings many minor improvements.

A really cool “minor” improvement is that you can now configure OpenDJ directory server over HTTP. In the draft Admin Guide, you can also find a procedure titled, To Set Up REST Access to Administrative Data.

tl;dr—Directory administrators can configure the server over REST through the /admin/config endpoint, and can read monitoring info under the /admin/monitor endpoint.

Important note: Before you go wild writing a whole new OpenDJ web-based console as a single-page app, keep in mind that the REST to LDAP implementation is still an Evolving interface, so incompatible changes can happen even in minor releases.

Here’s one example using /admin/config:

#
# This example demonstrates 
# using the /admin/config endpoint
# to create a password policy
# as a directory administrator
# who is also a regular user.
# 
# This requires a nightly build or release 
# from no earlier than late June 2016.
# 
# In order to get this working,
# first set up OpenDJ directory server
# with data from Example.ldif,
# and enable the HTTP connection handler.
#

#
# Give Kirsten Vaughan the right
# to read/write the server configuration.
# This command updates privileges, 
# which are explained in the Admin Guide:
#
/path/to/opendj/bin/ldapmodify 
 --port 1389 
 --bindDN "cn=Directory Manager" 
 --bindPassword password
dn: uid=kvaughan,ou=People,dc=example,dc=com
changetype: modify
add: ds-privilege-name
ds-privilege-name: config-read
-
add: ds-privilege-name
ds-privilege-name: config-write

#
# Give Kirsten access to write password policies.
# This command adds a global ACI.
# Global ACIs are explained in the Admin Guide:
#
/path/to/opendj/bin/dsconfig 
 set-access-control-handler-prop 
 --port 4444 
 --hostname opendj.example.com 
 --bindDN "cn=Directory Manager" 
 --bindPassword "password" 
 --add global-aci:"(target="ldap:///cn=Password Policies,cn=config")(targetscope="subtree")(targetattr="*")(version 3.0; acl "Manage password policies"; allow (all) userdn="ldap:///uid=kvaughan,ou=People,dc=example,dc=com";)" 
 --trustAll 
 --no-prompt

#
# Server config-based password policies
# are under the container entry
# /admin/config/password-policies.
# This corresponds to 
# cn=Password Policies,cn=config in LDAP.
#
# The following are standard common REST operations.
# Common REST is explained and demonstrated
# in the OpenDJ Server Dev Guide.
#
# In production, of course,
# use HTTPS (as described in the Admin Guide).
#

#
# Add a new password policy:
#
curl 
 --user kvaughan:bribery 
 --request POST 
 --header "Content-Type: application/json" 
 --data '{
    "_id": "New Account Password Policy",
    "_schema": "password-policy",
    "password-attribute": "userPassword",
    "force-change-on-add": true,
    "default-password-storage-scheme": "Salted SHA-1"
}' http://opendj.example.com:8080/admin/config/password-policies

#
# Read the new password policy:
#
# curl --user kvaughan:bribery http://opendj.example.com:8080/admin/config/password-policies/New%20Account%20Password%20Policy

#
# An exercise for the reader:
# Figure out how to set a user's pwd policy over REST.
#

If you have not yet learned how to use commons REST and OpenDJ REST to LDAP, have a look at the Server Dev Guide chapter, Performing RESTful Operations.


OpenDJ: Uploading a photo over REST

ForgeRock LogoOpenDJ REST to LDAP is based on ForgeRock common REST. ForgeRock common REST handles HTTP multipart requests, so you can upload binary data, such as JPEG photos. This blog entry demonstrates how to upload a JPEG photo as a patch using the curl command.

Before you start, set up OpenDJ server and configure HTTP access to sample directory data, as described in the OpenDJ server dev guide chapter on Performing RESTful Operations. To follow the example below, make sure you have imported data from Example.ldif and that you can read Babs’s resource over HTTP as shown in the documentation.

As of this writing, the default configuration for OpenDJ REST to LDAP does not include a mapping for a user’s jpegPhoto attribute, so you must add a mapping. To add the mapping, edit the "/users" attributes section in the configuration file /path/to/opendj/config/http-config.json to include a "photo" property in user resources. The following line adds a simple mapping from the "photo" property to the jpegPhoto LDAP attribute:

"photo" : { "simple" : { "ldapAttribute" : "jpegPhoto", "isSingleValued" : true } },

When finished, save your changes and restart OpenDJ server or at least the HTTP connection handler to have the connection handler reread the updated configuration file:

/path/to/opendj/bin/stop-ds --restart

To test your changes, copy a JPEG photo to picture.jpg in the current directory and patch Babs’s resource to add the photo:

curl 
 --request PATCH 
 --form 'json=[{"operation": "add", "field": "/photo", 
         "value": {"$ref":"cid:picture#content"}}];type=application/json' 
 --form 'picture=@picture.jpg;type=image/jpeg' 
 'http://bjensen:hifalutin@opendj.example.com:8080/users/bjensen'

The result of the operation should be Babs’s resource with the photo a (potentially very long) base64-encoded string:

{
	"_id": "bjensen",
	"_rev": "00000000160694db",
	"schemas": ["urn:scim:schemas:core:1.0"],
	"userName": "bjensen@example.com",
	"displayName": "Barbara Jensen",
	"name": {
		"givenName": "Barbara",
		"familyName": "Jensen"
	},
	"photo": "_9j_4RZJRXhpZg...AA",
	"manager": [{
		"_id": "trigden",
		"displayName": "Torrey Rigden"
	}],
	"contactInformation": {
		"telephoneNumber": "+1 408 555 1862",
		"emailAddress": "bjensen@example.com"
	},
	"meta": {
		"lastModified": "2016-03-22T09:13:23Z"
	}
}

Notice the curl command form data. When you specify the reference to the content ID, the reference takes the form:

{"$ref":"cid:identifier#(content|filename|mimetype)"}

If you want other attributes to hold the filename (picture.jpg) and MIME type (image/jpeg) of the file you upload, you can reference those as well. In the example above, {"$ref":"cid:picture#filename"} gets picture.jpg and {"$ref":"cid:picture#mimetype"} gets image/jpeg.


ForgeRock Common Audit

ForgeRock LogoCommon Audit is another new feature of the ForgeRock platform.

Common Audit is part of the platform-wide infrastructure: a framework to handle audit events using common audit event handlers that are plugged in to the individual products. The handlers record events, logging them for example into files, relational databases, or syslog. Because handlers are pluggable, new handlers can be added to interoperate with your systems that store and analyze audit data.

Each audit event is identified by a unique transaction ID. The IDs can be communicated across the products and recorded for each local event. The transaction ID is the means to track requests as they traverse the platform.

In the current platform, configuring handlers depends on the product. So there are several places in the docs to read about how to configure Common Audit:

In addition, if you want to get the source code for Common Audit, or are interested in trying out new handlers and developments, you can find it on the ForgeRock Stash server. Right now it is in the forgerock-audit git repository. (To access most code on the ForgeRock Stash server, sign in with your ForgeRock credentials. You can sign up if you have not done so.)


What’s new in the ForgeRock platform release

ForgeRock LogoPerhaps you have read yesterday’s news about ForgeRock launching the updated identity platform.

Those of us who spent the last year working on this update are proud of all the new capabilities, from the integration achieved with common components to the depth and breadth of new features across all the products in the platform.

Looking for detailed lists of what’s new? Here are some quick links to each of the products’ release notes:

I’ll drill down on some of those in future posts.


OpenDJ: LDAP Controls

OpenDJ LogoLDAP controls are a standard mechanism for extending basic LDAP operations. For example, you can use a control to ask the LDAP server to sort search results before returning them, or to return search results a few at a time.

OpenDJ directory server supports a fairly long list of controls. Let’s take a look at three of them.

“Only do this if…”

The Assertion Control tells the directory server only to process the operation if a specified assertion is true for the target entry. You can specify the assertion as a filter to match.

As an example, let’s replace Babs Jensen’s street address, but only if it is the one we are expecting. Notice the assertion filter passed to the ldapmodify request. If Babs’s street address is not “500 3rd Street”, the request does not have an effect:

$ ldapmodify 
> --port 1389 
> --bindDN uid=kvaughan,ou=people,dc=example,dc=com 
> --bindPassword bribery 
> --assertionFilter "(street=500 3rd Street)"
dn: uid=bjensen,ou=people,dc=example,dc=com
changetype: modify
replace: street
street: 33 New Montgomery Street

Processing MODIFY request for uid=bjensen,ou=people,dc=example,dc=com
MODIFY operation successful for DN uid=bjensen,ou=people,dc=example,dc=com

“Make the modification, and shut up”

The Permissive Modify Control is handy when you want to make a modification no matter what. It lets you add an attribute that already exists, or delete one that is already gone without getting an error.

As an example, let’s make sure user.0 is a member of a big static group. It doesn’t matter whether user.0 was already a member, but if not, we want to make sure user.0 is added to the group.

$ ldapmodify 
>  --port 1389 
>  --bindDN uid=user.1,ou=people,dc=example,dc=com 
>  --bindPassword password 
>  --control 1.2.840.113556.1.4.1413
dn: cn=Static,ou=Groups,dc=example,dc=com
changetype: modify
add: member
member: uid=user.0,ou=people,dc=example,dc=com

Processing MODIFY request for cn=Static,ou=Groups,dc=example,dc=com
MODIFY operation successful for DN cn=Static,ou=Groups,dc=example,dc=com

“Delete the children, too”

The Subtree Delete Control lets you delete an entire branch of entries.

As an example, let’s delete ou=Groups,dc=example,dc=com and any groups underneath. The user doing this needs an access to use the tree delete control, as in aci: (targetcontrol="1.2.840.113556.1.4.805") (version 3.0; acl "Tree delete"; allow(all) userdn ="ldap:///uid=user.1,ou=people,dc=example,dc=com";).

$ ldapdelete 
>  --port 1389 
>  --bindDN uid=user.1,ou=people,dc=example,dc=com 
>  --bindPassword password 
>  --deleteSubtree 
>  ou=Groups,dc=example,dc=com
Processing DELETE request for ou=Groups,dc=example,dc=com

DELETE operation successful for DN ou=Groups,dc=example,dc=com

As mentioned above, OpenDJ directory server supports many LDAP controls. So does OpenDJ LDAP SDK. If you want to use one in your application, see the Dev Guide chapter on Working With Controls.


OpenDJ directory server & groups

OpenDJ Logo When you think about groups in general, you realize there are different ways to define groups.

You can define a group by:

  • Specifying attributes that all members have in common (“All the software developers in Grenoble”).
  • Listing the members (“Alice, Bob, Carol”).
  • Using a mix of both (all the software developers in Grenoble, plus Alice, Bob, and Carol).

Notice that when you define a group by attributes of its members, the size of the definition is not a function of the size of the group. “All the software developers in Grenoble” remains the same size, even if the number of software developers in Grenoble varies.

Also notice that when you define a group by listing its members, the size of the definition depends mainly on the size of the group. “Alice, Bob, Carol” is about half the size of “Alice, Bob, Carol, Dan, Elena, Francis.”

OpenDJ directory server has groups of each of these types.

  • OpenDJ represents groups as directory entries. The group entry’s objectClass tells you what type of group it is.
  • Dynamic groups work by specifying attributes that all members have in common. They identify members by LDAP URLs, as in memberUrl: ldap:///ou=Developers,ou=People,dc=example,dc=com??sub?l=Grenoble. This is like specifying members by LDAP search, as in ldapsearch --baseDN ou=Product Development,ou=People,dc=example,dc=com "(l=Grenoble)".
    Dynamic group entries have objectClass: groupOfUrls. Dynamic group entries generally remain small even when they have lots of members.
  • Static groups list each member. Static group entries can have several object classes in OpenDJ, including groupOfNames, groupOfUniqueNames, and groupOfEntries.
    For groups with objectClass: groupOfNames and objectClass: groupOfEntries the member attribute is member, as in

    member: uid=Alice,ou=People,dc=example,dc=com
    member: uid=Bob,ou=People,dc=example,dc=com
    member: uid=Carol,ou=People,dc=example,dc=com
    member: cn=Grenoble Developers,ou=Groups,dc=example,dc=com
    

    As you would expect, static group entries get larger as you add members.

    You can nest groups, too, by adding a group as a member of a group. (For instance, the last member in the example is the Grenoble Developers group.)

    When you delete an entry or rename an entry, you no longer want it referenced in static groups. OpenDJ has a referential integrity plugin to handle this.

  • When you look up a dynamic group in OpenDJ what you get is the memberUrl value. This is fine if you are ready to use the LDAP URL, but not so great if you just wanted a list of the member entries. OpenDJ therefore provides what are called virtual static groups. Virtual static groups have OpenDJ look up the members of a dynamic group, and then return the list when you read the entry.

Checking group membership

In general, avoid reading and writing whole static group entries, because static groups can be large. Why read an entire 1-million-member static group entry over the network when all you want is to do is check membership for one or two entries?

To make it easy to check group membership, OpenDJ provides a virtual attribute, isMemberOf. If you explicitly request isMemberOf with a search, the values OpenDJ returns are the DNs of groups the entry belongs to. Reading isMemberOf is the recommended way to look up group membership.

Updating static groups

You can update a large OpenDJ static group without reading the group, and without checking membership beforehand. Use the Permissive Modify control (--control 1.2.840.113556.1.4.1413).

If you have an application that needs notification of changes to groups, OpenDJ has you covered there as well. Instead of polling group entries, you can use the external change log mechanism. The external change log is related to directory replication.

Where to go from here

This discussion of OpenDJ groups only scratches the surface. To dig a bit deeper, have a look at these sections in the documentation: