Skip to content

Commit 5590b01

Browse files
authored
IGNITE-24076 Distributed properties to block new connections (#11766)
1 parent 319250b commit 5590b01

File tree

13 files changed

+501
-12
lines changed

13 files changed

+501
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal.processors.query.calcite.jdbc;
19+
20+
import java.sql.Connection;
21+
import java.sql.DriverManager;
22+
import java.sql.SQLException;
23+
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
24+
import org.apache.ignite.configuration.IgniteConfiguration;
25+
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
26+
import org.junit.Test;
27+
28+
import static org.apache.ignite.internal.cluster.DistributedConfigurationUtils.CONN_DISABLED_BY_ADMIN_ERR_MSG;
29+
import static org.apache.ignite.internal.processors.configuration.distributed.DistributedConfigurationProcessor.toMetaStorageKey;
30+
import static org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcThinTransactionalSelfTest.URL;
31+
import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
32+
33+
/** */
34+
public class JdbcConnectionEnabledPropertyTest extends GridCommonAbstractTest {
35+
/** */
36+
private static final String JDBC_CONN_ENABLED_PROP = "newJdbcConnectionsEnabled";
37+
38+
/** {@inheritDoc} */
39+
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
40+
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
41+
42+
cfg.getSqlConfiguration().setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration());
43+
44+
return cfg;
45+
}
46+
47+
/** {@inheritDoc} */
48+
@Override protected void beforeTestsStarted() throws Exception {
49+
super.beforeTestsStarted();
50+
51+
startGrid();
52+
}
53+
54+
/** */
55+
@Test
56+
public void testConnectionEnabledProperty() throws Exception {
57+
try (Connection conn = DriverManager.getConnection(URL)) {
58+
assertTrue(conn.isValid(3));
59+
}
60+
61+
grid().context().distributedMetastorage().write(toMetaStorageKey(JDBC_CONN_ENABLED_PROP), false);
62+
63+
assertThrows(log, () -> DriverManager.getConnection(URL), SQLException.class, CONN_DISABLED_BY_ADMIN_ERR_MSG);
64+
65+
grid().context().distributedMetastorage().write(toMetaStorageKey(JDBC_CONN_ENABLED_PROP), true);
66+
67+
try (Connection conn = DriverManager.getConnection(URL)) {
68+
assertTrue(conn.isValid(3));
69+
}
70+
}
71+
}

modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcThinTransactionalSelfTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
/** */
5050
public class JdbcThinTransactionalSelfTest extends GridCommonAbstractTest {
5151
/** URL. */
52-
private static final String URL = "jdbc:ignite:thin://127.0.0.1";
52+
public static final String URL = "jdbc:ignite:thin://127.0.0.1";
5353

5454
/** {@inheritDoc} */
5555
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {

modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.apache.ignite.internal.processors.query.calcite.exec.NumericTypesPrecisionsTest;
2525
import org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteSqlFunctionsTest;
2626
import org.apache.ignite.internal.processors.query.calcite.exec.tracker.MemoryTrackerTest;
27+
import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcConnectionEnabledPropertyTest;
2728
import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcSetClientInfoTest;
2829
import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcThinTransactionalSelfTest;
2930
import org.apache.ignite.internal.processors.query.calcite.message.CalciteCommunicationMessageSerializationTest;
@@ -62,6 +63,7 @@
6263

6364
JdbcThinTransactionalSelfTest.class,
6465
JdbcSetClientInfoTest.class,
66+
JdbcConnectionEnabledPropertyTest.class
6567
})
6668
public class IgniteCalciteTestSuite {
6769
}

modules/core/src/main/java/org/apache/ignite/internal/cluster/DistributedConfigurationUtils.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@
1818
package org.apache.ignite.internal.cluster;
1919

2020
import java.io.Serializable;
21+
import java.util.Arrays;
22+
import java.util.List;
2123
import java.util.Objects;
24+
import java.util.stream.Collectors;
2225
import org.apache.ignite.IgniteCheckedException;
2326
import org.apache.ignite.IgniteLogger;
2427
import org.apache.ignite.internal.IgniteInternalFuture;
2528
import org.apache.ignite.internal.processors.configuration.distributed.DistributePropertyListener;
29+
import org.apache.ignite.internal.processors.configuration.distributed.DistributedBooleanProperty;
30+
import org.apache.ignite.internal.processors.configuration.distributed.DistributedConfigurationLifecycleListener;
2631
import org.apache.ignite.internal.processors.configuration.distributed.DistributedProperty;
32+
import org.apache.ignite.internal.processors.configuration.distributed.DistributedPropertyDispatcher;
33+
import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
2734
import org.apache.ignite.internal.util.future.GridFinishedFuture;
2835
import org.jetbrains.annotations.NotNull;
2936

@@ -33,6 +40,9 @@
3340
* Distributed configuration utilities methods.
3441
*/
3542
public final class DistributedConfigurationUtils {
43+
/** */
44+
public static final String CONN_DISABLED_BY_ADMIN_ERR_MSG = "Connection disabled by administrator";
45+
3646
/**
3747
*/
3848
private DistributedConfigurationUtils() {
@@ -95,4 +105,39 @@ public static <T extends Serializable> IgniteInternalFuture<Void> setDefaultValu
95105
}
96106
};
97107
}
108+
109+
/**
110+
* Creates and registers distributed properties to enable connection by type.
111+
* @param subscriptionProcessor Processor to register properties.
112+
* @param log Logger to log default values.
113+
* @param types Connection types.
114+
* @return Detached distributed property.
115+
*/
116+
public static List<DistributedBooleanProperty> newConnectionEnabledProperty(
117+
GridInternalSubscriptionProcessor subscriptionProcessor,
118+
IgniteLogger log,
119+
String... types
120+
) {
121+
List<DistributedBooleanProperty> props = Arrays.stream(types).map(type -> DistributedBooleanProperty.detachedBooleanProperty(
122+
"new" + type + "ConnectionsEnabled",
123+
"If true then new " + type.toUpperCase() + " connections allowed."
124+
)).collect(Collectors.toList());
125+
126+
subscriptionProcessor.registerDistributedConfigurationListener(new DistributedConfigurationLifecycleListener() {
127+
@Override public void onReadyToRegister(DistributedPropertyDispatcher dispatcher) {
128+
props.forEach(p -> {
129+
if (dispatcher.property(p.getName()) != null)
130+
return;
131+
132+
dispatcher.registerProperty(p);
133+
});
134+
}
135+
136+
@Override public void onReadyToWrite() {
137+
props.forEach(prop -> setDefaultValue(prop, true, log));
138+
}
139+
});
140+
141+
return props;
142+
}
98143
}

modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.apache.ignite.internal.util.nio.GridNioSession;
2525
import org.jetbrains.annotations.Nullable;
2626

27+
import static org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.MANAGEMENT_CLIENT_ATTR;
28+
2729
/**
2830
* SQL listener connection context.
2931
*/
@@ -88,4 +90,11 @@ void initializeFromHandshake(GridNioSession ses, ClientListenerProtocolVersion v
8890
* Connection attributes.
8991
*/
9092
Map<String, String> attributes();
93+
94+
/**
95+
* @return {@code True} if client is management.
96+
*/
97+
default boolean managementClient() {
98+
return Boolean.parseBoolean(attributes().get(MANAGEMENT_CLIENT_ATTR));
99+
}
91100
}

modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.Closeable;
2121
import java.util.concurrent.atomic.AtomicInteger;
22+
import java.util.function.Predicate;
2223
import org.apache.ignite.IgniteCheckedException;
2324
import org.apache.ignite.IgniteLogger;
2425
import org.apache.ignite.configuration.ClientConnectorConfiguration;
@@ -47,8 +48,11 @@
4748
import org.apache.ignite.internal.util.nio.GridNioSession;
4849
import org.apache.ignite.internal.util.nio.GridNioSessionMetaKey;
4950
import org.apache.ignite.internal.util.typedef.internal.U;
51+
import org.apache.ignite.plugin.security.SecurityException;
52+
import org.apache.ignite.plugin.security.SecurityPermission;
5053
import org.jetbrains.annotations.Nullable;
5154

55+
import static org.apache.ignite.internal.cluster.DistributedConfigurationUtils.CONN_DISABLED_BY_ADMIN_ERR_MSG;
5256
import static org.apache.ignite.internal.processors.odbc.ClientListenerMetrics.clientTypeLabel;
5357

5458
/**
@@ -103,19 +107,31 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<Clie
103107
/** Metrics. */
104108
private final ClientListenerMetrics metrics;
105109

110+
/**
111+
* If return {@code true} then specifi protocol connections enabled.
112+
* Predicate checks distributed property value.
113+
*
114+
* @see ClientListenerNioListener#ODBC_CLIENT
115+
* @see ClientListenerNioListener#JDBC_CLIENT
116+
* @see ClientListenerNioListener#THIN_CLIENT
117+
*/
118+
private final Predicate<Byte> newConnEnabled;
119+
106120
/**
107121
* Constructor.
108122
*
109123
* @param ctx Context.
110124
* @param busyLock Shutdown busy lock.
111125
* @param cliConnCfg Client connector configuration.
112126
* @param metrics Client listener metrics.
127+
* @param newConnEnabled Predicate to check if connection of specified type enabled.
113128
*/
114129
public ClientListenerNioListener(
115130
GridKernalContext ctx,
116131
GridSpinBusyLock busyLock,
117132
ClientConnectorConfiguration cliConnCfg,
118-
ClientListenerMetrics metrics
133+
ClientListenerMetrics metrics,
134+
Predicate<Byte> newConnEnabled
119135
) {
120136
assert cliConnCfg != null;
121137

@@ -126,10 +142,12 @@ public ClientListenerNioListener(
126142
maxCursors = cliConnCfg.getMaxOpenCursorsPerConnection();
127143
log = ctx.log(getClass());
128144

129-
thinCfg = cliConnCfg.getThinClientConfiguration() == null ? new ThinClientConfiguration()
145+
thinCfg = cliConnCfg.getThinClientConfiguration() == null
146+
? new ThinClientConfiguration()
130147
: new ThinClientConfiguration(cliConnCfg.getThinClientConfiguration());
131148

132149
this.metrics = metrics;
150+
this.newConnEnabled = newConnEnabled;
133151
}
134152

135153
/** {@inheritDoc} */
@@ -380,8 +398,7 @@ private void onHandshake(GridNioSession ses, ClientMessage msg) {
380398
if (connCtx.isVersionSupported(ver)) {
381399
connCtx.initializeFromHandshake(ses, ver, reader);
382400

383-
if (nodeInRecoveryMode() && !Boolean.parseBoolean(connCtx.attributes().get(MANAGEMENT_CLIENT_ATTR)))
384-
throw new ClientConnectionNodeRecoveryException("Node in recovery mode.");
401+
ensureConnectionAllowed(connCtx);
385402

386403
ses.addMeta(CONN_CTX_META_KEY, connCtx);
387404
}
@@ -534,4 +551,40 @@ private void ensureClientPermissions(byte clientType) throws IgniteCheckedExcept
534551
throw new IgniteCheckedException("Unknown client type: " + clientType);
535552
}
536553
}
554+
555+
/**
556+
* Ensures if the client are allowed to connect.
557+
*
558+
* @param connCtx Connection context.
559+
* @throws IgniteCheckedException If failed.
560+
*/
561+
private void ensureConnectionAllowed(ClientListenerConnectionContext connCtx) throws IgniteCheckedException {
562+
boolean isControlUtility = connCtx.clientType() == THIN_CLIENT && connCtx.managementClient();
563+
564+
if (nodeInRecoveryMode()) {
565+
if (!isControlUtility)
566+
throw new ClientConnectionNodeRecoveryException("Node in recovery mode.");
567+
568+
return;
569+
}
570+
571+
// If security enabled then only admin allowed to connect as management.
572+
if (isControlUtility) {
573+
if (connCtx.securityContext() != null) {
574+
try (OperationSecurityContext ignored = ctx.security().withContext(connCtx.securityContext())) {
575+
ctx.security().authorize(SecurityPermission.ADMIN_OPS);
576+
}
577+
catch (SecurityException e) {
578+
throw new IgniteAccessControlException("ADMIN_OPS permission required");
579+
}
580+
}
581+
582+
// Allow to connect control utility even if connection disabled.
583+
// Must provide a way to invoke commands.
584+
return;
585+
}
586+
587+
if (!newConnEnabled.test(connCtx.clientType()))
588+
throw new IgniteAccessControlException(CONN_DISABLED_BY_ADMIN_ERR_MSG);
589+
}
537590
}

modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerProcessor.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
import java.util.ArrayList;
2424
import java.util.Collection;
2525
import java.util.Collections;
26+
import java.util.HashMap;
2627
import java.util.List;
2728
import java.util.Map;
2829
import java.util.concurrent.ExecutorService;
2930
import java.util.concurrent.atomic.AtomicLong;
3031
import java.util.function.Function;
32+
import java.util.function.Predicate;
3133
import javax.cache.configuration.Factory;
3234
import javax.management.JMException;
3335
import javax.management.ObjectName;
@@ -42,6 +44,7 @@
4244
import org.apache.ignite.internal.managers.systemview.walker.ClientConnectionAttributeViewWalker;
4345
import org.apache.ignite.internal.managers.systemview.walker.ClientConnectionViewWalker;
4446
import org.apache.ignite.internal.processors.GridProcessorAdapter;
47+
import org.apache.ignite.internal.processors.configuration.distributed.DistributedBooleanProperty;
4548
import org.apache.ignite.internal.processors.configuration.distributed.DistributedThinClientConfiguration;
4649
import org.apache.ignite.internal.processors.metric.MetricRegistryImpl;
4750
import org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext;
@@ -66,11 +69,15 @@
6669
import org.jetbrains.annotations.NotNull;
6770
import org.jetbrains.annotations.Nullable;
6871

72+
import static org.apache.ignite.internal.cluster.DistributedConfigurationUtils.newConnectionEnabledProperty;
6973
import static org.apache.ignite.internal.processors.metric.GridMetricManager.CLIENT_CONNECTOR_METRICS;
7074
import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName;
7175
import static org.apache.ignite.internal.processors.odbc.ClientListenerMetrics.clientTypeLabel;
7276
import static org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.CLI_TYPES;
7377
import static org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.CONN_CTX_META_KEY;
78+
import static org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.JDBC_CLIENT;
79+
import static org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.ODBC_CLIENT;
80+
import static org.apache.ignite.internal.processors.odbc.ClientListenerNioListener.THIN_CLIENT;
7481

7582
/**
7683
* Client connector processor.
@@ -177,12 +184,14 @@ public ClientListenerProcessor(GridKernalContext ctx) {
177184
? this::onOutboundMessageOffered
178185
: null;
179186

187+
Predicate<Byte> newConnEnabled = connectionEnabledPredicate();
188+
180189
for (int port = cliConnCfg.getPort(); port <= portTo && port <= 65535; port++) {
181190
try {
182191
srv = GridNioServer.<ClientMessage>builder()
183192
.address(hostAddr)
184193
.port(port)
185-
.listener(new ClientListenerNioListener(ctx, busyLock, cliConnCfg, metrics))
194+
.listener(new ClientListenerNioListener(ctx, busyLock, cliConnCfg, metrics, newConnEnabled))
186195
.logger(log)
187196
.selectorCount(selectorCnt)
188197
.igniteInstanceName(ctx.igniteInstanceName())
@@ -248,6 +257,35 @@ public ClientListenerProcessor(GridKernalContext ctx) {
248257
}
249258
}
250259

260+
/**
261+
* @return Predicate to check is connection for specific client type enabled.
262+
* @see ClientListenerNioListener#ODBC_CLIENT
263+
* @see ClientListenerNioListener#JDBC_CLIENT
264+
* @see ClientListenerNioListener#THIN_CLIENT
265+
*/
266+
private Predicate<Byte> connectionEnabledPredicate() {
267+
Map<Byte, DistributedBooleanProperty> connEnabledMap = new HashMap<>();
268+
269+
List<DistributedBooleanProperty> props = newConnectionEnabledProperty(
270+
ctx.internalSubscriptionProcessor(),
271+
log,
272+
"Odbc",
273+
"Jdbc",
274+
"Thin"
275+
);
276+
277+
connEnabledMap.put(ODBC_CLIENT, props.get(0));
278+
connEnabledMap.put(JDBC_CLIENT, props.get(1));
279+
connEnabledMap.put(THIN_CLIENT, props.get(2));
280+
281+
return type -> {
282+
assert type != null : "Connection type is null";
283+
assert connEnabledMap.containsKey(type) : "Unknown connection type: " + type;
284+
285+
return connEnabledMap.get(type).getOrDefault(true);
286+
};
287+
}
288+
251289
/** */
252290
private Iterable<ClientConnectionAttributeView> connectionAttributeViewSupplier(Map<String, Object> filter) {
253291
Long connId = (Long)filter.get(ClientConnectionAttributeViewWalker.CONNECTION_ID_FILTER);

0 commit comments

Comments
 (0)