Chapter 14. Securing & Hardening OpenIDM

Table of Contents
14.1. Use SSL and HTTPS
14.2. Restrict REST Access to the HTTPS Port
14.3. Encrypt Data Internally & Externally
14.4. Use Message Level Security
14.5. Replace Default Security Settings
14.6. Secure Jetty
14.7. Protect Sensitive REST Interface URLs
14.8. Protect Sensitive Files & Directories
14.9. Obfuscate Bootstrap Information
14.10. Remove or Protect Development & Debug Tools
14.11. Protect the OpenIDM Repository
14.12. Adjust Log Levels
14.13. Set Up Restart At System Boot

After following the guidance in this chapter, make sure that you test your installation to verify that it behaves as expected before putting it into production.

Out of the box, OpenIDM is set up for ease of development and deployment. When deploying OpenIDM in production, take the following precautions.

14.1. Use SSL and HTTPS

Disable plain HTTP access, included for development convenience, as described in the section titled Secure Jetty.

Use TLS/SSL to access OpenIDM, ideally with mutual authentication so that only trusted systems can invoke each other. TLS/SSL protects data on the wire. Mutual authentication with certificates imported into the applications' trust and key stores provides some confidence for trusting application access.

Augment this protection with message level security where appropriate.

14.2. Restrict REST Access to the HTTPS Port

Use certificates to secure REST access, over HTTPS. The following procedure shows how to generate a self-signed certificate to secure REST calls, over HTTPS. Note that in production systems, it is recommended that you use a key that has been signed by a certificate authority.

  1. Extract the certificate that is installed with OpenIDM.

    $ openssl s_client -showcerts -connect localhost:8443 </dev/null

    This command outputs the entire certificate to the terminal.

  2. Using any text editor, create a file named server.crt. Copy the portion of the certificate from ­­­­­BEGIN CERTIFICATE­­­­­ to ­­­­­END CERTIFICATE­­­­­ and paste it into the server.crt file. Your server.crt file should now contain something like the following:

    $ more server.crt
    -----BEGIN CERTIFICATE-----
    MIIB8zCCAVygAwIBAgIETkvDjjANBgkqhkiG9w0BAQUFADA+MSgwJgYDVQQKEx9P
    cGVuSURNIFNlbGYtU2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQDEwlsb2NhbGhv
    c3QwHhcNMTEwODE3MTMzNTEwWhcNMjEwODE3MTMzNTEwWjA+MSgwJgYDVQQKEx9P
    cGVuSURNIFNlbGYtU2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQDEwlsb2NhbGhv
    c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKwMkyvHS5yHAnI7+tXUIbfI
    nQfhcTChpWNPTHc/cli/+Ta1InTpN8vRScPoBG0BjCaIKnVVl2zZ5ya74UKgwAVe
    oJQ0xDZvIyeC9PlvGoqsdtH/Ihi+T+zzZ14oVxn74qWoxZcvkG6rWEOd42QzpVhg
    wMBzX98slxkOZhG9IdRxAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEASo4qMI0axEKZ
    m0jU4yJejLBHydWoZVZ8fKcHVlD/rTirtVgWsVgvdr3yUr0Idk1rH1nEF47Tzn+V
    UCq7qJZ75HnIIeVrZqmfTx8169paAKAaNF/KRhTE6ZII8+awst02L86shSSWqWz3
    s5xPB2YTaZHWWdzrPVv90gL8JL/N7/Q=
    -----END CERTIFICATE-----
                 
  3. Generate a private, self-signed key as follows:

    1. Generate an encrypted 1024-bit RSA key, and save it to a file named localhost.key. Enter a pass phrase for the key as requested.

      $ openssl genrsa -des3 -out localhost.key 1024
      Generating RSA private key, 1024 bit long modulus
      .........++++++
      .........................++++++
      e is 65537 (0x10001)
      Enter pass phrase for localhost.key:
      Verifying - Enter pass phrase for localhost.key:
    2. Generate a certificate request using the key you created in the previous step, and save it to a file named localhost.csr. Enter any required information to create the DN for the request.

      $ openssl req -new -key localhost.key -out localhost.csr

      This step creates a file, localhost.csr, that contains the details of the certificate request.

    3. Sign the certificate with the key you created in the previous step, and generate a certificate that is valid for one year in a file named localhost.crt. The x509 subcommand enables you to retrieve the information that is stored in the SSL certificate. Output will depend on the details that you entered in the certificate request.

      $ openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt
      Signature ok
      subject=/C=FR/ST=Il-DE-FRANCE/L=Paris/O=example.com
      Getting Private key
      Enter pass phrase for localhost.key:
                           

      The contents of localhost.crt should now be something like this:

      $ more localhost.crt
      -----BEGIN CERTIFICATE-----
      MIIB/zCCAWgCCQD6VdiF6rX2czANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJa
      QTELMAkGA1UECBMCV0MxEjAQBgNVBAcTCUNhcGUgVG93bjEUMBIGA1UEChMLZXhh
      bXBsZS5jb20wHhcNMTMwMTI1MTIzNzIyWhcNMTQwMTI1MTIzNzIyWjBEMQswCQYD
      VQQGEwJaQTELMAkGA1UECBMCV0MxEjAQBgNVBAcTCUNhcGUgVG93bjEUMBIGA1UE
      ChMLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAONLO82s
      wKA0tWkbR66DajwQKNO9QlYwZvcK4X7MFOcwex+8j2vvG5HCB0BW2Gm72mFTWei8
      gVgQDP1oe/yTWDZRaiJ8rGWdvpgH1Cmxcd3N1AhhRya1I2j5wxrc9ZsyyTYCg2fd
      pFfULrUXSd9QlB2qQZz7kb4ksT/mSwPiGqvFAgMBAAEwDQYJKoZIhvcNAQEFBQAD
      gYEA3WrP8NKjXwQzE0vabYmdUhPHt3PF8EMMwVJ+h8G9Dwmtll0P/kLybXdHF1P/
      SvN8ofdaEKi4DrLvBifkJvHdTm9DgZJo+bROM6LM9kac6CxNvwj9m/4g6mhnjxEV
      63WQPzvAeriO51JC0ysMVe5vf+lO0t+J8W6SfPTKwoXDQhY=
      -----END CERTIFICATE-----
                           
  4. Combine the contents of server.crt and localhost.crt to create a Privacy Enhanced Mail Certificate (.pem) file named CA.pem.

    $ cat server.crt localhost.crt > CA.pem

    The contents of CA.pem should be something like the following (a concatenation of server.crt and localhost.crt).

    $ more CA.pem
    -----BEGIN CERTIFICATE-----
    MIIB8zCCAVygAwIBAgIETkvDjjANBgkqhkiG9w0BAQUFADA+MSgwJgYDVQQKEx9P
    cGVuSURNIFNlbGYtU2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQDEwlsb2NhbGhv
    c3QwHhcNMTEwODE3MTMzNTEwWhcNMjEwODE3MTMzNTEwWjA+MSgwJgYDVQQKEx9P
    cGVuSURNIFNlbGYtU2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQDEwlsb2NhbGhv
    c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKwMkyvHS5yHAnI7+tXUIbfI
    nQfhcTChpWNPTHc/cli/+Ta1InTpN8vRScPoBG0BjCaIKnVVl2zZ5ya74UKgwAVe
    oJQ0xDZvIyeC9PlvGoqsdtH/Ihi+T+zzZ14oVxn74qWoxZcvkG6rWEOd42QzpVhg
    wMBzX98slxkOZhG9IdRxAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEASo4qMI0axEKZ
    m0jU4yJejLBHydWoZVZ8fKcHVlD/rTirtVgWsVgvdr3yUr0Idk1rH1nEF47Tzn+V
    UCq7qJZ75HnIIeVrZqmfTx8169paAKAaNF/KRhTE6ZII8+awst02L86shSSWqWz3
    s5xPB2YTaZHWWdzrPVv90gL8JL/N7/Q=
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    MIIB/zCCAWgCCQD6VdiF6rX2czANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJa
    QTELMAkGA1UECBMCV0MxEjAQBgNVBAcTCUNhcGUgVG93bjEUMBIGA1UEChMLZXhh
    bXBsZS5jb20wHhcNMTMwMTI1MTIzNzIyWhcNMTQwMTI1MTIzNzIyWjBEMQswCQYD
    VQQGEwJaQTELMAkGA1UECBMCV0MxEjAQBgNVBAcTCUNhcGUgVG93bjEUMBIGA1UE
    ChMLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAONLO82s
    wKA0tWkbR66DajwQKNO9QlYwZvcK4X7MFOcwex+8j2vvG5HCB0BW2Gm72mFTWei8
    gVgQDP1oe/yTWDZRaiJ8rGWdvpgH1Cmxcd3N1AhhRya1I2j5wxrc9ZsyyTYCg2fd
    pFfULrUXSd9QlB2qQZz7kb4ksT/mSwPiGqvFAgMBAAEwDQYJKoZIhvcNAQEFBQAD
    gYEA3WrP8NKjXwQzE0vabYmdUhPHt3PF8EMMwVJ+h8G9Dwmtll0P/kLybXdHF1P/
    SvN8ofdaEKi4DrLvBifkJvHdTm9DgZJo+bROM6LM9kac6CxNvwj9m/4g6mhnjxEV
    63WQPzvAeriO51JC0ysMVe5vf+lO0t+J8W6SfPTKwoXDQhY=
    -----END CERTIFICATE-----
                 
  5. Test REST access on the HTTPS port, using the certificate that you created in the previous step. For example:

    $ curl
     --header "X-OpenIDM-Username:openidm-admin"
     --header "X-OpenIDM-Password:openidm-admin"
     --cacert CA.pem
     --request GET
     "https://localhost:8443/openidm/managed/user/?_queryId=query-all-ids"
    {
        "conversion-time-ms": 0,
        "result": [
            {
                "_rev": "0",
                "_id": "8afd44a7-13be-449e-9c47-7a310e675c00"
            }
        ],
        "query-time-ms": 1
    }
                 

    Note

    If you receive the response curl: (52) Empty reply from server, check that you have, in fact, used https and not http in the URL.

14.3. Encrypt Data Internally & Externally

Beyond relying on end-to-end availability of TLS/SSL to protect data, OpenIDM also supports explicit encryption of data that goes on the wire. This can be important if the TLS/SSL termination happens prior to the final end point.

OpenIDM also supports encryption of data stored in the repository, using a symmetric key. This protects against some attacks on the data store. Explicit table mapping is supported for encrypted string values.

OpenIDM automatically encrypts sensitive data in configuration files, such as passwords. OpenIDM replaces clear text values when the system first reads the configuration file. Take care with configuration files having clear text values that OpenIDM has not yet read and updated.

14.4. Use Message Level Security

OpenIDM supports message level security, forcing authentication before granting access. Authentication works by means of a filter-based mechanism that lets you use either an HTTP Basic like mechanism or OpenIDM-specific headers, setting a cookie in the response that you can use for subsequent authentication. If you attempt to access OpenIDM URLs without the appropriate headers or session cookie, OpenIDM returns HTTP 401 Unauthorized, or HTTP 403 Forbidden, depending on the situation. If you use a session cookie, you must include an additional header that indicates the origin of the request.

The following examples show successful authentications.

$ curl
 --dump-header /dev/stdout
 --user openidm-admin:openidm-admin
 "http://localhost:8080/openidm/managed/user/?_queryId=query-all-ids"

HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=2l0zobpuk6st1b2m7gvhg5zas;Path=/
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: application/json; charset=UTF-8
Date: Wed, 18 Jan 2012 10:36:19 GMT
Accept-Ranges: bytes
Server: Restlet-Framework/2.0.9
Transfer-Encoding: chunked

{"query-time-ms":1,"result":[{"_id":"ajensen"},{"_id":"bjensen"}]}

$ curl
 --dump-header /dev/stdout
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 "http://localhost:8080/openidm/managed/user/?_queryId=query-all-ids"

HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=ixnekr105coj11ji67xcluux8;Path=/
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: application/json; charset=UTF-8
Date: Wed, 18 Jan 2012 10:36:40 GMT
Accept-Ranges: bytes
Server: Restlet-Framework/2.0.9
Transfer-Encoding: chunked

{"query-time-ms":0,"result":[{"_id":"ajensen"},{"_id":"bjensen"}]}

$ curl
 --dump-header /dev/stdout
 --header "Cookie: JSESSIONID=ixnekr105coj11ji67xcluux8"
 --header "X-Requested-With: OpenIDM Plugin"
 "http://localhost:8080/openidm/managed/user/?_queryId=query-all-ids"

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Wed, 18 Jan 2012 10:37:20 GMT
Accept-Ranges: bytes
Server: Restlet-Framework/2.0.9
Transfer-Encoding: chunked

{"query-time-ms":1,"result":[{"_id":"ajensen"},{"_id":"bjensen"}]}

Notice that the last example uses the cookie OpenIDM set in the response to the previous request, and includes the X-Requested-With header to indicate the origin of the request. The value of the header can be any string, but should be informative for logging purposes. If you do not include the X-Requested-With header, OpenIDM returns HTTP 403 Forbidden.

You can also request one-time authentication without a session.

$ curl
 --dump-header /dev/stdout
 --header "X-OpenIDM-NoSession: true"
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 "http://localhost:8080/openidm/managed/user/?_queryId=query-all-ids"

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Wed, 18 Jan 2012 10:52:27 GMT
Accept-Ranges: bytes
Server: Restlet-Framework/2.0.9
Transfer-Encoding: chunked

{"query-time-ms":1,"result":[{"_id":"ajensen"},{"_id":"bjensen"}]}

To log out and destroy the session, send the specific OpenIDM header.

$ curl
 --dump-header /dev/stdout
 --header "Cookie: JSESSIONID=ixnekr105coj11ji67xcluux8"
 --header "X-Requested-With: OpenIDM Plugin"
 --header "X-OpenIDM-Logout: true"
 "http://localhost:8080/openidm/"

HTTP/1.1 204 No Content

OpenIDM creates the openidm-admin user with password openidm-admin by default. This internal user is stored in OpenIDM's repository.

mysql> select objectid,roles from internaluser;
+---------------+----------------------------------+
| objectid      | roles                            |
+---------------+----------------------------------+
| anonymous     | openidm-reg                      |
| openidm-admin | openidm-admin,openidm-authorized |
+---------------+----------------------------------+
2 rows in set (0.00 sec)

OpenIDM uses the internal table for authentication, and also to set the roles for RBAC authorization of an authenticated user. The router service, described in the Router Service Reference appendix, enables you to apply filters as shown in openidm/conf/router.json and the associated script, openidm/script/router-authz.js. See the chapter on Managing Authentication, Authorization & RBAC for details.

14.5. Replace Default Security Settings

The default security settings are adequate for evaluation purposes. For production, change the default encryption key, and then replace the default user password.

Procedure 14.1. To Change Default Encryption Keys

By default, OpenIDM uses an symmetric encryption key with alias openidm-sym-default. Change this default key before deploying OpenIDM in production.

  1. Add the new key to the key store.

    $ cd /path/to/openidm/
    $ keytool
     -genseckey
     -alias new-sym-key
     -keyalg AES
     -keysize 128
     -keystore security/keystore.jceks
     -storetype JCEKS
    Enter keystore password:
    Enter key password for <new-sym-key>
      (RETURN if same as keystore password):
    Re-enter new password:
    $ 

    Also see openidm/samples/security/keystore_readme.txt.

  2. Change the alias used in openidm/conf/boot/boot.properties.

Procedure 14.2. To Replace the Default User & Password

After changing the default encryption key, change at least the default user password.

  1. Use the encrypt command to obtain the encrypted version of the new password.

    $ cd /path/to/openidm/
    $ cli.sh encrypt newpwd
    ...
    -----BEGIN ENCRYPTED VALUE-----
    {
      "$crypto" : {
        "value" : {
          "iv" : "TCoC/YrmiRmINw6jCPB5LQ==",
          "data" : "nCFvBIApIQ7C6k+UPzosaA==",
          "cipher" : "AES/CBC/PKCS5Padding",
          "key" : "openidm-sym-default"
        },
        "type" : "x-simple-encryption"
      }
    }
    ------END ENCRYPTED VALUE------
  2. Replace the user object in the openidm/db/scripts/mysql/openidm.sql script before setting up MySQL as a repository for OpenIDM.

    Alternatively, replace the user in the internal user table.

14.6. Secure Jetty

Before running OpenIDM in production, edit the openidm/conf/jetty.xml configuration to avoid clear text HTTP. Opt instead for HTTPS, either with or without mutual authentication. To disable plain HTTP access, comment out the section in openidm/conf/jetty.xml that enables HTTP on port 8080.

<!--
<Item>
    <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
        <Set name="host"><Property name="jetty.host" /></Set>
        <Set name="port">8080</Set>
        <Set name="maxIdleTime">300000</Set>
        <Set name="Acceptors">2</Set>
        <Set name="statsOn">false</Set>
        <Set name="confidentialPort">8443</Set>
        <Set name="lowResourcesConnections">20000</Set>
        <Set name="lowResourcesMaxIdleTime">5000</Set>
    </New>
</Item>
-->

14.7. Protect Sensitive REST Interface URLs

Although the repository is accessible directly by default, since anything attached to the router is accessible with the default policy, avoid direct HTTP access in production. If you do not need such access, deny it in the authorization policy to reduce the attack surface.

Similarly deny direct HTTP access to system objects in production, particularly access to action. As a rule of thumb, do not expose anything that is not used in production. The main public interfaces over HTTP are /openidm/managed/ and /openidm/config/. Other URIs are triggered indirectly, or are for internal consumption.

OpenIDM supports native query expressions on the JDBC repository and it is possible to enable these over HTTP, for example:

$curl 
 --header "X-OpenIDM-Username: openidm-admin"
 --header "X-OpenIDM-Password: openidm-admin"
 "http://localhost:8080/openidm/managed/user?_queryExpression=select+*+from+managedobjects"

By default, direct HTTP access to native queries is disallowed, and should remain so in production systems. To enable native queries on the JDBC repository over HTTP, specifically for testing or development purposes, remove the custom authorization call from the router authorization script (openidm/script/router-authz.js).

"customAuthz" : "disallowQueryExpression()"

Remember to remove the comma at the end of the preceding line as well.

See the chapter on Managing Authentication, Authorization & RBAC for an example showing how to protect sensitive URLs.

14.8. Protect Sensitive Files & Directories

Protect OpenIDM files from access by unauthorized users.

In particular, prevent other users from reading files in at least the openidm/conf/boot/ and openidm/security/ directories.

14.9. Obfuscate Bootstrap Information

OpenIDM uses the information in conf/boot/boot.properties, including the key store password, to start up. You can set an obfuscated version in the file, or prompt for the password at start up time.

To use an obfuscated password, follow these steps:

  1. Generate an obfuscated version of the password, by using the crypto bundle provided with OpenIDM:

    $ java -jar /path/to/openidm/bundle/openidm-crypto-2.1.0-SNAPSHOT.jar
    This utility helps obfuscate passwords to prevent casual observation.
    It is not securely encrypted and needs further measures to prevent disclosure.
    Please enter the password:
    OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0
    CRYPT:a8b5a01ba48a306f300b62a1541734c7
  2. Paste the obfuscated password into the conf/boot/boot.properties file. Comment out the regular keystore password and remove the comment tag from the line that contains the obfuscated password:

    $ more conf/boot/boot.properties
    ...
       # Keystore password, adjust to match your keystore and protect this file
       # openidm.keystore.password=changeit
       openidm.truststore.password=changeit
    
       # optionally use the cli encrypt to obfuscate the password and set
         openidm.keystore.password=OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0
       #openidm.keystore.password=CRYPT:
    ...
  3. Restart OpenIDM.

    $ ./startup.sh

14.10. Remove or Protect Development & Debug Tools

Before deploying OpenIDM in production, remove or protect development and debug tools, including the OSGi console exposed under /system/console. Authentication for this console is not integrated with authentication for OpenIDM.

To remove the OSGi console, remove the web console bundle, org.apache.felix.webconsole-version.jar.

If you cannot remove the OSGi console, then protect it by overriding the default admin:admin credentials. Create a file called openidm/conf/org.apache.felix.webconsole.internal.servlet.OsgiManager.cfg containing the user name and password to access the console in Java properties file format.

username=user-name
password=password

14.11. Protect the OpenIDM Repository

Use the JDBC repository. OrientDB is not yet supported for production use.

Use a strong password for the JDBC connection. Do not rely on default passwords.

Use a case sensitive database, particularly if you work with systems with different identifiers that match except for case. Otherwise correlation queries can pick up identifiers that should not be considered the same.

14.12. Adjust Log Levels

Leave log levels at INFO in production to ensure that you capture enough information to help diagnose issues. See the chapter on Configuring Server Logs for more information.

At start up and shut down, INFO can produce many messages. Yet, during stable operation, INFO generally results in log messages only when coarse-grain operations such as scheduled reconciliation start or stop.

14.13. Set Up Restart At System Boot

You can run OpenIDM in the background as a service (daemon), and add startup and shutdown scripts to manage the service at system boot and shutdown. For more information, see Starting and Stopping OpenIDM.

See your operating system documentation for details on adding a service such as OpenIDM to be started at boot and shut down at system shutdown.