IDIEP-41
Author
Sponsor
Created 02.03.2020
Status

ACTIVE


Motivation

At present, Ignite uses cluster node attributes to spread a security context. When a node joins a cluster, it is authorized, and its security context is written to IgniteNodeAttributes.ATTR_SECURITY_SUBJECT_V2 attribute.
There is an assumption that the subject id equals to the node id. Therefore, we should find a cluster node with an identifier that equals to subject id and extract the security context from attributes in order to get a security context by subject id.

But if an operation is initialized by any kind of thin client (binary protocol-based client, REST client, JDBC client), the assumption of subject id equality to node id is not valid. Hence, in the case of thin clients, Ignite cannot get actual security context; this can be the reason for fatal errors on a remote node. The following reproducer demonstrates behavior described: testLocalInsert succeeds, while testRemoteInsert fails.

Reproducer for JDBC Client
package org.apache.ignite.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ignite.client.Config;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.processors.security.AbstractSecurityTest;
import org.apache.ignite.internal.processors.security.impl.TestSecurityData;
import org.apache.ignite.internal.processors.security.impl.TestSecurityPluginProvider;
import org.apache.ignite.plugin.security.SecurityPermission;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
import org.junit.Test;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.ignite.cache.CacheMode.PARTITIONED;
import static org.apache.ignite.cluster.ClusterState.ACTIVE;
import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_CREATE;
import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_PUT;
import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_READ;
import static org.apache.ignite.plugin.security.SecurityPermission.JOIN_AS_SERVER;
import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.create;
import static org.apache.ignite.testframework.GridTestUtils.assertTimeout;

/** */
public class JdbcRemoteKeyInsertTest extends AbstractSecurityTest {
    /** */
    public static final String JDBC_URL_PREFIX = "jdbc:ignite:thin://";

    /** */
    private static final String TEST_CACHE = "test-cache";

    /** */
    private static final String TEST_SCHEMA = "test_schema";

    /** */
    private static final String JDBC_CLIENT = "jdbc-client";

    /** {@inheritDoc} */
    @Override protected void beforeTestsStarted() throws Exception {
        super.beforeTestsStarted();

        cleanPersistenceDir();

        startGrid(
            getConfiguration(0,
                new TestSecurityData(
                    JDBC_CLIENT,
                    cachePermissions(TEST_CACHE, CACHE_PUT)
                )
            )
        );

        startGrid(getConfiguration(1)).cluster().state(ACTIVE);

        CacheConfiguration<Integer, Integer> ccfg = new CacheConfiguration<>(TEST_CACHE);

        ccfg.setIndexedTypes(Integer.class, Integer.class);
        ccfg.setCacheMode(PARTITIONED);
        ccfg.setSqlSchema(TEST_SCHEMA);

        grid(0).createCache(ccfg);
    }

    /** */
    private IgniteConfiguration getConfiguration(int idx, TestSecurityData... users) throws Exception {
        String instanceName = getTestIgniteInstanceName(idx);

        return getConfiguration(
            instanceName,
            new TestSecurityPluginProvider(
                instanceName,
                null,
                create()
                    .appendSystemPermissions(CACHE_CREATE, JOIN_AS_SERVER)
                    .appendCachePermissions(TEST_CACHE, CACHE_READ)
                    .build(),
                false,
                users
            )
        );
    }

    /** {@inheritDoc} */
    @Override protected long getTestTimeout() {
        return 30 * 1000;
    }

    /** */
    private void doInsert(int nodeIdx) throws Exception {
        int key = keyForNode(nodeIdx);

        String tableName = Integer.class.getSimpleName();

        execute(
            JDBC_CLIENT,
            "INSERT INTO " + TEST_SCHEMA + '.' + tableName + " (_key, _val) VALUES (" + key + ", 0)"
        );

        int val = grid(1).<Integer, Integer>cache(TEST_CACHE).get(key);

        assert val == 0;
    }

    /** */
    @Test
    public void testLocalInsert() throws Exception {
        doInsert(0);
    }

    /** */
    @Test
    public void testRemoteInsert() throws Exception {
        doInsert(1);
    }

    /** */
    private int keyForNode(int nodeIdx) {
        return keyForNode(
            grid(0).affinity(TEST_CACHE),
            new AtomicInteger(0),
            grid(nodeIdx).localNode()
        );
    }

    /** */
    private SecurityPermissionSet cachePermissions(String cacheName, SecurityPermission... perms) {
        return create()
            .defaultAllowAll(false)
            .appendCachePermissions(cacheName, perms)
            .build();
    }

    /** */
    protected void execute(String login, String sql) throws Exception {
        try (Connection conn = getConnection(login)) {
            Statement stmt = conn.createStatement();

            assertTimeout(
                getTestTimeout(),
                MILLISECONDS,
                () -> {
                    try {
                        stmt.execute(sql);
                    }
                    catch (SQLException e) {
                        throw new RuntimeException(e);
                    }
                });
        }
    }

    /** */
    protected Connection getConnection(String login) throws SQLException {
        return DriverManager.getConnection(JDBC_URL_PREFIX + Config.SERVER, login, "");
    }
}

Description

Getting a security context

To solve the problem, I offer to extend GridSecurityProcessor interface by adding securityContext(UUID subjId) method and use this method to get the actual security context.

GridSecurityProcessor
/**
* Gets security context for authenticated nodes and thin clients.
*
* @param subjId Security subject id.
* @return Security context or null if not found.
*/
public default SecurityContext securityContext(UUID subjId){
	throw new UnsupportedOperationException();
}


The logic of security context retrieval resides in GridSecurityProcessor implementations.

The subject id for the node can be stored in its IgniteNodeAttributes.ATTR_SECURITY_SUBJECT_ID attribute.


Unknown security context

In the case when GridSecurityProcessor cannot get a security context, it can lead to node hanging. To avoid this problem, we should return the unknown security context.

All operations are forbidden with the unknown security context.

This way allows getting a response with an error message on the client-side.


Backward compatibility

The logic of getting security context for Ignite up to v. 3.0:

  1. Get a security context through GridSecurityProcessor;
  2. If GridSecurityProcessor returned null, try to get a security context using ClusterNode attributes (as it works now);
  3. If ClusterNode attributes contain no security context, return the unknown security context.


The logic of getting security context since v. 3.0:

  1. Get a security context through GridSecurityProcessor;
  2. If GridSecurityProcessor returned null, return the unknown security context.

Thus, we will provide backward compatibility for existing GridSecurityProcessor implementations up to Ignite version 3.0.

I suggest removing IgniteNodeAttributes.ATTR_SECURITY_SUBJECT_V2 in Ignite version 3.0.

Risks and Assumptions

// Describe project risks, such as API or binary compatibility issues, major protocol changes, etc.

Discussion Links

http://apache-ignite-developers.2346864.n4.nabble.com/JDBC-thin-client-incorrect-security-context-td45929.html

http://apache-ignite-developers.2346864.n4.nabble.com/Security-Subject-of-thin-client-on-remote-nodes-td46029.html

Reference Links

Reproducer for REST Client

Tickets

Key Summary T Created Assignee Reporter P Status Resolution
Loading...
Refresh

  • No labels