DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • What Is API-First?
  • Binary Code Verification in Open Source World
  • Spring Beans With Auto-Generated Implementations: How-To
  • Rapidly Develop Java Microservices on Kubernetes With Telepresence

Trending

  • Comparing SaaS vs. PaaS for Kafka and Flink Data Streaming
  • Subtitles: The Good, the Bad, and the Resource-Heavy
  • Mastering Advanced Traffic Management in Multi-Cloud Kubernetes: Scaling With Multiple Istio Ingress Gateways
  • How to Format Articles for DZone
  1. DZone
  2. Coding
  3. Languages
  4. How to Secure Apache Ignite From Scratch

How to Secure Apache Ignite From Scratch

Currently, Apache Ignite doesn't provide a security implementation out-of-the-box. So, I'm going to show you how to create an Apache Ignite security plugin from scratch.

By 
Denis Garus user avatar
Denis Garus
·
Updated Jan. 05, 21 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
7.3K Views

Join the DZone community and get the full member experience.

Join For Free

To use Apache Ignite securely, you need an implementation of GridSecurityProcessor, a security plugin. Currently, Apache Ignite doesn't provide this implementation out-of-the-box. So, I'm going to show you how to create an Apache Ignite security plugin from scratch.

To create and test our plugin, we will use Apache Ignite 2.9.1, which, at this time, is the most recent version of Ignite.

The plug-in that we're going to create will be able to do the following:

  • Authenticate an Apache Ignite node that is joining to a cluster.
  • Authorize Ignite operations.
  • Authenticate a thin client.
  • Run a user-defined code with restrictions.

The code that is used in this article is available on GitHub.

Implementation of Apache Ignite Security Interfaces

Let's start with the pom.xml file. All we need do is define the following dependencies:

XML
 




x
10


 
1
<dependency>
2
    <groupId>org.apache.ignite</groupId>
3
    <artifactId>ignite-core</artifactId>
4
    <version>2.9.1</version>
5
</dependency>
6
<dependency>
7
    <groupId>org.apache.ignite</groupId>
8
    <artifactId>ignite-spring</artifactId>
9
    <version>2.9.1</version>
10
</dependency>


Now, we are ready to create implementations of the security interfaces.

The first implementation is SecuritySubject.

Java
 




x


 
1
package security;
2

          
3
import java.net.InetSocketAddress;
4
import java.util.UUID;
5
import org.apache.ignite.plugin.security.SecurityPermissionSet;
6
import org.apache.ignite.plugin.security.SecuritySubject;
7
import org.apache.ignite.plugin.security.SecuritySubjectType;
8

          
9
public class SecuritySubjectImpl implements SecuritySubject {
10
    private static final long serialVersionUID = 0L;
11
  
12
    private UUID id;
13
    private SecuritySubjectType type;
14
    private Object login;
15
    private InetSocketAddress address;
16
    private SecurityPermissionSet permissions;
17

          
18
    public UUID id() {
19
        return id;
20
    }
21

          
22
    public SecuritySubjectImpl id(UUID id) {
23
        this.id = id;
24
        return this;
25
    }
26

          
27
    public SecuritySubjectType type() {
28
        return type;
29
    }
30

          
31
    public SecuritySubjectImpl type(SecuritySubjectType type) {
32
        this.type = type;
33
        return this;
34
    }
35

          
36
    public Object login() {
37
        return login;
38
    }
39

          
40
    public SecuritySubjectImpl login(Object login) {
41
        this.login = login;
42
        return this;
43
    }
44

          
45
    public InetSocketAddress address() {
46
        return address;
47
    }
48

          
49
    public SecuritySubjectImpl address(InetSocketAddress address) {
50
        this.address = address;
51
        return this;
52
    }
53

          
54
    public SecurityPermissionSet permissions() {
55
        return permissions;
56
    }
57

          
58
    public SecuritySubjectImpl permissions(SecurityPermissionSet permissions) {
59
        this.permissions = permissions;
60
        return this;
61
    }
62
  
63
    /** {@inheritDoc} */
64
    @Override public String toString() {
65
        return "TestSecuritySubject{" +
66
            "login=" + login +
67
            '}';
68
    }
69
}


The SecurityContext interface can be implemented in the following way:

Java
 




x


 
1
package security;
2

          
3
import java.io.Serializable;
4
import java.util.Collection;
5
import org.apache.ignite.internal.processors.security.SecurityContext;
6
import org.apache.ignite.internal.util.typedef.F;
7
import org.apache.ignite.plugin.security.SecurityPermission;
8
import org.apache.ignite.plugin.security.SecuritySubject;
9

          
10
public class SecurityContextImpl implements SecurityContext, Serializable {
11
    private static final long serialVersionUID = 0L;
12

          
13
    private final SecuritySubject subject;
14

          
15
    public SecurityContextImpl(SecuritySubject subject) {
16
        this.subject = subject;
17
    }
18

          
19
    public SecuritySubject subject() {
20
        return subject;
21
    }
22

          
23
    public boolean taskOperationAllowed(String taskClsName, SecurityPermission perm) {
24
        return hasPermission(subject.permissions().taskPermissions().get(taskClsName), perm);
25
    }
26

          
27
    public boolean cacheOperationAllowed(String cacheName, SecurityPermission perm) {
28
        return hasPermission(subject.permissions().cachePermissions().get(cacheName), perm);
29
    }
30

          
31
    public boolean serviceOperationAllowed(String srvcName, SecurityPermission perm) {
32
        return hasPermission(subject.permissions().servicePermissions().get(srvcName), perm);
33
    }
34

          
35
    public boolean systemOperationAllowed(SecurityPermission perm) {
36
        Collection<SecurityPermission> perms = subject.permissions().systemPermissions();
37

          
38
        if (F.isEmpty(perms))
39
            return subject.permissions().defaultAllowAll();
40

          
41
        return perms.stream().anyMatch(p -> perm == p);
42
    }
43

          
44
    public boolean operationAllowed(String opName, SecurityPermission perm) {
45
        switch (perm) {
46
            case CACHE_CREATE:
47
            case CACHE_DESTROY:
48
                return systemOperationAllowed(perm) || cacheOperationAllowed(opName, perm);
49

          
50
            case CACHE_PUT:
51
            case CACHE_READ:
52
            case CACHE_REMOVE:
53
                return cacheOperationAllowed(opName, perm);
54

          
55
            case TASK_CANCEL:
56
            case TASK_EXECUTE:
57
                return taskOperationAllowed(opName, perm);
58

          
59
            case SERVICE_DEPLOY:
60
            case SERVICE_INVOKE:
61
            case SERVICE_CANCEL:
62
                return serviceOperationAllowed(opName, perm);
63

          
64
            case EVENTS_DISABLE:
65
            case EVENTS_ENABLE:
66
            case ADMIN_VIEW:
67
            case ADMIN_CACHE:
68
            case ADMIN_QUERY:
69
            case ADMIN_OPS:
70
            case JOIN_AS_SERVER:
71
                return systemOperationAllowed(perm);
72
            // You should decide what is a proper reaction when unknown permission is getting
73
            default:
74
                throw new IllegalArgumentException("Unknown security permission: " + perm);
75
        }
76
    }
77

          
78
    private boolean hasPermission(Collection<SecurityPermission> perms, SecurityPermission perm) {
79
        if (perms == null)
80
            return subject.permissions().defaultAllowAll();
81

          
82
        return perms.stream().anyMatch(p -> perm == p);
83
    }
84
}


There are a few tricky points. The SecurityContext interface has to extend the Serializable interface; so, don't forget to add Serializable to the list of implementing interfaces.

Permissions are divided into four groups:

  • system operations: CACHE_CREATE, CACHE_DESTROY, EVENTS_DISABLE, EVENTS_ENABLE, ADMIN_VIEW, ADMIN_CACHE, ADMIN_QUERY, ADMIN_OPS, JOIN_AS_SERVER
  • service operations: SERVICE_DEPLOY, SERVICE_INVOKE, SERVICE_CANCEL
  • cache operations: CACHE_CREATE, CACHE_DESTROY, CACHE_PUT, CACHE_READ, CACHE_REMOVE
  • task operations: TASK_EXECUTE, TASK_CANCEL

To determine whether a security subject has been given permission, you have to know what the permission group is. The operationAllowed() method shows how you can identify the permission group.

Notice that CACHE_CREATE and CACHE_DESTROY are included in two groups (system operations and cache operations). When CACHE_CREATE (CACHE_DESTROY) is treated as a system permission, it applies to all caches. In other cases, when CACHE_CREATE (CACHE_DESTROY) is treated as cache permission, permission checking is executed with the account of the cache name.

In the future, the SecurityPermission enum can be extended by new constants. So, when you get unknown permissions, you need to decide on an appropriate reaction.

The GridSecurityProcessor is the central interface for Ignite security. We will improve our implementation of GridSecurityProcessor step-by-step by adding the capabilities that are described in the introduction to this article.

Java
 




x



1
package security;
2

          
3
import java.net.InetSocketAddress;
4
import java.util.Collection;
5
import java.util.UUID;
6
import org.apache.ignite.IgniteCheckedException;
7
import org.apache.ignite.cluster.ClusterNode;
8
import org.apache.ignite.internal.GridKernalContext;
9
import org.apache.ignite.internal.IgniteNodeAttributes;
10
import org.apache.ignite.internal.processors.GridProcessorAdapter;
11
import org.apache.ignite.internal.processors.security.GridSecurityProcessor;
12
import org.apache.ignite.internal.processors.security.SecurityContext;
13
import org.apache.ignite.internal.util.typedef.F;
14
import org.apache.ignite.plugin.security.AuthenticationContext;
15
import org.apache.ignite.plugin.security.SecurityCredentials;
16
import org.apache.ignite.plugin.security.SecurityException;
17
import org.apache.ignite.plugin.security.SecurityPermission;
18
import org.apache.ignite.plugin.security.SecurityPermissionSetBuilder;
19
import org.apache.ignite.plugin.security.SecuritySubject;
20
import org.apache.ignite.plugin.security.SecuritySubjectType;
21

          
22
public class GridSecurityProcessorImpl extends GridProcessorAdapter implements GridSecurityProcessor {
23

          
24
   private final SecurityCredentials localNodeCredentials;
25

          
26
    public GridSecurityProcessorImpl(GridKernalContext ctx, SecurityCredentials cred) {
27
        super(ctx);
28
        localNodeCredentials = cred;
29
    }
30
  
31
   public void start() throws IgniteCheckedException {
32
        U.quiet(false, "[GridSecurityProcessorImpl] Start; localNode=" + ctx.localNodeId()
33
            + ", login=" + localNodeCredentials.getLogin());
34

          
35
        ctx.addNodeAttribute(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, localNodeCredentials);
36
     
37
        super.start();
38
    }
39

          
40
    public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials credentials) {
41
      // This is the place to check the credentials of the joining node.
42
      
43
        SecuritySubject subject = new SecuritySubjectImpl()
44
            .id(node.id())
45
            .login(credentials.getLogin())
46
            .address(new InetSocketAddress(F.first(node.addresses()), 0))
47
            .type(SecuritySubjectType.REMOTE_NODE)
48
            .permissions(
49
                SecurityPermissionSetBuilder
50
                    .create()
51
                    .appendSystemPermissions(SecurityPermission.JOIN_AS_SERVER)
52
                    .build()
53
            );
54

          
55
        U.quiet(false, "[GridSecurityProcessorImpl] Authenticate node; " +
56
            "localNode=" + ctx.localNodeId() +
57
            ", authenticatedNode=" + node.id() +
58
            ", login=" + credentials.getLogin());
59
      
60
        return new SecurityContextImpl(subject);
61
    }
62

          
63
    public boolean enabled() {
64
        return true;
65
    }
66
  
67
    public boolean isGlobalNodeAuthentication() {
68
        return false;
69
    }
70
  
71
  // other no-op methods of GridSecurityProcessor
72
}


You can use the GridProcessorAdapter to create your processor. This abstract class enables you to override only the methods that you need to override and to hide boilerplate code. In our case, we want to override the start() method and, thus, add the credentials to the node's attributes. One or more cluster nodes will use the credentials to authenticate the node that is joined to the cluster in the authenticateNode() method. Before you create a SecuritySubject instance, make sure that the credentials are valid.

The code that we wrote for GridSecurityProcessor enables nodes to join a cluster.

Let's try it!

Start Ignite Nodes With a Security Plugin

To start Ignite nodes with our GridSecurityProcessor, we must define the processor as an Ignite plugin. You can read about Ignite plugins in the Plugins document, but when you create an Ignite processor plugin, you need to be aware of a few idiosyncrasies.

Java
 




x


 
1
package security;
2

          
3
import java.io.Serializable;
4
import java.util.UUID;
5
import org.apache.ignite.IgniteCheckedException;
6
import org.apache.ignite.cluster.ClusterNode;
7
import org.apache.ignite.internal.IgniteEx;
8
import org.apache.ignite.internal.processors.security.GridSecurityProcessor;
9
import org.apache.ignite.plugin.CachePluginContext;
10
import org.apache.ignite.plugin.CachePluginProvider;
11
import org.apache.ignite.plugin.ExtensionRegistry;
12
import org.apache.ignite.plugin.IgnitePlugin;
13
import org.apache.ignite.plugin.PluginContext;
14
import org.apache.ignite.plugin.PluginProvider;
15
import org.apache.ignite.plugin.PluginValidationException;
16
import org.apache.ignite.plugin.security.SecurityCredentials;
17

          
18
public class SecurityPluginProvider implements PluginProvider {
19

          
20
    private final SecurityCredentials localNodeCredentials;
21

          
22
    public SecurityPluginProvider(SecurityCredentials cred) {
23
        localNodeCredentials = cred;
24
    }
25

          
26
    public Object createComponent(PluginContext ctx, Class cls) {
27
        if (cls.isAssignableFrom(GridSecurityProcessor.class))
28
            return new GridSecurityProcessorImpl(((IgniteEx)ctx.grid()).context(), localNodeCredentials);
29

          
30
        return null;
31
    }
32

          
33
    public String name() {
34
        return "SecurityPluginProvider";
35
    }
36

          
37
    public String version() {
38
        return "1.0.0";
39
    }
40

          
41
    public String copyright() {
42
        return "for the article";
43
    }
44

          
45
    public IgnitePlugin plugin() {
46
        return new IgnitePlugin() {
47
        };
48
    }
49
  
50
  // other no-op methods of PluginProvider
51
}


When Ignite's node-starting routine requires a security processor, the createComponent() method returns an instance of GridSecurityProcessor. We don't need an IgnitePlugin object, but we cannot return null, so we created this instance of IgnitePlugin that does nothing.

Now, we are ready to use our plugin to start nodes. Using ignite.bat and Spring configuration, I started a node:

XML
 




x


 
1
    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
2
        <property name="pluginProviders">
3
            <array>
4
                <bean class="security.SecurityPluginProvider">
5
                   <constructor-arg ref="credentials"/>
6
                </bean>
7
            </array>
8
        </property>
9
    </bean>
10

          
11
    <bean id="credentials" class="org.apache.ignite.plugin.security.SecurityCredentials">
12
        <constructor-arg value="firstSubject"/>
13
        <constructor-arg value=""/>
14
    </bean>


And, the second node was started from the following Java code:

Java
 




x


 
1
    Ignition.start(
2
            new IgniteConfiguration()
3
                .setPluginProviders(
4
                    new SecurityPluginProvider(new SecurityCredentials("secondSubject", null))
5
                )
6
        );


Let's look at the console output on the joining node:

We see that the security plugin is in place and started, and authentication is on.

The first node that started is the coordinator. In our case, the coordinator processes authentication of the node that is joining. The coordinator displays the following text:

Plain Text
 




xxxxxxxxxx
1


 
1
[15:30:55] [GridSecurityProcessorImpl] Authenticate node; localNode=c060fe0a-0b95-4903-8612-3e773e702eb4, authenticatedNode=0958bfd8-f2fe-4a17-9e58-ded8bdaa4c7e, login=secondSubject



For all nodes in the cluster, the same security plugin must be configured and security must be enabled. Otherwise, when the node starts, you receive an error.

Authorize Ignite Operations

To authorize Ignite operations, we need to implement the authorize() method, which can look like the following:

Java
 




x


 
1
    public void authorize(String name, SecurityPermission perm, SecurityContext secCtx) 
2
        throws SecurityException {
3
        if (!((SecurityContextImpl)secCtx).operationAllowed(name, perm))
4
            throw new SecurityException("Authorization failed [perm=" + perm +
5
                ", name=" + name +
6
                ", subject=" + secCtx.subject() + ']');
7
    }


The authorize() method throws a SecurityException if the requested access, defined by the name and the permission, is not permitted based on passed SecurityContext.

Let's try to authorize cache operations.

Create two caches by adding a definition of cache configurations to the Ignite configuration file:

XML
 




x


 
1
    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
2
        <property name="pluginProviders">
3
            <array>
4
                <bean class="security.SecurityPluginProvider">
5
                   <constructor-arg ref="credentials"/>
6
                </bean>
7
            </array>
8
        </property>
9
        <property name="cacheConfiguration">
10
            <array>
11
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
12
                    <constructor-arg value="common_cache"/>
13
                </bean>
14
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
15
                    <constructor-arg value="secret_cache"/>
16
                </bean>
17
            </array>
18
        </property>
19
    </bean>


Add to the implementation of GridSecurityProcessor, the method that emulates a security poliсy:

Java
 




xxxxxxxxxx
1
10


 
1
    private SecurityPermissionSet getPermissionSet(Object login) {
2
        if(login.equals("secondSubject"))
3
            return new SecurityPermissionSetBuilder()
4
                .appendSystemPermissions(SecurityPermission.JOIN_AS_SERVER)
5
                .appendCachePermissions("common_cache",
6
                                        SecurityPermission.CACHE_PUT,
7
                                        SecurityPermission.CACHE_READ)
8
                .build();
9

          
10
        return SecurityPermissionSetBuilder.ALLOW_ALL;
11
    }


The subject with the secondSubject login can write to and read from only the cache that is named common_cache. The process of node authentication uses the following method to get the subject's permissions:

Java
 




x


 
1
    SecuritySubject subject = new SecuritySubjectImpl()
2
    subject.permissions(getPermissionSet(credentials.getLogin()));
3
    // sets other fields like described early


Now, we are ready to start the cache-operations example:

Java
 




x


 
1
        Ignite ignite = Ignition.start(
2
            new IgniteConfiguration()
3
                .setPluginProviders(
4
                    new SecurityPluginProvider(new SecurityCredentials("secondSubject", null))
5
                )
6
        );
7
        try {
8
            ignite.cache("secret_cache").put("key", "value");
9
        }
10
        catch (SecurityException e){
11
            System.out.println("The try to put to 'secret_cache': " + e.getMessage());
12
        }
13
        IgniteCache<String, String> cache = ignite.cache("common_cache");
14
        cache.put("key", "some_value");
15
        System.out.println("Cache " + cache.getName() + " key=" + cache.get("key"));
16
        try {
17
            cache.remove("key");
18
        }
19
        catch (SecurityException e){
20
            System.out.println("The try to remove from 'common_cache': " + e.getMessage());
21
        }


The output looks like the following:

Plain Text
 




xxxxxxxxxx
1


 
1
The try to put to 'secret_cache': Authorization failed [perm=CACHE_PUT, name=secret_cache, subject=TestSecuritySubject{login=secondSubject}]
2
Cache common_cache key=some_value
3
The try to remove from 'common_cache': Authorization failed [perm=CACHE_REMOVE, name=common_cache, subject=TestSecuritySubject{login=secondSubject}]


In regard to the put operation, the output indicates that the subject that has the secondSubject login doesn't have permission to manipulate the cache that is named secret_cache. The lack of permission is due to the fact that we didn't mention the name secret_cache in our security policy. However, the subject can perform put and read operations, but not remove operations, on the cache that is named common_cache. This behavior adheres to the permissions that we defined in the security policy method.

Thin Client Authentication

A thin client is a lightweight Ignite client that establishes a socket connection to a standard Ignite node. The node performs authentication and creates a security context associated with the thin client. The GridSecurityProcessor provides the interface to get a thin client's security context on every node in the cluster. But how to implement this feature is the developers' decision. I'm going to use Ignite's cache to make a thin client's security context accessible throughout the cluster.

Define the following methods in the GridSecurityProcessor implementation:

Java
 




xxxxxxxxxx
1
26


 
1
    public SecurityContext authenticate(AuthenticationContext context) {
2
        // This is the place to check the credentials of the thin client.
3
      
4
        SecuritySubject subject = new SecuritySubjectImpl()
5
            .id(context.subjectId())
6
            .login(context.credentials().getLogin())
7
            .type(SecuritySubjectType.REMOTE_CLIENT)
8
            .permissions(getPermissionSet(context.credentials().getLogin()));
9

          
10
        SecurityContext res = new SecurityContextImpl(subject);
11

          
12
        ctx.grid().getOrCreateCache("thin_clients").put(subject.id(), res);
13

          
14
        U.quiet(false, "[GridSecurityProcessorImpl] Authenticate thin client subject; " +
15
            " login=" + subject.login());
16

          
17
        return res;
18
    }
19

          
20
    public SecurityContext securityContext(UUID subjId) {
21
        return (SecurityContext)ctx.grid().getOrCreateCache("thin_clients").get(subjId);
22
    }
23

          
24
    public void onSessionExpired(UUID subjId) {
25
        ctx.grid().getOrCreateCache("thin_clients").remove(subjId);
26
    }


We use the thin_clients cache to implement transmission of a client's security context between nodes.

Now, we can start a thin client:

Java
 




xxxxxxxxxx
1
11


 
1
 ClientConfiguration config = new ClientConfiguration()
2
     .setAddresses("127.0.0.1:10800")
3
     .setUserName("secondSubject")
4
     .setUserPassword("");
5

          
6
 try (IgniteClient client = Ignition.startClient(config)) {
7
     ClientCache<String, String> cache = client.cache("common_cache");
8
     cache.put("key", "some_value");
9
     System.out.println("Cache " + cache.getName() + " key=" + cache.get("key"));
10
     cache.remove("key");
11
 }


Now, we remember that the secondSubject subject has put and read permissions for the common_cache cache but that the subject cannot remove anything from the cache. Therefore, the output looks like the following:

Plain Text
 




xxxxxxxxxx
1


 
1
Cache common_cache key=some_value
2
Exception in thread "main" org.apache.ignite.client.ClientAuthorizationException: User is not authorized to perform this operation


Run User-Defined Code With Restrictions

User-defined code contains custom logic via various APIs, including compute tasks, event filters, and message listeners. In some cases, you might want to restrict the code's capabilities on the nodes that execute the code. For this purpose, you can use the Ignite Sandbox feature.

Let's consider changes that have been made in the security interface implementations that allow code to be run inside Sandbox.

Java
 




x


 
1
public class GridSecurityProcessorImpl extends GridProcessorAdapter implements GridSecurityProcessor {
2
  
3
    public boolean sandboxEnabled() {
4
        return true;
5
    }
6
  
7
    private PermissionCollection getSandboxPermissions(Object login) {
8
        PermissionCollection res = new Permissions();
9
        if (login.equals("sandboxSubject"))
10
            res.add(new PropertyPermission("java.version", "read"));
11
        else
12
            res.add(new AllPermission());
13
        return res;
14
    }
15

          
16
    public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials credentials) {
17
        // This is the place to check the credentials of the joining node.
18

          
19
        SecuritySubject subject = new SecuritySubjectImpl()
20
            .id(node.id())
21
            .login(credentials.getLogin())
22
            .address(new InetSocketAddress(F.first(node.addresses()), 0))
23
            .type(SecuritySubjectType.REMOTE_NODE)
24
            .permissions(getPermissionSet(credentials.getLogin()))
25
            .sandboxPermissions(getSandboxPermissions(credentials.getLogin()));
26

          
27
        U.quiet(false, "[GridSecurityProcessorImpl] Authenticate node; " +
28
            "localNode=" + ctx.localNodeId() +
29
            ", authenticatedNode=" + node.id() +
30
            ", login=" + credentials.getLogin());
31

          
32
        return new SecurityContextImpl(subject);
33
    }
34
  
35
  // other fields and methods
36
}


The sandboxEnable() method enables you to switch off Ignite Sandbox when Java's SecurityManager is in place. By default, Ignite Sandbox is off, so we will override this method. Where a node passes authentication, we use the getSandboxPermissions() method to emulate a security policy inside Sandbox. A security subject with the sandboxSubject login has only read access to the system property that is named java.version.

Java
 




xxxxxxxxxx
1
14


 
1
public class SecuritySubjectImpl implements SecuritySubject {
2
  
3
    private PermissionCollection sandboxPermissions;
4
  
5
    public PermissionCollection sandboxPermissions() {
6
        return sandboxPermissions;
7
    }
8

          
9
    public SecuritySubjectImpl sandboxPermissions(PermissionCollection sandboxPermissions) {
10
        this.sandboxPermissions = sandboxPermissions;
11
        return this;
12
    }
13
  
14
  // other fields and methods
15
}


The implementation of SecuritySubject contains the field and the methods that are required to pass permissions to Sandbox.

Ignite needs enough permissions to work correctly. I'm going to use the most straightforward way to grant all permissions to Ignite. I will use the {IGNITE-HOME}\security\test.policy file:

Plain Text
 




xxxxxxxxxx
1


 
1
grant codeBase "file:{$IGNITE-HOME}\\libs\\-" {
2
        permission java.security.AllPermission;
3
};


To start an Ignite node with SecurityManager and a specific security policy, we can modify the ignite.bat file by adding the following text into the ADD YOUR/CHANGE ADDITIONAL OPTIONS HERE section:

Shell
 




x


 
1
set JVM_OPTS=%JVM_OPTS% -Djava.security.manager 
2
set JVM_OPTS=%JVM_OPTS% -Djava.security.policy="file:%IGNITE_HOME%\security\test.policy"


Now, we are ready to do our final test. When we start an Ignite node, we see the following output:

Plain Text
 




xxxxxxxxxx
1


 
1
[15:30:56] Security status [authentication=on, sandbox=on, tls/ssl=off]


Notice that Sandbox is on.

The test example looks like the following:

Java
 




xxxxxxxxxx
1
40


 
1
import org.apache.ignite.Ignite;
2
import org.apache.ignite.IgniteCompute;
3
import org.apache.ignite.Ignition;
4
import org.apache.ignite.configuration.IgniteConfiguration;
5
import org.apache.ignite.lang.IgniteCallable;
6
import org.apache.ignite.plugin.security.SecurityCredentials;
7
import security.SecurityPluginProvider;
8

          
9
public class SandboxTest {
10
    public static void main(String[] args) {
11
        Ignite ignite = Ignition.start(
12
            new IgniteConfiguration()
13
                .setPeerClassLoadingEnabled(true)
14
                .setPluginProviders(
15
                    new SecurityPluginProvider(new SecurityCredentials("sandboxSubject", null))
16
                )
17
        );
18
        try {
19
            IgniteCompute compute = ignite.compute(ignite.cluster().forRemotes());
20
            System.out.println("Java version: " + compute.call(new PropertyReader("java.version")));
21
            System.out.println("Java home: " + compute.call(new PropertyReader("java.home")));
22
        }
23
        finally {
24
            Ignition.stop(true);
25
        }
26
    }
27

          
28
    private static class PropertyReader implements IgniteCallable<String>{
29
        private final String propertyName;
30

          
31
        public PropertyReader(String propertyName) {
32
            this.propertyName = propertyName;
33
        }
34

          
35
        @Override public String call() {
36
            return System.getProperty(propertyName);
37
        }
38
    }
39
}


For the first calling, we get the Java version that is used on the remote node. But, for the reading java.home property, we get the following output:

Plain Text
 




xxxxxxxxxx
1


 
1
java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.home" "read")


Peer class loading should be enabled on every node.

It is hard to find all the information that you need about how to implement an Ignite security plugin in one place, so I hope that this tutorial will be useful to you. If you find the tutorial useful, please like this blog post. 

If you have a question or comment, please enter it below.

security Apache Ignite Java (programming language) Plain text Cache (computing) code style cluster Implementation Scratch (programming language) Interface (computing)

Opinions expressed by DZone contributors are their own.

Related

  • What Is API-First?
  • Binary Code Verification in Open Source World
  • Spring Beans With Auto-Generated Implementations: How-To
  • Rapidly Develop Java Microservices on Kubernetes With Telepresence

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!