This is an automated email from the ASF dual-hosted git repository.
nizhikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 5590b018fdb IGNITE-24076 Distributed properties to block new
connections (#11766)
5590b018fdb is described below
commit 5590b018fdb94fa6488f2b7b7a1f6b7e53c0d410
Author: Nikolay <[email protected]>
AuthorDate: Thu Dec 26 11:34:46 2024 +0300
IGNITE-24076 Distributed properties to block new connections (#11766)
---
.../jdbc/JdbcConnectionEnabledPropertyTest.java | 71 ++++++++++
.../jdbc/JdbcThinTransactionalSelfTest.java | 2 +-
.../ignite/testsuites/IgniteCalciteTestSuite.java | 2 +
.../cluster/DistributedConfigurationUtils.java | 45 +++++++
.../odbc/ClientListenerConnectionContext.java | 9 ++
.../processors/odbc/ClientListenerNioListener.java | 61 ++++++++-
.../processors/odbc/ClientListenerProcessor.java | 40 +++++-
.../ignite/spi/discovery/tcp/ServerImpl.java | 38 +++++-
.../cache/ConnectionEnabledPropertyTest.java | 149 +++++++++++++++++++++
.../ThinClientPermissionCheckSecurityTest.java | 5 +
.../client/ThinClientPermissionCheckTest.java | 81 ++++++++++-
.../ignite/testframework/junits/IgniteMock.java | 7 +-
.../ignite/testsuites/IgnitePdsTestSuite8.java | 3 +
13 files changed, 501 insertions(+), 12 deletions(-)
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcConnectionEnabledPropertyTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcConnectionEnabledPropertyTest.java
new file mode 100644
index 00000000000..b6cce49c10b
--- /dev/null
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcConnectionEnabledPropertyTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.jdbc;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+import static
org.apache.ignite.internal.cluster.DistributedConfigurationUtils.CONN_DISABLED_BY_ADMIN_ERR_MSG;
+import static
org.apache.ignite.internal.processors.configuration.distributed.DistributedConfigurationProcessor.toMetaStorageKey;
+import static
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcThinTransactionalSelfTest.URL;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
+
+/** */
+public class JdbcConnectionEnabledPropertyTest extends GridCommonAbstractTest {
+ /** */
+ private static final String JDBC_CONN_ENABLED_PROP =
"newJdbcConnectionsEnabled";
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ cfg.getSqlConfiguration().setQueryEnginesConfiguration(new
CalciteQueryEngineConfiguration());
+
+ return cfg;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ startGrid();
+ }
+
+ /** */
+ @Test
+ public void testConnectionEnabledProperty() throws Exception {
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ assertTrue(conn.isValid(3));
+ }
+
+
grid().context().distributedMetastorage().write(toMetaStorageKey(JDBC_CONN_ENABLED_PROP),
false);
+
+ assertThrows(log, () -> DriverManager.getConnection(URL),
SQLException.class, CONN_DISABLED_BY_ADMIN_ERR_MSG);
+
+
grid().context().distributedMetastorage().write(toMetaStorageKey(JDBC_CONN_ENABLED_PROP),
true);
+
+ try (Connection conn = DriverManager.getConnection(URL)) {
+ assertTrue(conn.isValid(3));
+ }
+ }
+}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcThinTransactionalSelfTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcThinTransactionalSelfTest.java
index d1e17397d6d..1a583c760db 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcThinTransactionalSelfTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcThinTransactionalSelfTest.java
@@ -49,7 +49,7 @@ import static
org.apache.ignite.testframework.GridTestUtils.assertThrows;
/** */
public class JdbcThinTransactionalSelfTest extends GridCommonAbstractTest {
/** URL. */
- private static final String URL = "jdbc:ignite:thin://127.0.0.1";
+ public static final String URL = "jdbc:ignite:thin://127.0.0.1";
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
index 4a0cfa7e0cb..5bdf5d417b5 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
@@ -24,6 +24,7 @@ import
org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplem
import
org.apache.ignite.internal.processors.query.calcite.exec.NumericTypesPrecisionsTest;
import
org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteSqlFunctionsTest;
import
org.apache.ignite.internal.processors.query.calcite.exec.tracker.MemoryTrackerTest;
+import
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcConnectionEnabledPropertyTest;
import
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcSetClientInfoTest;
import
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcThinTransactionalSelfTest;
import
org.apache.ignite.internal.processors.query.calcite.message.CalciteCommunicationMessageSerializationTest;
@@ -62,6 +63,7 @@ import org.junit.runners.Suite;
JdbcThinTransactionalSelfTest.class,
JdbcSetClientInfoTest.class,
+ JdbcConnectionEnabledPropertyTest.class
})
public class IgniteCalciteTestSuite {
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/cluster/DistributedConfigurationUtils.java
b/modules/core/src/main/java/org/apache/ignite/internal/cluster/DistributedConfigurationUtils.java
index eb12788dfaf..3bb947e2e1d 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/cluster/DistributedConfigurationUtils.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/cluster/DistributedConfigurationUtils.java
@@ -18,12 +18,19 @@
package org.apache.ignite.internal.cluster;
import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.IgniteInternalFuture;
import
org.apache.ignite.internal.processors.configuration.distributed.DistributePropertyListener;
+import
org.apache.ignite.internal.processors.configuration.distributed.DistributedBooleanProperty;
+import
org.apache.ignite.internal.processors.configuration.distributed.DistributedConfigurationLifecycleListener;
import
org.apache.ignite.internal.processors.configuration.distributed.DistributedProperty;
+import
org.apache.ignite.internal.processors.configuration.distributed.DistributedPropertyDispatcher;
+import
org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
import org.apache.ignite.internal.util.future.GridFinishedFuture;
import org.jetbrains.annotations.NotNull;
@@ -33,6 +40,9 @@ import static java.lang.String.format;
* Distributed configuration utilities methods.
*/
public final class DistributedConfigurationUtils {
+ /** */
+ public static final String CONN_DISABLED_BY_ADMIN_ERR_MSG = "Connection
disabled by administrator";
+
/**
*/
private DistributedConfigurationUtils() {
@@ -95,4 +105,39 @@ public final class DistributedConfigurationUtils {
}
};
}
+
+ /**
+ * Creates and registers distributed properties to enable connection by
type.
+ * @param subscriptionProcessor Processor to register properties.
+ * @param log Logger to log default values.
+ * @param types Connection types.
+ * @return Detached distributed property.
+ */
+ public static List<DistributedBooleanProperty>
newConnectionEnabledProperty(
+ GridInternalSubscriptionProcessor subscriptionProcessor,
+ IgniteLogger log,
+ String... types
+ ) {
+ List<DistributedBooleanProperty> props = Arrays.stream(types).map(type
-> DistributedBooleanProperty.detachedBooleanProperty(
+ "new" + type + "ConnectionsEnabled",
+ "If true then new " + type.toUpperCase() + " connections allowed."
+ )).collect(Collectors.toList());
+
+ subscriptionProcessor.registerDistributedConfigurationListener(new
DistributedConfigurationLifecycleListener() {
+ @Override public void
onReadyToRegister(DistributedPropertyDispatcher dispatcher) {
+ props.forEach(p -> {
+ if (dispatcher.property(p.getName()) != null)
+ return;
+
+ dispatcher.registerProperty(p);
+ });
+ }
+
+ @Override public void onReadyToWrite() {
+ props.forEach(prop -> setDefaultValue(prop, true, log));
+ }
+ });
+
+ return props;
+ }
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
index 11a87649349..8e827a49ad2 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java
@@ -24,6 +24,8 @@ import
org.apache.ignite.internal.processors.security.SecurityContext;
import org.apache.ignite.internal.util.nio.GridNioSession;
import org.jetbrains.annotations.Nullable;
+import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.MANAGEMENT_CLIENT_ATTR;
+
/**
* SQL listener connection context.
*/
@@ -88,4 +90,11 @@ public interface ClientListenerConnectionContext {
* Connection attributes.
*/
Map<String, String> attributes();
+
+ /**
+ * @return {@code True} if client is management.
+ */
+ default boolean managementClient() {
+ return Boolean.parseBoolean(attributes().get(MANAGEMENT_CLIENT_ATTR));
+ }
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
index fd22b5feef2..d21aa215fe3 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.odbc;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.configuration.ClientConnectorConfiguration;
@@ -47,8 +48,11 @@ import
org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter;
import org.apache.ignite.internal.util.nio.GridNioSession;
import org.apache.ignite.internal.util.nio.GridNioSessionMetaKey;
import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.plugin.security.SecurityException;
+import org.apache.ignite.plugin.security.SecurityPermission;
import org.jetbrains.annotations.Nullable;
+import static
org.apache.ignite.internal.cluster.DistributedConfigurationUtils.CONN_DISABLED_BY_ADMIN_ERR_MSG;
import static
org.apache.ignite.internal.processors.odbc.ClientListenerMetrics.clientTypeLabel;
/**
@@ -103,6 +107,16 @@ public class ClientListenerNioListener extends
GridNioServerListenerAdapter<Clie
/** Metrics. */
private final ClientListenerMetrics metrics;
+ /**
+ * If return {@code true} then specifi protocol connections enabled.
+ * Predicate checks distributed property value.
+ *
+ * @see ClientListenerNioListener#ODBC_CLIENT
+ * @see ClientListenerNioListener#JDBC_CLIENT
+ * @see ClientListenerNioListener#THIN_CLIENT
+ */
+ private final Predicate<Byte> newConnEnabled;
+
/**
* Constructor.
*
@@ -110,12 +124,14 @@ public class ClientListenerNioListener extends
GridNioServerListenerAdapter<Clie
* @param busyLock Shutdown busy lock.
* @param cliConnCfg Client connector configuration.
* @param metrics Client listener metrics.
+ * @param newConnEnabled Predicate to check if connection of specified
type enabled.
*/
public ClientListenerNioListener(
GridKernalContext ctx,
GridSpinBusyLock busyLock,
ClientConnectorConfiguration cliConnCfg,
- ClientListenerMetrics metrics
+ ClientListenerMetrics metrics,
+ Predicate<Byte> newConnEnabled
) {
assert cliConnCfg != null;
@@ -126,10 +142,12 @@ public class ClientListenerNioListener extends
GridNioServerListenerAdapter<Clie
maxCursors = cliConnCfg.getMaxOpenCursorsPerConnection();
log = ctx.log(getClass());
- thinCfg = cliConnCfg.getThinClientConfiguration() == null ? new
ThinClientConfiguration()
+ thinCfg = cliConnCfg.getThinClientConfiguration() == null
+ ? new ThinClientConfiguration()
: new
ThinClientConfiguration(cliConnCfg.getThinClientConfiguration());
this.metrics = metrics;
+ this.newConnEnabled = newConnEnabled;
}
/** {@inheritDoc} */
@@ -380,8 +398,7 @@ public class ClientListenerNioListener extends
GridNioServerListenerAdapter<Clie
if (connCtx.isVersionSupported(ver)) {
connCtx.initializeFromHandshake(ses, ver, reader);
- if (nodeInRecoveryMode() &&
!Boolean.parseBoolean(connCtx.attributes().get(MANAGEMENT_CLIENT_ATTR)))
- throw new ClientConnectionNodeRecoveryException("Node in
recovery mode.");
+ ensureConnectionAllowed(connCtx);
ses.addMeta(CONN_CTX_META_KEY, connCtx);
}
@@ -534,4 +551,40 @@ public class ClientListenerNioListener extends
GridNioServerListenerAdapter<Clie
throw new IgniteCheckedException("Unknown client type: " +
clientType);
}
}
+
+ /**
+ * Ensures if the client are allowed to connect.
+ *
+ * @param connCtx Connection context.
+ * @throws IgniteCheckedException If failed.
+ */
+ private void ensureConnectionAllowed(ClientListenerConnectionContext
connCtx) throws IgniteCheckedException {
+ boolean isControlUtility = connCtx.clientType() == THIN_CLIENT &&
connCtx.managementClient();
+
+ if (nodeInRecoveryMode()) {
+ if (!isControlUtility)
+ throw new ClientConnectionNodeRecoveryException("Node in
recovery mode.");
+
+ return;
+ }
+
+ // If security enabled then only admin allowed to connect as
management.
+ if (isControlUtility) {
+ if (connCtx.securityContext() != null) {
+ try (OperationSecurityContext ignored =
ctx.security().withContext(connCtx.securityContext())) {
+ ctx.security().authorize(SecurityPermission.ADMIN_OPS);
+ }
+ catch (SecurityException e) {
+ throw new IgniteAccessControlException("ADMIN_OPS
permission required");
+ }
+ }
+
+ // Allow to connect control utility even if connection disabled.
+ // Must provide a way to invoke commands.
+ return;
+ }
+
+ if (!newConnEnabled.test(connCtx.clientType()))
+ throw new
IgniteAccessControlException(CONN_DISABLED_BY_ADMIN_ERR_MSG);
+ }
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
index e09f535d5b6..4bb2f61314b 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java
@@ -23,11 +23,13 @@ import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.cache.configuration.Factory;
import javax.management.JMException;
import javax.management.ObjectName;
@@ -42,6 +44,7 @@ import org.apache.ignite.internal.GridKernalContext;
import
org.apache.ignite.internal.managers.systemview.walker.ClientConnectionAttributeViewWalker;
import
org.apache.ignite.internal.managers.systemview.walker.ClientConnectionViewWalker;
import org.apache.ignite.internal.processors.GridProcessorAdapter;
+import
org.apache.ignite.internal.processors.configuration.distributed.DistributedBooleanProperty;
import
org.apache.ignite.internal.processors.configuration.distributed.DistributedThinClientConfiguration;
import org.apache.ignite.internal.processors.metric.MetricRegistryImpl;
import org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext;
@@ -66,11 +69,15 @@ import
org.apache.ignite.spi.systemview.view.ClientConnectionView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import static
org.apache.ignite.internal.cluster.DistributedConfigurationUtils.newConnectionEnabledProperty;
import static
org.apache.ignite.internal.processors.metric.GridMetricManager.CLIENT_CONNECTOR_METRICS;
import static
org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName;
import static
org.apache.ignite.internal.processors.odbc.ClientListenerMetrics.clientTypeLabel;
import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.CLI_TYPES;
import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.CONN_CTX_META_KEY;
+import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.JDBC_CLIENT;
+import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.ODBC_CLIENT;
+import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.THIN_CLIENT;
/**
* Client connector processor.
@@ -177,12 +184,14 @@ public class ClientListenerProcessor extends
GridProcessorAdapter {
? this::onOutboundMessageOffered
: null;
+ Predicate<Byte> newConnEnabled = connectionEnabledPredicate();
+
for (int port = cliConnCfg.getPort(); port <= portTo && port
<= 65535; port++) {
try {
srv = GridNioServer.<ClientMessage>builder()
.address(hostAddr)
.port(port)
- .listener(new ClientListenerNioListener(ctx,
busyLock, cliConnCfg, metrics))
+ .listener(new ClientListenerNioListener(ctx,
busyLock, cliConnCfg, metrics, newConnEnabled))
.logger(log)
.selectorCount(selectorCnt)
.igniteInstanceName(ctx.igniteInstanceName())
@@ -248,6 +257,35 @@ public class ClientListenerProcessor extends
GridProcessorAdapter {
}
}
+ /**
+ * @return Predicate to check is connection for specific client type
enabled.
+ * @see ClientListenerNioListener#ODBC_CLIENT
+ * @see ClientListenerNioListener#JDBC_CLIENT
+ * @see ClientListenerNioListener#THIN_CLIENT
+ */
+ private Predicate<Byte> connectionEnabledPredicate() {
+ Map<Byte, DistributedBooleanProperty> connEnabledMap = new HashMap<>();
+
+ List<DistributedBooleanProperty> props = newConnectionEnabledProperty(
+ ctx.internalSubscriptionProcessor(),
+ log,
+ "Odbc",
+ "Jdbc",
+ "Thin"
+ );
+
+ connEnabledMap.put(ODBC_CLIENT, props.get(0));
+ connEnabledMap.put(JDBC_CLIENT, props.get(1));
+ connEnabledMap.put(THIN_CLIENT, props.get(2));
+
+ return type -> {
+ assert type != null : "Connection type is null";
+ assert connEnabledMap.containsKey(type) : "Unknown connection
type: " + type;
+
+ return connEnabledMap.get(type).getOrDefault(true);
+ };
+ }
+
/** */
private Iterable<ClientConnectionAttributeView>
connectionAttributeViewSupplier(Map<String, Object> filter) {
Long connId =
(Long)filter.get(ClientConnectionAttributeViewWalker.CONNECTION_ID_FILTER);
diff --git
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
index 8cb424bab34..d4e65e2d4ba 100644
---
a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
+++
b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java
@@ -82,6 +82,7 @@ import org.apache.ignite.internal.IgnitionEx;
import org.apache.ignite.internal.events.DiscoveryCustomEvent;
import org.apache.ignite.internal.managers.discovery.CustomMessageWrapper;
import
org.apache.ignite.internal.managers.discovery.DiscoveryServerOnlyCustomMessage;
+import
org.apache.ignite.internal.processors.configuration.distributed.DistributedBooleanProperty;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
import org.apache.ignite.internal.processors.security.SecurityContext;
import org.apache.ignite.internal.processors.security.SecurityUtils;
@@ -177,6 +178,8 @@ import static
org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MARSHALLER;
import static
org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MARSHALLER_COMPACT_FOOTER;
import static
org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MARSHALLER_USE_BINARY_STRING_SER_VER_2;
import static
org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MARSHALLER_USE_DFLT_SUID;
+import static
org.apache.ignite.internal.cluster.DistributedConfigurationUtils.CONN_DISABLED_BY_ADMIN_ERR_MSG;
+import static
org.apache.ignite.internal.cluster.DistributedConfigurationUtils.newConnectionEnabledProperty;
import static
org.apache.ignite.internal.processors.security.SecurityUtils.authenticateLocalNode;
import static
org.apache.ignite.internal.processors.security.SecurityUtils.withSecurityContext;
import static org.apache.ignite.spi.IgnitePortProtocol.TCP;
@@ -297,6 +300,12 @@ class ServerImpl extends TcpDiscoveryImpl {
private final ConcurrentMap<InetSocketAddress,
GridPingFutureAdapter<IgniteBiTuple<UUID, Boolean>>> pingMap =
new ConcurrentHashMap<>();
+ /** Client node connection allowed property. */
+ private final DistributedBooleanProperty cliConnEnabled;
+
+ /** Server node connection allowed property. */
+ private final DistributedBooleanProperty srvConnEnabled;
+
/**
* Maximum size of history of IDs of server nodes ever tried to join
current topology (ever sent join request).
*/
@@ -319,6 +328,16 @@ class ServerImpl extends TcpDiscoveryImpl {
utilityPoolSize,
2000,
new LinkedBlockingQueue<>());
+
+ List<DistributedBooleanProperty> props = newConnectionEnabledProperty(
+ ((IgniteEx)spi.ignite()).context().internalSubscriptionProcessor(),
+ log,
+ "ClientNode",
+ "ServerNode"
+ );
+
+ cliConnEnabled = props.get(0);
+ srvConnEnabled = props.get(1);
}
/** {@inheritDoc} */
@@ -4303,7 +4322,9 @@ class ServerImpl extends TcpDiscoveryImpl {
}
}
- if (spi.nodeAuth != null) {
+ IgniteNodeValidationResult err = ensureJoinEnabled(node);
+
+ if (spi.nodeAuth != null && err == null) {
// Authenticate node first.
try {
SecurityCredentials cred = unmarshalCredentials(node);
@@ -4405,9 +4426,8 @@ class ServerImpl extends TcpDiscoveryImpl {
}
}
- IgniteNodeValidationResult err;
-
- err = spi.getSpiContext().validateNode(node);
+ if (err == null)
+ err = spi.getSpiContext().validateNode(node);
if (err == null) {
try {
@@ -6422,6 +6442,16 @@ class ServerImpl extends TcpDiscoveryImpl {
}
}
+ /**
+ * @param node Node to connect.
+ * @return {@code null} if connection allowed, error otherwise.
+ */
+ private IgniteNodeValidationResult ensureJoinEnabled(TcpDiscoveryNode
node) {
+ return (node.isClient() ? cliConnEnabled :
srvConnEnabled).getOrDefault(true)
+ ? null
+ : new IgniteNodeValidationResult(node.id(),
CONN_DISABLED_BY_ADMIN_ERR_MSG);
+ }
+
/**
* Creates proper timeout helper taking in account current send state and
ring state.
*
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ConnectionEnabledPropertyTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ConnectionEnabledPropertyTest.java
new file mode 100644
index 00000000000..a9ca46d5176
--- /dev/null
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ConnectionEnabledPropertyTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache;
+
+import java.util.Arrays;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.util.lang.RunnableX;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static
org.apache.ignite.internal.processors.configuration.distributed.DistributedConfigurationProcessor.toMetaStorageKey;
+import static
org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+
+/** */
+@RunWith(Parameterized.class)
+public class ConnectionEnabledPropertyTest extends GridCommonAbstractTest {
+ /** */
+ private static final String SRV_CONN_ENABLED_PROP =
"newServerNodeConnectionsEnabled";
+
+ /** */
+ private static final String CLI_CONN_ENABLED_PROP =
"newClientNodeConnectionsEnabled";
+
+ /** */
+ @Parameterized.Parameter
+ public boolean persistence;
+
+ /** */
+ @Parameterized.Parameters(name = "persistence={0}")
+ public static Iterable<Boolean> parameters() {
+ return Arrays.asList(true, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ if (persistence) {
+ cfg.setDataStorageConfiguration(new DataStorageConfiguration());
+
cfg.getDataStorageConfiguration().getDefaultDataRegionConfiguration().setPersistenceEnabled(true);
+ }
+
+ return cfg;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ stopAllGrids();
+
+ cleanPersistenceDir();
+ }
+
+ /** */
+ @Test
+ public void testConnectionEnabledProperty() throws Exception {
+ RunnableX srvCanJoin = () -> {
+ try (Ignite srv1 = startGrid(1)) {
+ assertNotNull(srv1.cacheNames());
+ }
+ };
+
+ RunnableX cliCanJoin = () -> {
+ try (Ignite cli = startClientGrid(2)) {
+ assertNotNull(cli.cacheNames());
+ }
+ };
+
+ // Two iteration to check successfull restart when
newServerNodeConnectionsEnabled=false.
+ for (int i = 0; i < 2; i++) {
+ try (IgniteEx srv = startGrid(0)) {
+ srv.cluster().state(ClusterState.ACTIVE);
+
+ if (persistence && i == 1) {
+
assertFalse(srv.context().distributedMetastorage().read(toMetaStorageKey(SRV_CONN_ENABLED_PROP)));
+
assertFalse(srv.context().distributedMetastorage().read(toMetaStorageKey(CLI_CONN_ENABLED_PROP)));
+
+
srv.context().distributedMetastorage().write(toMetaStorageKey(SRV_CONN_ENABLED_PROP),
true);
+
srv.context().distributedMetastorage().write(toMetaStorageKey(CLI_CONN_ENABLED_PROP),
true);
+ }
+
+
assertTrue(srv.context().distributedMetastorage().read(toMetaStorageKey(SRV_CONN_ENABLED_PROP)));
+
assertTrue(srv.context().distributedMetastorage().read(toMetaStorageKey(CLI_CONN_ENABLED_PROP)));
+
+
srv.context().distributedMetastorage().write(toMetaStorageKey(SRV_CONN_ENABLED_PROP),
true);
+
srv.context().distributedMetastorage().write(toMetaStorageKey(CLI_CONN_ENABLED_PROP),
true);
+
+ srvCanJoin.run();
+ cliCanJoin.run();
+
+
srv.context().distributedMetastorage().write(toMetaStorageKey(SRV_CONN_ENABLED_PROP),
false);
+
+ assertThrowsWithCause(srvCanJoin,
IgniteCheckedException.class);
+ cliCanJoin.run();
+
+
srv.context().distributedMetastorage().write(toMetaStorageKey(CLI_CONN_ENABLED_PROP),
false);
+
+ assertThrowsWithCause(srvCanJoin,
IgniteCheckedException.class);
+ assertThrowsWithCause(cliCanJoin,
IgniteCheckedException.class);
+
+
srv.context().distributedMetastorage().write(toMetaStorageKey(SRV_CONN_ENABLED_PROP),
true);
+
+ srvCanJoin.run();
+ assertThrowsWithCause(cliCanJoin,
IgniteCheckedException.class);
+
+
srv.context().distributedMetastorage().write(toMetaStorageKey(CLI_CONN_ENABLED_PROP),
true);
+
+ srvCanJoin.run();
+ cliCanJoin.run();
+
+ if (persistence) {
+
srv.context().distributedMetastorage().write(toMetaStorageKey(SRV_CONN_ENABLED_PROP),
false);
+
srv.context().distributedMetastorage().write(toMetaStorageKey(CLI_CONN_ENABLED_PROP),
false);
+
+ assertTrue(waitForCondition(() -> {
+ try {
+ return
!srv.context().distributedMetastorage().<Boolean>read(toMetaStorageKey(SRV_CONN_ENABLED_PROP))
&&
+
!srv.context().distributedMetastorage().<Boolean>read(toMetaStorageKey(CLI_CONN_ENABLED_PROP));
+ }
+ catch (IgniteCheckedException e) {
+ throw new RuntimeException(e);
+ }
+ }, 30_000));
+ }
+ }
+ }
+ }
+}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckSecurityTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckSecurityTest.java
index b9efea94ae6..815f9035e2f 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckSecurityTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckSecurityTest.java
@@ -49,4 +49,9 @@ public class ThinClientPermissionCheckSecurityTest extends
ThinClientPermissionC
@Override protected Map<String, String> userAttributres() {
return new UserAttributesFactory().create();
}
+
+ /** {@inheritDoc} */
+ @Override public void testConnectAsManagementClient() {
+ // No-op.
+ }
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckTest.java
index ec26bf5763e..e6d5a952515 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientPermissionCheckTest.java
@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableSet;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteException;
import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientAuthenticationException;
import org.apache.ignite.client.ClientAuthorizationException;
import org.apache.ignite.client.ClientCache;
import org.apache.ignite.client.ClientException;
@@ -55,6 +56,7 @@ import
org.apache.ignite.internal.processors.security.AbstractTestSecurityPlugin
import org.apache.ignite.internal.processors.security.impl.TestSecurityData;
import
org.apache.ignite.internal.processors.security.impl.TestSecurityPluginProvider;
import org.apache.ignite.internal.util.lang.RunnableX;
+import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.lang.IgniteBiTuple;
@@ -66,13 +68,18 @@ import org.junit.runners.JUnit4;
import static java.util.Collections.singletonMap;
import static org.apache.ignite.configuration.DataPageEvictionMode.RANDOM_LRU;
import static org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_EXPIRED;
+import static
org.apache.ignite.internal.cluster.DistributedConfigurationUtils.CONN_DISABLED_BY_ADMIN_ERR_MSG;
+import static
org.apache.ignite.internal.processors.configuration.distributed.DistributedConfigurationProcessor.toMetaStorageKey;
+import static
org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.MANAGEMENT_CLIENT_ATTR;
import static org.apache.ignite.internal.util.lang.GridFunc.t;
+import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_OPS;
import static
org.apache.ignite.plugin.security.SecurityPermission.CACHE_CREATE;
import static
org.apache.ignite.plugin.security.SecurityPermission.CACHE_DESTROY;
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.CACHE_REMOVE;
import static
org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALL_PERMISSIONS;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
import static
org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
/**
@@ -95,6 +102,9 @@ public class ThinClientPermissionCheckTest extends
AbstractSecurityTest {
/** Client that has system permissions. */
private static final String CLIENT_SYS_PERM = "client_sys_perm";
+ /** Client that has admin permissions. */
+ private static final String ADMIN = "admin";
+
/** Cache. */
protected static final String CACHE = "TEST_CACHE";
@@ -116,6 +126,12 @@ public class ThinClientPermissionCheckTest extends
AbstractSecurityTest {
/** Size of the data region for object eviction testing. */
protected static final int EVICTION_TEST_DATA_REGION_SIZE = 20 * (1 << 20);
+ /** */
+ private static final String THIN_CONN_ENABLED_PROP =
"newThinConnectionsEnabled";
+
+ /** */
+ private Map<String, String> userAttrs;
+
/**
* @param clientData Array of client security data.
*/
@@ -198,6 +214,11 @@ public class ThinClientPermissionCheckTest extends
AbstractSecurityTest {
SecurityPermissionSetBuilder.create().defaultAllowAll(false)
.appendSystemPermissions(CACHE_CREATE, CACHE_DESTROY)
.build()
+ ),
+ new TestSecurityData(ADMIN,
+
SecurityPermissionSetBuilder.create().defaultAllowAll(false)
+ .appendSystemPermissions(ADMIN_OPS)
+ .build()
)
)
);
@@ -366,6 +387,64 @@ public class ThinClientPermissionCheckTest extends
AbstractSecurityTest {
}
}
+ /** */
+ @Test
+ public void testConnectAsManagementClient() throws Exception {
+ Runnable cliCanConnect = () -> {
+ try (IgniteClient cli = startClient(CLIENT)) {
+ assertNotNull("Cach query from CLIENT", cli.cacheNames());
+ }
+ };
+
+ Runnable adminCanConnect = () -> {
+ try (IgniteClient cli = startClient(ADMIN)) {
+ assertNotNull("Cach query from CLIENT", cli.cacheNames());
+ }
+ };
+
+ Runnable withUserAttrsCheck = () -> {
+ userAttrs = F.asMap(MANAGEMENT_CLIENT_ATTR, "true");
+
+ try {
+ // Trying to connect as CLIENT with "management client" flag
must fail, because of security.
+ // CLIENT has no ADMIN_OPS permission.
+ assertThrows(log, () -> startClient(CLIENT),
ClientAuthenticationException.class, "ADMIN_OPS permission required");
+
+ adminCanConnect.run();
+ }
+ finally {
+ userAttrs = null;
+ }
+ };
+
+ Runnable checkDflt = () -> {
+ cliCanConnect.run();
+ adminCanConnect.run();
+
+ withUserAttrsCheck.run();
+ };
+
+ checkDflt.run();
+
+
assertTrue(grid(0).context().distributedMetastorage().read(toMetaStorageKey(THIN_CONN_ENABLED_PROP)));
+
+ // Disable all new thin client connections except ADMIN_OPS control.sh
+
grid(0).context().distributedMetastorage().write(toMetaStorageKey(THIN_CONN_ENABLED_PROP),
false);
+
+ try {
+ assertThrows(log, () -> startClient(CLIENT),
ClientAuthenticationException.class, CONN_DISABLED_BY_ADMIN_ERR_MSG);
+ // Trying to connect without specifying "management client" flag
must fail.
+ assertThrows(log, () -> startClient(ADMIN),
ClientAuthenticationException.class, CONN_DISABLED_BY_ADMIN_ERR_MSG);
+
+ withUserAttrsCheck.run();
+ }
+ finally {
+
grid(0).context().distributedMetastorage().write(toMetaStorageKey(THIN_CONN_ENABLED_PROP),
true);
+ }
+
+ checkDflt.run();
+ }
+
/**
* Gets all operations.
*
@@ -451,7 +530,7 @@ public class ThinClientPermissionCheckTest extends
AbstractSecurityTest {
* @return User attributes.
*/
protected Map<String, String> userAttributres() {
- return null;
+ return userAttrs;
}
/** */
diff --git
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/IgniteMock.java
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/IgniteMock.java
index f308f61a89e..1d62058a174 100644
---
a/modules/core/src/test/java/org/apache/ignite/testframework/junits/IgniteMock.java
+++
b/modules/core/src/test/java/org/apache/ignite/testframework/junits/IgniteMock.java
@@ -72,6 +72,7 @@ import
org.apache.ignite.internal.processors.cache.GridCacheUtilityKey;
import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
import
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext;
import org.apache.ignite.internal.processors.cacheobject.NoOpBinary;
+import
org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
import
org.apache.ignite.internal.processors.tracing.configuration.NoopTracingConfigurationManager;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiTuple;
@@ -142,7 +143,11 @@ public class IgniteMock implements IgniteEx {
this.staticCfg = staticCfg;
try {
- kernalCtx = new StandaloneGridKernalContext(new
GridTestLog4jLogger(), null, null);
+ kernalCtx = new StandaloneGridKernalContext(new
GridTestLog4jLogger(), null, null) {
+ @Override public GridInternalSubscriptionProcessor
internalSubscriptionProcessor() {
+ return new GridInternalSubscriptionProcessor(this);
+ }
+ };
}
catch (IgniteCheckedException e) {
throw new IgniteException(e);
diff --git
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite8.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite8.java
index c8a8ae67bee..87743fe7c37 100644
---
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite8.java
+++
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite8.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.ignite.cache.CircledRebalanceTest;
+import
org.apache.ignite.internal.processors.cache.ConnectionEnabledPropertyTest;
import
org.apache.ignite.internal.processors.cache.distributed.rebalancing.CacheRebalanceWithRemovedWalSegment;
import
org.apache.ignite.internal.processors.cache.distributed.rebalancing.SupplyPartitionHistoricallyWithReorderedUpdates;
import
org.apache.ignite.internal.processors.cache.expiry.ActivationOnExpirationTimeoutTest;
@@ -105,6 +106,8 @@ public class IgnitePdsTestSuite8 {
GridTestUtils.addTestIfNeeded(suite,
PagesPossibleCorruptionDiagnosticTest.class, ignoredTests);
GridTestUtils.addTestIfNeeded(suite,
MaintenancePersistenceTaskTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite,
ConnectionEnabledPropertyTest.class, ignoredTests);
+
return suite;
}