Skip to content

Commit

Permalink
IGNITE-24076 Introduce properties to disable client connections by pr…
Browse files Browse the repository at this point in the history
…otocol
  • Loading branch information
nizhikov committed Dec 23, 2024
1 parent 5a23206 commit 36fc206
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -33,6 +40,9 @@
* Distributed configuration utilities methods.
*/
public final class DistributedConfigurationUtils {
/** */
public static final String CONN_DISABLED_BY_ADMIN_ERR_MSG = "Connection disabled by administrator";

/**
*/
private DistributedConfigurationUtils() {
Expand Down Expand Up @@ -95,4 +105,34 @@ public static <T extends Serializable> IgniteInternalFuture<Void> setDefaultValu
}
};
}

/**
* @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> connectionAllowedProperty(
GridInternalSubscriptionProcessor subscriptionProcessor,
IgniteLogger log,
String... types
) {
List<DistributedBooleanProperty> props = Arrays.stream(types).map(type -> DistributedBooleanProperty.detachedBooleanProperty(
"allowNew" + type + "Connections",
"If true then new " + type.toUpperCase() + " connections allowed."
)).collect(Collectors.toList());

subscriptionProcessor.registerDistributedConfigurationListener(new DistributedConfigurationLifecycleListener() {
@Override public void onReadyToRegister(DistributedPropertyDispatcher dispatcher) {
props.forEach(dispatcher::registerProperty);
}

@Override public void onReadyToWrite() {
props.forEach(prop -> setDefaultValue(prop, true, log));
}
});


return props;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
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;

/**
Expand Down Expand Up @@ -557,12 +558,12 @@ private void ensureClientPermissions(ClientListenerConnectionContext connCtx) th
if (!connAllowed.test(connCtx.clientType())) {
// Allow to connect by the control.sh even if connection disabled to be able to invoke commands.
if (!controlShClient)
throw new IgniteCheckedException("Connection disabled by administrator");
throw new IgniteCheckedException(CONN_DISABLED_BY_ADMIN_ERR_MSG);

// TODO: checkme
// TODO: checkme in tests.
if (connCtx.securityContext() != null) {
try (OperationSecurityContext ignored = ctx.security().withContext(connCtx.securityContext())) {
ctx.security().authorize(SecurityPermission.CONNECT_AS_MAMAGEMENT_CLIENT);
ctx.security().authorize(SecurityPermission.ADMIN_OPS);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,10 @@
import org.apache.ignite.configuration.OdbcConfiguration;
import org.apache.ignite.configuration.SqlConnectorConfiguration;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.cluster.DistributedConfigurationUtils;
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.DistributedConfigurationLifecycleListener;
import org.apache.ignite.internal.processors.configuration.distributed.DistributedPropertyDispatcher;
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;
Expand All @@ -72,6 +69,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.apache.ignite.internal.cluster.DistributedConfigurationUtils.connectionAllowedProperty;
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;
Expand Down Expand Up @@ -186,21 +184,12 @@ public ClientListenerProcessor(GridKernalContext ctx) {
? this::onOutboundMessageOffered
: null;

Map<Byte, DistributedBooleanProperty> allowConnMap = registerDistributedProperties();

Predicate<Byte> connAllowed = type -> {
assert type != null : "Connection type is null";
assert allowConnMap.containsKey(type) : "Unknown connection type: " + type;

return allowConnMap.get(type).get() != Boolean.FALSE;
};

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, connAllowed))
.listener(new ClientListenerNioListener(ctx, busyLock, cliConnCfg, metrics, connectionAllowedPredicate()))
.logger(log)
.selectorCount(selectorCnt)
.igniteInstanceName(ctx.igniteInstanceName())
Expand Down Expand Up @@ -266,29 +255,33 @@ public ClientListenerProcessor(GridKernalContext ctx) {
}
}

private Map<Byte, DistributedBooleanProperty> registerDistributedProperties() {
/**
* @return Predicate to check is connection for specific client type allowed by administrator.
* @see ClientListenerNioListener#ODBC_CLIENT
* @see ClientListenerNioListener#JDBC_CLIENT
* @see ClientListenerNioListener#THIN_CLIENT
*/
private Predicate<Byte> connectionAllowedPredicate() {
Map<Byte, DistributedBooleanProperty> allowConnMap = new HashMap<>();

Function<String, DistributedBooleanProperty> prop = type -> DistributedBooleanProperty.detachedBooleanProperty(
"allowNew" + type + "Connections",
"If true then new " + type.toUpperCase() + " connections allowed. Default is true."
List<DistributedBooleanProperty> props = connectionAllowedProperty(
ctx.internalSubscriptionProcessor(),
log,
"Odbc",
"Jdbc",
"Thin"
);

allowConnMap.put(ODBC_CLIENT, prop.apply("Odbc"));
allowConnMap.put(JDBC_CLIENT, prop.apply("Jdbc"));
allowConnMap.put(THIN_CLIENT, prop.apply("Thin"));
allowConnMap.put(ODBC_CLIENT, props.get(0));
allowConnMap.put(JDBC_CLIENT, props.get(1));
allowConnMap.put(THIN_CLIENT, props.get(2));

ctx.internalSubscriptionProcessor().registerDistributedConfigurationListener(new DistributedConfigurationLifecycleListener() {
@Override public void onReadyToRegister(DistributedPropertyDispatcher dispatcher) {
allowConnMap.values().forEach(dispatcher::registerProperty);
}
return type -> {
assert type != null : "Connection type is null";
assert allowConnMap.containsKey(type) : "Unknown connection type: " + type;

@Override public void onReadyToWrite() {
allowConnMap.values().forEach(prop -> DistributedConfigurationUtils.setDefaultValue(prop, true, log));
}
});

return allowConnMap;
return allowConnMap.get(type).get() != Boolean.FALSE;
};
}

/** */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package org.apache.ignite.plugin.security;

import org.apache.ignite.cluster.ClusterState;
import org.apache.ignite.internal.processors.odbc.ClientListenerNioListener;
import org.jetbrains.annotations.Nullable;

/**
Expand Down Expand Up @@ -118,13 +117,7 @@ public enum SecurityPermission {
SQL_VIEW_CREATE,

/** Permission to execute DROP VIEW command. */
SQL_VIEW_DROP,

/**
* Connect as a management client.
* @see ClientListenerNioListener#MANAGEMENT_CLIENT_ATTR
*/
CONNECT_AS_MAMAGEMENT_CLIENT;
SQL_VIEW_DROP;

/** Enumerated values. */
private static final SecurityPermission[] VALS = values();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
import org.apache.ignite.internal.IgniteNodeAttributes;
import org.apache.ignite.internal.IgnitionEx;
import org.apache.ignite.internal.cluster.DistributedConfigurationUtils;
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;
Expand Down Expand Up @@ -177,6 +179,7 @@
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.processors.security.SecurityUtils.authenticateLocalNode;
import static org.apache.ignite.internal.processors.security.SecurityUtils.withSecurityContext;
import static org.apache.ignite.spi.IgnitePortProtocol.TCP;
Expand Down Expand Up @@ -297,6 +300,8 @@ class ServerImpl extends TcpDiscoveryImpl {
private final ConcurrentMap<InetSocketAddress, GridPingFutureAdapter<IgniteBiTuple<UUID, Boolean>>> pingMap =
new ConcurrentHashMap<>();

private DistributedBooleanProperty clientNodeConnectionAllowed;

/**
* Maximum size of history of IDs of server nodes ever tried to join current topology (ever sent join request).
*/
Expand Down Expand Up @@ -470,6 +475,12 @@ class ServerImpl extends TcpDiscoveryImpl {
/** {@inheritDoc} */
@Override public void onContextInitialized0(IgniteSpiContext spiCtx) throws IgniteSpiException {
spiCtx.registerPort(tcpSrvr.port, TCP);

clientNodeConnectionAllowed = DistributedConfigurationUtils.connectionAllowedProperty(
((IgniteEx)spi.ignite()).context().internalSubscriptionProcessor(),
log,
"ClientNode"
).get(0);
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -4405,9 +4416,10 @@ else if (log.isDebugEnabled())
}
}

IgniteNodeValidationResult err;
IgniteNodeValidationResult err = node.isClient() ? ensureClientJoinAllowed(node.id()) : null;

err = spi.getSpiContext().validateNode(node);
if (err == null)
err = spi.getSpiContext().validateNode(node);

if (err == null) {
try {
Expand Down Expand Up @@ -6422,6 +6434,17 @@ private void checkConnection() {
}
}

/**
* @param nodeId Node id.
* @return {@code null} if connection allowed, error otherwise.
*/
private IgniteNodeValidationResult ensureClientJoinAllowed(UUID nodeId) {
if (clientNodeConnectionAllowed != null && clientNodeConnectionAllowed.get() == Boolean.FALSE)
return new IgniteNodeValidationResult(nodeId, CONN_DISABLED_BY_ADMIN_ERR_MSG);

return null;
}

/**
* Creates proper timeout helper taking in account current send state and ring state.
*
Expand Down

0 comments on commit 36fc206

Please sign in to comment.