Managing Apache Ignite Secrets With HashiCorp Vault

Where do you store your passwords?

Whether you’re integrating Apache Ignite with a relational database, a message queue, or something else, you probably need to manage secrets such as usernames, passwords, and security tokens. In this post, we consider a couple of options to avoid having secrets in your configuration file: using property files and integrating with HashiCorp Vault.

Authentication in Apache Ignite and GridGain

Apache Ignite needs authentication information for several reasons. The two reasons that we talk about in this post are for logging into third-party storage and for securing access to the cluster itself.

By third-party storage, we mean where Ignite is used as a cache for a legacy database, such as MySQL or Oracle. The credentials are used to log into that external data store. This configuration works in all editions of GridGain and Apache Ignite.

We also talk about the enterprise-grade security that is found only in GridGain Enterprise Edition and Ultimate Edition. In this case, credentials are required to determine whether a new node is allowed to join the cluster.

Configuring Ignite Secrets via Properties Files

A simple way to avoid including secrets in your Ignite configuration file – a way that doesn’t require coding – is to use some special properties of the Spring configuration file that we often use when we start nodes. The configuration file magic is called “property-placeholder.” Property-placeholder enables you to put parameters in environment variables and property files. Let’s see how the process works with a properties file.

In your Ignite configuration file, above the IgniteConfiguration bean, you put this line:

<context:property-placeholder location="classpath:config.properties" ignore-resource-not-found="false"/>

I like to set ignore-resource-not-found so that, if I have a typo later on, the node won’t start. If you’re feeling brave or reckless, you can skip the “ignore” setting.

The config.properties file has a simple list of key/value pairs, separated by equal signs. Here’s an example:

payroll_password=super_secret_password

How is using a property placeholder better than putting the passwords in the GridGain configuration file? When you use property files, you can apply the normal Unix file permissions, so the credentials file is not readable by anyone other than the user who runs the server process.

You can also skip the properties file and put the required values in environment variables.

Back to the GridGain configuration file – when you want to use a parameter from the file, you say ${parameter}. In context, the configuration looks something like the following:

<bean id="mysql_database_bean" class="com.mysql.cj.jdbc.MysqlDataSource">
    <property name="url" value="jdbc:mysql://127.0.0.1/"/>
    <property name="databaseName" value="gridgain"/>
    <property name="user" value="root"/>
    <property name="password" value="${payroll_password}"/>
</bean>

The nice thing about using properties is that you can fill in the blanks for anything, not just for authentication information. Some clients use this pattern used in the sample above so that the same configuration file can be used for both their UAT and production systems. In this case, differences, such as hostnames and port numbers, are included only in properties and environment variables.

Using Vault for Password Management

Of course, storing passwords in property files has a downside. Most obviously, the credentials are still on-disk on every machine in your cluster. That’s both a security risk and an inconvenience. How do you distribute credential changes to all of your nodes? How do you manage access to the secrets?

Fortunately, there are tools that do for servers what tools such as 1Password and iCloud Keychain do for laptops. Most cloud vendors have their own enterprise versions: AWS Secret Manager, Azure Key Vault, and Oracle Cloud Infrastructure Vault. However, there are also vendor-agnostic applications that can be used both in the cloud and on-premises. In this post, we’re going to integrate with HashiCorp Vault, but the same approach would work for any of the services.

First, let’s modify our property placeholder example. Rather than using properties, let’s keep our credentials in Vault and write some glue so that GridGain knows where to look.

Let’s start with our password in Ignite’s configuration file. If you review the documentation, you see that we must define a “data source bean”:

<bean class="com.mysql.cj.jdbc.MysqlDataSource" id="mysqlDataSource">
    <property name="URL" value="jdbc:mysql://[host]:[port]/[database]"/>
    <property name="user" value="YOUR_USER_NAME"/>
    <property name="password" value="YOUR_PASSWORD"/>
</bean>

What we want is a bean that does exactly the same thing, but rather than having the credentials in plain text, we have a link to our Vault server. The MySQL data source doesn’t have the ability to talk directly to Vault, but we can build a “proxy” bean. Here’s the new configuration file:

<bean id="cachestore_connection" class="com.gridgain.ecosystem.vault.VaultDataSourceProxy">
    <property name="proxy">
        <bean id="mysql_database_bean" class="com.mysql.cj.jdbc.MysqlDataSource">
            <property name="url" value="jdbc:mysql://127.0.0.1/"/>
            <property name="databaseName" value="gridgain"/>
            <property name="user" value="root"/>
        </bean>
    </property>
    <property name="vaultAddress" value="http://127.0.0.1:8200"/>
    <property name="vaultToken" value="s.o5Ed0zZ3La8hoITIvCaT9Tbb"/>
    <property name="passwordPath" value="secret/mysql"/>
    <property name="passwordProperty" value="password"/>
</bean>

Our proxy must pretend to be a data source, so it inherits from DataSource. Most methods forward their parameters to our proxy object, defined by the property “proxy.”  The one interesting method is “getPassword”:

public String getPassword() {
  try {
    final VaultConfig vaultConfig = new VaultConfig()
      .address(this.vaultAddress)
      .token(this.vaultToken)
      .build();
    final Vault vault = new Vault(vaultConfig);
    final String password = vault.logical().read(this.passwordPath).getData().get("password");
    return password;
  }
  catch (VaultException e) {
    return null;
  }
}

This code is fairly straightforward: taking the values that we provided in the XML configuration file, we build a connection object for the Vault API, request the password, and return the password to the caller.

A full working copy of the proxy bean code can be found on GitHub.

Integrating Vault with the GridGain Security Framework

Next, let’s look at how we can use Vault to store the passwords used to authenticate GridGain nodes. This feature is available only in the GridGain Enterprise Edition and GridGain Ultimate Edition.

The documentation shows how to set up authentication between nodes by placing passwords directly in the configuration file. While we could use the same proxy-bean approach as we did for third-party storage, there is a public API for providing security credentials. The interface that we need to implement is “SecurityCredentialsProvider”.

As before, with a small amount of glue code, we can have GridGain request the password from Vault, so we do not have to store the password ourselves. The code is very similar to the getPassword() method in the proxy-bean example above, so I won’t repeat it here. The method is called “credentials,” and you can see the code on GitHub.

Plain (Text) Sailing

While Apache Ignite doesn’t have a built-in method for encrypting or obfuscating passwords, this blog has shown two different ways to avoid storing them as plain text in your configuration file. Property placeholders offer a simple, coding-free way to inject parameters into your configuration from the environment. We also saw how to integrate with third-party credential stores that expose a public API.