I’ve been building a data grid at an investment bank using Apache Ignite and have had a tough time securing it. The documentation for Ignite is really very good but doesn’t touch on how to secure a cluster at all.
In this blog I’ll give an overview of how to secure a cluster in the hope that it will prove useful to anyone facing a similar task. I can’t give complete sources for a solution of course, but I will do my best to illustrate what needs to be done and where you can look in the existing Ignite code for some clues.
Ignite is a new open source project managed by Apache. It is a mature code base and is derived from GridGain’s data grid product and so far I’m very impressed with it. The initial version of this software (currently 1.0.0-RC1) ships with no security so you have to provide your own. Another option is to purchase a license for GridGain 7.0 (due to be released in a couple of weeks time) which will offer a security component on top of Ignite.
My security requirements are fairly simple, and I just want to be able to have a white list of I.P. addresses that are allowed to join the cluster. There are hooks for full authentication but I’ll not be discussing them in this blog.
Plugins
Ignite has a nice modular architecture that allows different modules to be configured. The default security module, which simply grants all permissions to everyone, is implemented in the GridOsSecurityProcessor
class. More on how this works later, the aim of this section is to outline how you go about replacing it with one of your own.
The first step is to inject a plugin to your IgniteConfiguration. I’m using Spring to bootstrap Ignite, so I need to add this to my Spring context:
<bean id="ignite" class="org.apache.ignite.configuration.IgniteConfiguration" p:gridName="mygrid"> <property name="pluginConfigurations"> <bean class="uk.co.smartkey.ignite.WhiteListPluginConfiguration"/> </property> </bean>
The implementation of this configuration class is trivial, it just needs to create a plugin-provider:
public class WhiteListPluginConfiguration implements PluginConfiguration { @Override public Class<? extends PluginProvider> providerClass() { return WhiteListPluginProvider.class; } }
The plugin-provider will be interrogated by the IgniteKernal at startup and asked to create plugins which support different interfaces. We’re interested in the security plugins, so we’re looking out for the request to create a GridSecurityProcessor
implementation:
public class WhiteListPluginProvider implements PluginProvider<WhiteListPluginConfiguration> { @Override public String name() { return "WhiteListSecurity"; } @Override public String version() { return "1.0.0"; } @Nullable @Override public Object createComponent(PluginContext ctx, Class cls) { if (cls.isAssignableFrom(GridSecurityProcessor.class)) { return new WhiteListSecurityProcessor(); } else { return null; } } @Override public IgnitePlugin plugin() { return new WhiteListAuthenticator(); } //all other methods are no-op }
See how we create our plugin class in the createComponent
method? We must also return something form the plugin
method, but from what I could tell this doesn’t seem to be used by Ignite (yet?).
All other methods on this class, and there are a lot of them, should just be no-op implementations.
WhiteListSecurityProcessor
So, we’ve configured Ignite to create and install our security component. All that’s left to do now is implement a few methods that will provide the authentication and authorisation for our cluster. In my case, I’m only really focusing on authentication, as once recognized all privileges will be granted.
Here are the important bits of code:
public class WhiteListSecurityProcessor implements DiscoverySpiNodeAuthenticator, GridSecurityProcessor, IgnitePlugin { //the hosts that will be allowed to join the cluster private Set<String> whitelist = new HashSet<>(); private boolean isAddressOk(Collection<String> addresses) { //return true if the address is in the whitelist } @Override public GridSecurityContext authenticateNode(ClusterNode node, GridSecurityCredentials cred) throws IgniteException { return new GridSecurityContext(new GridSecuritySubject() { @Override public GridSecurityPermissionSet permissions() { if (isAddressOk(node.addresses())) { return WhiteListPermissionSets.ALLOW_ALL; } else { return WhiteListPermissionSets.ALLOW_NONE; } } //all other methods are noop }); } @Override public boolean isGlobalNodeAuthentication() { //allow any node to perform the authentication return true; } @Override public void start() throws IgniteCheckedException { //load the whitelist //check that this process is running on a white listed server //if there's a problem throw new IgniteCheckedException } @Nullable @Override public IgniteSpiNodeValidationResult validateNode(ClusterNode node) { if (!isAddressOk(node.addresses())) { return new IgniteSpiNodeValidationResult(node.id(), "Access denied", "Access denied"); } else { return null; } } //all other methods are noop }
I’ve only included pseudo code for the mechanics of what this class does, but you should be able to get a good idea from this of what you might need to do for your own requirements.
The start
method is called when Ignite bootstraps, so it’s a good place for me to load my white list of I.P. addresses. I also check here that this process is running on a white listed server. If there are any problems throwing an IgniteCheckedException
will cause the process to shut down with an error message.
When a new cluster member starts and attempts to connect, the authenticateNode
and validateNode
methods will be called (in that order). The call to authenticateNode
needs to return a security context which identifies the permissions granted to the process. To be safe, I return an ALLOW_NONE
policy if the I.P. address is not on the white list. The call to validateNode
is then made; here I get the I.P. addresses for the connecting node and identify whether it can join the cluster. Contrary to the JavaDocs, this method should return a response only if there’s a problem.
For examples of how to create policy lists, take a look at Ignite’s GridOsSecurityProcessor
class.
Again, there are a lot of no-op methods that needed implementing. From what I could tell these were of no relevance to what I was trying to do.
Comments
The security plugin I’ve outlined above seems to work very well. If the cluster node that has been processing authentication crashes, a new one is elected and resumes service.
I’m impressed with Ignite. Considering it is only a release candidate at the moment, it seems to do everything I expect it to do without too much pain.
Comments 4
I’m trying to follow the example above but I’m not clear on a few steps.
You mention you are using Ignite above but I can not find the class GridSecurityContext . I only find the class SecurityContext.
Also when I put in the plug-in configuration in the ..cache.xml config file for GridGain to start, It does not pick up the plugin at all.
All I get is:
Configured plugins:
[14:45:39] ^– None
I have the line as above in the xml :
….
Does the plugin config only work in the Enterprise Edition or it works in the opensource Ignite version too ?
Thanks.
Author
Hi – this works with the open source version of Ignite. Make sure you’re using the same version mentioned in the blog and you should be able to find the classes.
What about WhiteListPermissionSets.ALLOW_ALL ?
Do I need a class which needs to implement SecurityPermissionSet ?
I’m using Open Source Ignite 1.5.0-
Thanks.
Hi Steve,
Thanks for your blog – very useful!
Just wondering why you didn’t try securing your environment using SSL/TLS? (Is SSL a recent addition, and just wasn’t available at the time? – https://apacheignite.readme.io/docs/ssltls)
AFAIU, it keeps unwanted nodes off the “network” and secures all network traffic – probably what you’d want for an investment bank, right?
Kind regards,
Nick