20.7. Securing Interfaces

While the Hot Rod interface may be secured programmatically, both the memcached and REST interfaces must be secured declaratively. Instructions for securing these interfaces are located in the JBoss Data Grid Administration and Configuration Guide.

20.7.1. Hot Rod Interface Security

20.7.1.1. Encryption of communication between Hot Rod Server and Hot Rod client

Hot Rod can be encrypted using TLS/SSL, and has the option to require certificate-based client authentication.
Use the following procedure to secure the Hot Rod connector using SSL.

Example 20.4. Secure Hot Rod Using SSL/TLS

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.impl.ConfigurationProperties;

[...]

public class SslConfiguration {

    public static final String ISPN_IP = "127.0.0.1";
    public static final String SERVER_NAME = "node0";
    public static final String SASL_MECH = "EXTERNAL";

    private static final String KEYSTORE_PATH = "./keystore_client.jks";
    private static final String KEYSTORE_PASSWORD = "secret";
    private static final String TRUSTSTORE_PATH = "./truststore_client.jks";
    private static final String TRUSTSTORE_PASSWORD = "secret";

    SslConfiguration(boolean enabled, 
                     String keyStoreFileName, 
                     char[] keyStorePassword, 
                     SSLContext sslContext, 
                     String trustStoreFileName, 
                     char[] trustStorePassword) {
        ConfigurationBuilder builder = new ConfigurationBuilder();
        builder.addServer()
            .host(ISPN_IP)
            .port(ConfigurationProperties.DEFAULT_HOTROD_PORT);
        //setup auth
        builder.security()
            .authentication()
            .serverName(SERVER_NAME)
            .saslMechanism(SASL_MECH)
            .enable()
            .callbackHandler(new VoidCallbackHandler());
        //setup encrypt
        builder.security()
            .ssl()
            .enable()
            .keyStoreFileName(KEYSTORE_PATH)
            .keyStorePassword(KEYSTORE_PASSWORD.toCharArray())
            .trustStoreFileName(TRUSTSTORE_PATH)
            .trustStorePassword(TRUSTSTORE_PASSWORD.toCharArray());
        
        RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
        RemoteCache<Object, Object> cache = cacheManager.getCache(RemoteCacheManager.DEFAULT_CACHE_NAME);
    }

    private static class VoidCallbackHandler implements CallbackHandler {
        @Override
        public void handle(Callback[] clbcks) throws IOException, UnsupportedCallbackException {
        }
    }
}

Important

To prevent plain text passwords from appearing in configurations or source codes, plain text passwords should be changed to Vault passwords. For more information about how to set up Vault passwords, see the Red Hat Enterprise Application Platform Security Guide.

20.7.1.2. Securing Hot Rod to LDAP Server using SSL

When connecting to an LDAP server with SSL enabled it may be necessary to specify a trust store or key store containing the appropriate certificates.
PLAIN authentication over SSL may be used for Hot Rod client authentication against an LDAP server. The Hot Rod client sends plain text credentials to the JBoss Data Grid server over SSL, and the server subsequently verifies the provided credentials against the specified LDAP server. In addition, a secure connection must be configured between the JBoss Data Grid server and the LDAP server. Refer to the JBoss Data Grid Administration and Configuration Guide for additional information on configuring the server to communicate to an LDAP backend. The example below demonstrates configuring PLAIN authentication over SSL on the Hot Rod client side:

Example 20.5. Hot Rod Client Authentication to LDAP Server

import static org.infinispan.demo.util.CacheOps.dumpCache;
import static org.infinispan.demo.util.CacheOps.onCache;
import static org.infinispan.demo.util.CacheOps.putTestKV;
import static org.infinispan.demo.util.CmdArgs.LOGIN_KEY;
import static org.infinispan.demo.util.CmdArgs.PASS_KEY;
import static org.infinispan.demo.util.CmdArgs.getCredentials;

import java.util.Map;

import javax.net.ssl.SSLContext;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.impl.ConfigurationProperties;
import org.infinispan.commons.util.SslContextFactory;
import org.infinispan.demo.util.SaslUtils.SimpleLoginHandler;

public class HotRodPlainAuthOverSSL {
    
    public static final String ISPN_IP = "127.0.0.1";
    public static final String SERVER_NAME = "node0";
    public static final String SASL_MECH = "PLAIN";
    private static final String SECURITY_REALM = "ApplicationRealm";

    private static final String TRUSTSTORE_PATH = "./truststore_client.jks";
    private static final String TRUSTSTORE_PASSWORD = "secret";

    public static void main(String[] args) {
        Map<String, String> userArgs = null;
        try {
            userArgs = getCredentials(args);
        } catch (IllegalArgumentException e) {
            System.err.println(e.getMessage());
            System.err.println(
                    "Invalid credentials format, plase provide credentials (and optionally cache name) with --cache=<cache> --user=<user> --password=<password>");
            System.exit(1);
        }
        
        ConfigurationBuilder builder = new ConfigurationBuilder();
        builder.addServer().host(ISPN_IP).port(ConfigurationProperties.DEFAULT_HOTROD_PORT);

        //set up PLAIN auth
        builder.security().authentication().serverName(SERVER_NAME).saslMechanism(SASL_MECH).enable().callbackHandler(
                new SimpleLoginHandler(userArgs.get(LOGIN_KEY), userArgs.get(PASS_KEY), SECURITY_REALM));
        
        //set up SSL
        SSLContext cont = SslContextFactory.getContext(null, null, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD.toCharArray());
        builder.security().ssl().sslContext(cont).enable();
        
        RemoteCacheManager cacheManager = new RemoteCacheManager(builder.build());
        RemoteCache<Object, Object> cache = cacheManager.getCache(RemoteCacheManager.DEFAULT_CACHE_NAME);

        onCache(cache, putTestKV.andThen(dumpCache));

        cacheManager.stop();
        System.exit(0);
    }

}

Important

To prevent plain text passwords from appearing in configurations or source codes, plain text passwords should be changed to Vault passwords. For more information about how to set up Vault passwords, see the Red Hat Enterprise Application Platform Security Guide.

20.7.1.3. User Authentication over Hot Rod Using SASL

User authentication over Hot Rod can be implemented using the following Simple Authentication and Security Layer (SASL) mechanisms:
  • PLAIN is the least secure mechanism because credentials are transported in plain text format. However, it is also the simplest mechanism to implement. This mechanism can be used in conjunction with encryption (SSL) for additional security.
  • DIGEST-MD5 is a mechanism than hashes the credentials before transporting them. As a result, it is more secure than the PLAIN mechanism.
  • GSSAPI is a mechanism that uses Kerberos tickets. As a result, it requires a correctly configured Kerberos Domain Controller (for example, Microsoft Active Directory).
  • EXTERNAL is a mechanism that obtains the required credentials from the underlying transport (for example, from a X.509 client certificate) and therefore requires client certificate encryption to work correctly.
20.7.1.3.1. Configure Hot Rod Authentication (GSSAPI/Kerberos)
Use the following steps to set up Hot Rod Authentication using the SASL GSSAPI/Kerberos mechanism:

Procedure 20.1. Configure SASL GSSAPI/Kerberos Authentication - Client-side Configuration

  1. Ensure that the Server-Side configuration has been completed. As this is configured declaratively this configuration is found in the JBoss Data Grid Administration and Configuration Guide.
  2. Define a login module in a login configuration file (gss.conf) on the client side:
    GssExample {
        com.sun.security.auth.module.Krb5LoginModule required client=TRUE;
    };
  3. Set up the following system properties:
    java.security.auth.login.config=gss.conf
    java.security.krb5.conf=/etc/krb5.conf

    Note

    The krb5.conf file is dependent on the environment and must point to the Kerberos Key Distribution Center.
  4. Implement the CallbackHandler:
    public class MyCallbackHandler implements CallbackHandler {
        final private String username;
        final private char[] password;
        final private String realm;
    
        public MyCallbackHandler() { }
    
        public MyCallbackHandler (String username, String realm, char[] password) {
            this.username = username;
            this.password = password;
            this.realm = realm;
        }
    
        @Override
        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof NameCallback) {
                    NameCallback nameCallback = (NameCallback) callback;
                    nameCallback.setName(username);
                } else if (callback instanceof PasswordCallback) {
                    PasswordCallback passwordCallback = (PasswordCallback) callback;
                    passwordCallback.setPassword(password);
                } else if (callback instanceof AuthorizeCallback) {
                    AuthorizeCallback authorizeCallback = (AuthorizeCallback) callback;
                    authorizeCallback.setAuthorized(authorizeCallback.getAuthenticationID().equals(
                        authorizeCallback.getAuthorizationID()));
                } else if (callback instanceof RealmCallback) {
                    RealmCallback realmCallback = (RealmCallback) callback;
                    realmCallback.setText(realm);
                } else {
                    throw new UnsupportedCallbackException(callback);
                }
            }
        }
    }
  5. Configure the Hot Rod Client, as seen in the below snippet:
    LoginContext lc = new LoginContext("GssExample", new MyCallbackHandler("krb_user", "krb_password".toCharArra()));
    lc.login();
    Subject clientSubject = lc.getSubject();
    
    ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
    clientBuilder.addServer()
            .host("127.0.0.1")
            .port(11222)
        .socketTimeout(1200000)
        .security()
            .authentication()
                .enable()
                .serverName("infinispan-server")
                .saslMechanism("GSSAPI")
                .clientSubject(clientSubject)
                .callbackHandler(new MyCallbackHandler());
    remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
    RemoteCache<String, String> cache = remoteCacheManager.getCache("secured");
20.7.1.3.2. Configure Hot Rod Authentication (MD5)
Use the following steps to set up Hot Rod Authentication using the SASL using the MD5 mechanism:
  1. Ensure that the server has been configured for MD5 Authentication. Instructions for performing this configuration on the server are found in JBoss Data Grid's Administration and Configuration Guide.
  2. Implement the CallbackHandler:
    public class MyCallbackHandler implements CallbackHandler {
       final private String username;
       final private char[] password;
       final private String realm;
    
       public MyCallbackHandler (String username, String realm, char[] password) {
          this.username = username;
          this.password = password;
          this.realm = realm;
       }
    
       @Override
       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
          for (Callback callback : callbacks) {
             if (callback instanceof NameCallback) {
                NameCallback nameCallback = (NameCallback) callback;
                nameCallback.setName(username);
             } else if (callback instanceof PasswordCallback) {
                PasswordCallback passwordCallback = (PasswordCallback) callback;
                passwordCallback.setPassword(password);
             } else if (callback instanceof AuthorizeCallback) {
                AuthorizeCallback authorizeCallback = (AuthorizeCallback) callback;
                authorizeCallback.setAuthorized(authorizeCallback.getAuthenticationID().equals(
                      authorizeCallback.getAuthorizationID()));
             } else if (callback instanceof RealmCallback) {
                RealmCallback realmCallback = (RealmCallback) callback;
                realmCallback.setText(realm);
             } else {
                throw new UnsupportedCallbackException(callback);
             }
          }
       }
    }
  3. Connect the client to the configured Hot Rod connector as seen below:
    ConfigurationBuilder clientBuilder = new ConfigurationBuilder();
    clientBuilder.addServer()
            .host("127.0.0.1")
            .port(11222)
        .socketTimeout(1200000)
        .security()
            .authentication()
                .enable()
                .serverName("myhotrodserver")
                .saslMechanism("DIGEST-MD5")
                .callbackHandler(new MyCallbackHandler("myuser", "ApplicationRealm", "qwer1234!".toCharArray()));
    remoteCacheManager = new RemoteCacheManager(clientBuilder.build());
    RemoteCache<String, String> cache = remoteCacheManager.getCache("secured");
    

20.7.2. Hot Rod C++ Client Encryption

By default all communication with the remote server is unencrypted; however, TLS encryption may be enabled by defining the server's key via the serverCAFile method on the SslConfigurationBuilder. Additionally, the client's certificate may be defined with the clientCertificateFile, allowing for client authentication.

Important

TLS Encryption is a Technology Preview feature of the Hot Rod C++ Client in JBoss Data Grid 7.0.0.
The following example demonstrates defining a server key with an optional client certificate:

Example 20.6. Hot Rod C++ TLS Example

#include "infinispan/hotrod/ConfigurationBuilder.h"
#include "infinispan/hotrod/RemoteCacheManager.h"
#include "infinispan/hotrod/RemoteCache.h"
#include "infinispan/hotrod/Version.h"

#include "infinispan/hotrod/JBasicMarshaller.h"
#include <stdlib.h>
#include <iostream>
#include <memory>
#include <typeinfo>

using namespace infinispan::hotrod;

int main(int argc, char** argv) {
    std::cout << "TLS Test" << std::endl;
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " server_ca_file [client_ca_file]" << std::endl;
        return 1;
    }
    {
      ConfigurationBuilder builder;
      builder.addServer().host("127.0.0.1").port(11222).protocolVersion(Configuration::PROTOCOL_VERSION_24);
      // Enable the TLS layer and install the server public key
      // this ensure that the server is authenticated
      builder.ssl().enable().serverCAFile(argv[1]);
      if (argc > 2) {
          // Send a client certificate for authentication (optional)
          // without this the socket will only be encrypted
          std::cout << "Using supplied client certificate for authentication against the server" << std::endl;
          builder.ssl().clientCertificateFile(argv[2]);
      }
      // That's all. Now do business as usual
      RemoteCacheManager cacheManager(builder.build(), false);
      BasicMarshaller<std::string> *km = new BasicMarshaller<std::string>();
      BasicMarshaller<std::string> *vm = new BasicMarshaller<std::string>();
      RemoteCache<std::string, std::string> cache = cacheManager.getCache<std::string, std::string>(km,
          &Marshaller<std::string>::destroy, vm, &Marshaller<std::string>::destroy );
      cacheManager.start();
      cache.clear();
      std::string k1("key13");
      std::string v1("boron");

      cache.put(k1, v1);
      std::unique_ptr<std::string> rv(cache.get(k1));
      if (rv->compare(v1)) {
          std::cerr << "get/put fail for " << k1 << " got " << *rv << " expected " << v1 << std::endl;
          return 1;
      }
      cacheManager.stop();
    }
    return 0;
}

20.7.3. Hot Rod C# Client Encryption

By default all communication with the remote server is unencrypted; however, TLS encryption may be enabled by defining the server's key via the ServerCAFile method on the SslConfigurationBuilder. Additionally, the client's certificate may be defined with the ClientCertificateFile, allowing for client authentication.

Important

TLS Encryption is a Technology Preview feature of the Hot Rod C# Client in JBoss Data Grid 7.0.0.
The following example demonstrates defining a server key with an optional client certificate:

Example 20.7. Hot Rod C# TLS Example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Infinispan.HotRod;
using Infinispan.HotRod.Config;

namespace TLS
{
    /// <summary>
    /// This sample code shows how to perform operations over TLS using the C# client
    /// </summary>

    class TLS
    {
        static void Main(string[] args)
        { 
            // Cache manager setup
            RemoteCacheManager remoteManager;
            ConfigurationBuilder conf = new ConfigurationBuilder();
            conf.AddServer().Host("127.0.0.1").Port(11222).ConnectionTimeout(90000).SocketTimeout(900);
            SslConfigurationBuilder sslConfB = conf.Ssl();
            // Retrieve the server public certificate, needed to do server authentication. Mandatory
            if (!System.IO.File.Exists("resources/infinispan-ca.pem"))
            {
                Console.WriteLine("File not found: resources/infinispan-ca.pem.");
                Environment.Exit(-1);
            }
            sslConfB.Enable().ServerCAFile("resources/infinispan-ca.pem");
            // Retrieve the client public certificate, needed if the server requires client authentication. Optional
            if (!System.IO.File.Exists("resources/client-ca.pem"))
            {
                Console.WriteLine("File not found: resources/client-ca.pem.");
                Environment.Exit(-1);
            }
            sslConfB.ClientCertificateFile("resources/client-ca.pem");

            // Usual business now
            conf.Marshaller(new JBasicMarshaller());
            remoteManager = new RemoteCacheManager(conf.Build(), true);
            IRemoteCache<string, string> testCache = remoteManager.GetCache<string, string>();
            testCache.Clear();
            string k1 = "key13";
            string v1 = "boron";
            testCache.Put(k1, v1);
        }
    }
}