Json2Ldap FAQ

What security features does Json2Ldap have?

Json2Ldap is not only a convenient JSON-based web API for dealing with LDAP directories. It also provides a collection of security features on top of LDAP, such as:

  • LDAP server whitelists.

  • Connection quotas per client IP and authenticated user (bind DN).

  • Use of secure connection identifiers (CID).

  • Clients may be required to authenticate with LDAP "bind" at connection time.

  • Supports hiding of the hostname / IP address of the back-end LDAP server from from web clients.

  • TLS/SSL may be required for web client access to Json2Ldap, or for connections to the backend LDAP directory.

  • Directory access may be limited to read-only or authenticate-only.

LDAP connection pooling

Applications that experience concurrent use or are multi-threaded can benefit from create a pool of LDAP connections (one per core is deemed optimal).

  • Create N LDAP connections with ldap.connect at application startup and save their connection identifiers (CIDs).

  • Let each application thread fetch a connection (CID) from the pool, to perform operations such as ldap.search, then return it back to the pool when no longer in use.

  • Close the pooled LDAP connections at application shutdown.

Note that LDAP connections can expire according to their configured json2ldap.ldap.maxIdleTime and json2ldap.ldap.maxConnectionTime properties. The -1000 error code or the ldap.isConnected call can be used to determine whether the LDAP connection had been closed and signal to create a new one.

How to set a user's password in Microsoft Active Directory?

In Active Directory (AD) the password attribute is called unicodePwd. The password value is expected to be provided in the following format: surrounded by quotes and encoded as a UTF-16 (little-endian) string. For example:

"secret"

Failing to meet the unicodePwd encoding criteria will result in the following LDAP error:

Server is unwilling to perform (53)

MS AD also requires an SSL/TLS LDAP connection for all password modify requests. A plain (unsecured) connection will not work.

JSON mandates a UTF-8 encoding and so does Json2Ldap by extension. To work around this and feed a UTF-16 string to MS AD we suggest use of the optional binary parameter of ldap.add / ldap.modify:

The steps:

  1. Produce the UTF-16 password string as expected by AD. Don't forget the double quotes!

  2. Encode it into a BASE64 string.

  3. Set the optional binary parameter true.

How to set the user password in other directories?

The preferred method for setting user passwords in other LDAP servers is the password modify extended operation. It is supported by the following LDAP servers:

  • OpenLDAP
  • OpenDJ
  • 389 Directory Server

The above list is not exhaustive.

How to set passwordNeverExpires in Microsoft Active Directory?

This setting is actually a bit in a bitfield of the userAccountControl attribute.

When the needed bitfield is composed convert it to an unsigned integer, such as 65536, and then set the userAccountControl attribute to that integer.

cURL

You can use the popular cURL command line utility to send requests to Json2Ldap.

Example ldap.connect request with cURL:

curl -H 'application/json' --data-binary '{"method":"ldap.connect","id":1,"jsonrpc":"2.0"}' http://demo.c2id.com/json2ldap/

Example ldap.getEntry request:

curl -H 'application/json' --data-binary '{"method":"ldap.getEntry","params":{"CID":"qkiHiA35XlzVj-hDjXrFG54UIWcaVgVBbZEVQugGN5c","DN":"uid=alice,ou=people,dc=wonderland,dc=net"},"id":2,"jsonrpc":"2.0"}' http://demo.c2id.com/json2ldap/

The -H parameter sets the Content-Type HTTP request header.

The --data-binary parameter sets the HTTP POST body (with the the JSON-RPC 2.0 request).

How to add members to a group?

In a LDAP directory a group is represented by an entry that can hold zero or more member attributes, as determined by its ObjectType.

Each member attribute must be set to the DN (Distinguished Name), for example cn=alice,ou=people,dc=example,dc=com for a user. The referenced DN must be valid, if not the LDAP directory will return an error if you try to add the member.

To add a member to a group use the ldap.modify call for updating an LDAP entry.

For example:

{ "method"  : "ldap.modify",
  "params"  : { "CID"       : "15002fb7-a830-413f-9445-858cf9a2cc6d",
                "DN"        : "cn=mygroup,ou=people,dc=example,dc=com",
                "attribute" : "member",
                "type"      : "ADD",
                "values"    : [ "cn=alice,ou=people,dc=example,dc=com" ] },
  "id"      : 1,
  "jsonrpc" : "2.0" }

To add multiple members at once:

{ "method"  : "ldap.modify",
  "params"  : { "CID"       : "15002fb7-a830-413f-9445-858cf9a2cc6d",
                "DN"        : "cn=mygroup,ou=people,dc=example,dc=com",
                "attribute" : "member",
                "type"      : "ADD",
                "values"    : [ "cn=alice,ou=people,dc=example,dc=com", "cn=bob,ou=people,dc=example,dc=com" ] },
  "id"      : 1,
  "jsonrpc" : "2.0" }

Note, when a user is added to a group the LDAP directory will automatically create a virtual attribute, typically called memberOf, that points back to the group. By checking this values of this attribute you can quickly find out what groups a particular user has been assigned. The memberOf virtual attribute is read-only and cannot be edited.

How to deal with international characters?

UTF-8 is the default character set for JSON messages and for LDAP strings. Therefore Json2Ldap will always return UTF-8 encoded content.

To avoid gibberish in your request parameters when dealing with non-ASCII strings, which may occur in DNs, search filters and attribute values, set your HTTP request content type header accordingly and make sure your strings are output in UTF-8 too:

Content-Type: application/json; charset=utf8

If you don't do that you may get unexpected results, such as search filters not matching or attributes with garbled values.

Note that MS Active Directory instances may deviate from the norm and be configured with a different character set.

How to monitor and debug client interactions with the Json2Ldap web API?

Switch the logging level to DEBUG. Json2Ldap will then log all received and returned JSON-RPC 2.0 messages. This together with the logged client IP address (recorded at INFO level) enables developers and administrators to identify potential issues with a given client application, for example whether LDAP connections are closed when no longer in use.

Example logging of a client's series of requests, where the client first makes an ldap.connect, authenticating with a user DN and password:

2024-01-25T13:13:25,852 INFO http-nio-8080-exec-12 HTTP POST request: ip=192.168.0.1 path=/json2ldap/
2024-01-25T13:13:25,853 DEBUG http-nio-8080-exec-12 JSON-RPC 2.0 request: {"method":"ldap.connect","id":1,"params":{"simpleBind":{"password":"secret","DN":"uid=alice,ou=people,dc=wonderland,dc=net"}},"jsonrpc":"
2.0"}
2024-01-25T13:13:25,858 INFO http-nio-8080-exec-12 Connected to LDAP server (security=NONE): localhost:10389
2024-01-25T13:13:25,859 INFO http-nio-8080-exec-12 [CID pm5ONkgqgvRO_t_a80-alJK3ueUTB68hzdVvzFl-FK8] Registered new LDAP connection for client IP 192.168.0.1
2024-01-25T13:13:25,859 DEBUG http-nio-8080-exec-12 ldap.connect: CID=pm5ONkgqgvRO_t_a80-alJK3ueUTB68hzdVvzFl-FK8 client_ip=192.168.0.1
2024-01-25T13:13:25,859 DEBUG http-nio-8080-exec-12 ldap.connect [bind]: type=SIMPLE
2024-01-25T13:13:25,862 DEBUG http-nio-8080-exec-12 ldap.connect [bind]: authzId=dn:uid=alice,ou=people,dc=wonderland,dc=net
2024-01-25T13:13:25,863 DEBUG http-nio-8080-exec-12 User registry: Request to remove dn:
2024-01-25T13:13:25,863 DEBUG http-nio-8080-exec-12 User registry: Request to add dn:uid=alice,ou=people,dc=wonderland,dc=net
2024-01-25T13:13:25,863 INFO http-nio-8080-exec-12 [CID pm5ONkgqgvRO_t_a80-alJK3ueUTB68hzdVvzFl-FK8] Set connection authzId to dn:uid=alice,ou=people,dc=wonderland,dc=net
2024-01-25T13:13:25,864 DEBUG http-nio-8080-exec-12 JSON-RPC 2.0 response: {"result":{"CID":"pm5ONkgqgvRO_t_a80-alJK3ueUTB68hzdVvzFl-FK8"},"id":1,"jsonrpc":"2.0"}
2024-01-25T13:13:25,865 INFO http-nio-8080-exec-12 ldap.connect: Success

The client then proceeds to make an ldap.getEntry request:

2024-01-25T13:14:26,050 INFO http-nio-8080-exec-14 HTTP POST request: ip=192.168.0.1 path=/json2ldap/
2024-01-25T13:14:26,054 DEBUG http-nio-8080-exec-14 JSON-RPC 2.0 request: {"method":"ldap.getEntry","id":2,"params":{"DN":"uid=alice,ou=people,dc=wonderland,dc=net","attributes":["mail"],"CID":"pm5ONkgqgvRO_t_a8
0-alJK3ueUTB68hzdVvzFl-FK8"},"jsonrpc":"2.0"}
2024-01-25T13:14:26,054 DEBUG http-nio-8080-exec-14 ldap.getEntry: DN=uid=alice,ou=people,dc=wonderland,dc=net attributes=[mail]  normalize=false output=JSON
2024-01-25T13:14:26,058 DEBUG http-nio-8080-exec-14 ldap.getEntry: dn: uid=alice,ou=people,dc=wonderland,dc=net
mail: [email protected]
2024-01-25T13:14:26,058 DEBUG http-nio-8080-exec-14 JSON-RPC 2.0 response: {"result":{"DN":"uid=alice,ou=people,dc=wonderland,dc=net","mail":["[email protected]"]},"id":2,"jsonrpc":"2.0"}
2024-01-25T13:14:26,060 INFO http-nio-8080-exec-14 ldap.getEntry: Success

Finally, the client makes an ldap.close request to release the LDAP connection:

2024-01-25T13:16:33,515 INFO http-nio-8080-exec-16 HTTP POST request: ip=192.168.0.1 path=/json2ldap/
2024-01-25T13:16:33,517 DEBUG http-nio-8080-exec-16 JSON-RPC 2.0 request: {"method":"ldap.close","id":3,"params":{"CID":"pm5ONkgqgvRO_t_a80-alJK3ueUTB68hzdVvzFl-FK8"},"jsonrpc":"2.0"}
2024-01-25T13:16:33,518 DEBUG http-nio-8080-exec-16 User registry: Request to remove dn:uid=alice,ou=people,dc=wonderland,dc=net
2024-01-25T13:16:33,520 INFO http-nio-8080-exec-16 [CID pm5ONkgqgvRO_t_a80-alJK3ueUTB68hzdVvzFl-FK8] Removed and closed connection
2024-01-25T13:16:33,520 DEBUG http-nio-8080-exec-16 JSON-RPC 2.0 response: {"result":null,"id":3,"jsonrpc":"2.0"}
2024-01-25T13:16:33,521 INFO http-nio-8080-exec-16 ldap.close: Success

Note, the CID (connection identifier) parameter can be used to trace all client requests for a given interaction with the Json2Ldap web API.

Why do I get an LDAP error 11 (Admin Limit Exceeded) when making searches?

This error is raised when the so-called "look through" limit is exceeded. If you're using OpenDS set the ds-cfg-lookthrough-limit configuration parameter to a value that is greater than the total number of entries in your directory.

Setting up client X.509 certificate authentication

On the client side:

If you're using a Java client library to connect to Json2Ldap, such as the open source JSON-RPC 2.0 Client, you need to specify the key store where the client's public certificate and its private key are stored.

This can be done by setting the following system properties, either with the -D switch to the java command, or with System.setProperty from within your client code:

javax.net.ssl.keyStoreType=jks
javax.net.ssl.keyStore=path/to/keystore.jks
javax.net.ssl.keyStorePassword=secret

You can find more information in the following Stack Overflow post.

On the server side:

The client certificate must also be imported into a Java trust store which is made available to the web server (e.g. Tomcat) where Json2Ldap is installed.

This can be done by setting the following system properties to the web server:

javax.net.ssl.trustStoreType=jks
javax.net.ssl.trustStore = path/to/truststore.jks
javax.net.ssl.trustStorePassword=secret

More on the difference between key and trust stores.

Looking for a key store GUI?

The excellent Key Store Explorer can save you the hassle of dealing with the keytool command line utility directly, for all common tasks such as viewing a keystore's content or importing / exporting keys and certificates.

How to find out the TLS version used by the LDAP server?

Use the OpenSSL s_client utility to connect to the LDAPS server port and receive diagnosing information:

Example use:

openssl s_client -connect ldap.example.com:636

The TLS version will be reported during the SSL handshake. Note that as of 2021 use of TLS 1.3 is recommended while TLS 1.2 is still in use. The older TLS versions 1.0 and 1.1 are deemed insecure and have been deprecated.

New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

How to create an Apache Tomcat deployment package?

Script to create an Apache Tomcat ZIP package with Json2Ldap deployed as the sole web application in it:

#!/usr/bin/env bash

# Path to the Json2Ldap WAR to deploy into Tomcat
WAR_FILE="/some/path/json2ldap.war"

# The Tomcat version and download URL; make sure it's the latest stable
# available Tomcat 9.0.x
TOMCAT_VERSION=9.0.56
TOMCAT_URL="https://mirrors.netix.net/apache/tomcat/tomcat-9/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.zip"

# The ZIP file to create
ZIP_FILE="Json2Ldap-Tomcat.zip"

# The work directory
WORK_DIR="work"

# Create work directory
mkdir -p ${WORK_DIR}

# Download Tomcat
wget -O ${WORK_DIR}/tomcat.zip ${TOMCAT_URL}

# Unzip Tomcat and strip version from directory name
unzip ${WORK_DIR}/tomcat.zip -d ${WORK_DIR}/
mv -v ${WORK_DIR}/apache-tomcat-${TOMCAT_VERSION} ${WORK_DIR}/tomcat

# Clean up included Tomcat web applications
rm -rvf ${WORK_DIR}/tomcat/webapps/docs
rm -rvf ${WORK_DIR}/tomcat/webapps/examples
rm -rvf ${WORK_DIR}/tomcat/webapps/host-manager
rm -rvf ${WORK_DIR}/tomcat/webapps/manager
rm -rvf ${WORK_DIR}/tomcat/webapps/ROOT

# Executable script permissions
chmod a+x ${WORK_DIR}/tomcat/bin/*.sh

# Copy Json2Ldap WAR and make it the root (/) application
cp -v ${WAR_FILE} ${WORK_DIR}/tomcat/webapps/ROOT.war

# Create ZIP package
cd ${WORK_DIR}
zip -r ${ZIP_FILE} tomcat
echo "Created $ZIP_FILE"

With the unzipped package Tomcat can be started with the following command on port 8080 (configured in tomcat/conf/server.xml):

tomcat/bin/startup.sh

To run it in the foreground:

tomcat/bin/catalina.sh run

Go to http://localhost:8080 to check the Json2Ldap banner page.

Why JSON-RPC 2.0?

JSON-RPC provided a clean path to keep the spirit of the original LDAP protocol. Moreover, its RPC messages can flow nicely through web sockets (on our road map).

Version 2.0 of JSON-RPC was chosen over 1.0 because it enables named parameters, improving API clarity and allowing us to add new request parameters in future without breaking backward compatibility. JSON-RPC 2.0 also has better error reporting.

Why not a RESTful web API?

REST was given serious consideration. In the end JSON-RPC was deemed more appropriate for Json2Ldap for two reasons:

  1. The objective of Json2Ldap was to mimic the nature of the original LDAP protocol as closely as possible, while using JSON to encode the messages.

  2. Utility functions such as ldap.util.isValidDN and ldap.util.normalizeDN do not fit well in the concept of REST.

How about DSML?

DSML is, well, what the acronym implies - it's dismal :-) The first version of this protocol was devised in 1999, but it didn't really pick up, and even the subsequent revision in 2001 wasn't particularly successful.

Using JSON to talk to directory servers over the web has significant advantages: In terms of format, JSON encoded messages are terse and easily consumed by JavaScript programs on the browser side. In terms of API, Json2Ldap keeps a close mapping to the LDAP protocol, so programmers who have previously worked with directories would feel immediately at home.

How does the Json2Ldap API compare to SCIM?

SCIM is a recent effort to provide RESTful access to identity data. Json2Ldap on the other hand offers a general purpose web API for working with any LDAP or virtual directory, and is not tied to a particular schema.

Cloud directory with Json2Ldap?

Json2Ldap can be fitted to any LDAP directory hosted in the cloud and web-enable it so that cloud-based or on-premise apps can conveniently work with identity data stored in it.

Why was the ldap.presetBind request removed from Json2Ldap in version 1.3?

The ldap.presetBind command was removed for the sake of simplicity. We realised that explaining the various security and configuration implications of this request was rather complicated, so we scrapped it from the Json2Ldap web API. Life should now be simpler for you and us too :)