This is an automated email from the ASF dual-hosted git repository.
namelchev pushed a commit to branch ignite-2.17
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/ignite-2.17 by this push:
new fdd19e159f7 IGNITE-23958 Fixed security context propagation for async
transactional operations (#11732)
fdd19e159f7 is described below
commit fdd19e159f7431b0652f8ef337d8cf5a6f1280b6
Author: Mikhail Petrov <[email protected]>
AuthorDate: Fri Dec 20 15:15:13 2024 +0300
IGNITE-23958 Fixed security context propagation for async transactional
operations (#11732)
(cherry picked from commit d547038712706b1246e60f38eca55074aec82943)
---
.../org/apache/ignite/IgniteSystemProperties.java | 12 ++
.../processors/cache/GridCacheAdapter.java | 28 ++-
.../platform/client/ClientRequestHandler.java | 15 +-
...curityContextInternalFuturePropagationTest.java | 231 +++++++++++++++++++++
.../ignite/testsuites/SecurityTestSuite.java | 2 +
5 files changed, 276 insertions(+), 12 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index ae4263392c8..843e13dbba2 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -132,6 +132,7 @@ import static
org.apache.ignite.internal.processors.performancestatistics.FilePe
import static
org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_CACHED_STRINGS_THRESHOLD;
import static
org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_FILE_MAX_SIZE;
import static
org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_FLUSH_SIZE;
+import static
org.apache.ignite.internal.processors.platform.client.ClientRequestHandler.DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS;
import static
org.apache.ignite.internal.processors.query.QueryUtils.DFLT_INDEXING_DISCOVERY_HISTORY_SIZE;
import static
org.apache.ignite.internal.processors.query.schema.SchemaIndexCachePartitionWorker.DFLT_IGNITE_INDEX_REBUILD_BATCH_SIZE;
import static
org.apache.ignite.internal.processors.rest.GridRestProcessor.DFLT_SES_TIMEOUT;
@@ -1319,6 +1320,17 @@ public final class IgniteSystemProperties {
defaults = "" + DFLT_JVM_PAUSE_DETECTOR_LAST_EVENTS_COUNT)
public static final String IGNITE_JVM_PAUSE_DETECTOR_LAST_EVENTS_COUNT =
"IGNITE_JVM_PAUSE_DETECTOR_LAST_EVENTS_COUNT";
+ /**
+ * Timeout in milliseconds that determines how long Ignite will
synchronously wait for asynchronous thin client
+ * requests to complete before releasing the thread.
+ */
+ @SystemProperty(
+ value = "Timeout in milliseconds that determines how long Ignite will
synchronously wait for" +
+ " asynchronous thin client requests to complete before releasing
the thread",
+ type = Long.class,
+ defaults = "" + DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS)
+ public static final String IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT
= "IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT";
+
/**
* Default value is {@code false}.
*
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
index c2ccd426c05..6843d1f3b08 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java
@@ -110,6 +110,8 @@ import
org.apache.ignite.internal.processors.datastreamer.DataStreamerImpl;
import
org.apache.ignite.internal.processors.dr.IgniteDrDataStreamerCacheUpdater;
import
org.apache.ignite.internal.processors.performancestatistics.OperationType;
import
org.apache.ignite.internal.processors.platform.cache.PlatformCacheEntryFilter;
+import org.apache.ignite.internal.processors.security.OperationSecurityContext;
+import org.apache.ignite.internal.processors.security.SecurityContext;
import org.apache.ignite.internal.processors.task.GridInternal;
import
org.apache.ignite.internal.transactions.IgniteTxHeuristicCheckedException;
import
org.apache.ignite.internal.transactions.IgniteTxRollbackCheckedException;
@@ -3850,24 +3852,30 @@ public abstract class GridCacheAdapter<K, V> implements
IgniteInternalCache<K, V
};
if (fut != null && !fut.isDone()) {
- IgniteInternalFuture<T> f = new GridEmbeddedFuture(fut,
- (IgniteOutClosure<IgniteInternalFuture>)() -> {
- GridFutureAdapter resFut = new GridFutureAdapter();
+ SecurityContext secCtx =
ctx.kernalContext().security().securityContext();
+
+ IgniteInternalFuture<T> f = new GridEmbeddedFuture<>(
+ fut,
+ (IgniteOutClosure<IgniteInternalFuture<T>>)() -> {
+ GridFutureAdapter<T> resFut = new
GridFutureAdapter<>();
ctx.kernalContext().closure().runLocalSafe((GridPlainRunnable)() -> {
- IgniteInternalFuture fut0;
+ IgniteInternalFuture<T> opFut;
if (ctx.kernalContext().isStopping())
- fut0 = new GridFinishedFuture<>(
+ opFut = new GridFinishedFuture<>(
new IgniteCheckedException("Operation has
been cancelled (node or cache is stopping)."));
else if (ctx.gate().isStopped())
- fut0 = new GridFinishedFuture<>(new
CacheStoppedException(ctx.name()));
+ opFut = new GridFinishedFuture<>(new
CacheStoppedException(ctx.name()));
else {
ctx.operationContextPerCall(opCtx);
ctx.shared().txContextReset();
- try {
- fut0 = op.op(tx0).chain(clo);
+ try (OperationSecurityContext ignored =
ctx.kernalContext().security().withContext(secCtx)) {
+ opFut = op.op(tx0).chain(clo);
+ }
+ catch (Throwable e) {
+ opFut = new GridFinishedFuture<>(e);
}
finally {
// It is necessary to clear tx context in
this thread as well.
@@ -3876,9 +3884,9 @@ public abstract class GridCacheAdapter<K, V> implements
IgniteInternalCache<K, V
}
}
- fut0.listen(() -> {
+ opFut.listen(lsnrFut -> {
try {
- resFut.onDone(fut0.get());
+ resFut.onDone(lsnrFut.get());
}
catch (Throwable ex) {
resFut.onDone(ex);
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
index 56b6636e296..6767011a3e5 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.platform.client;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteIllegalStateException;
import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.binary.BinaryWriterExImpl;
@@ -39,6 +40,7 @@ import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.plugin.security.SecurityException;
+import static
org.apache.ignite.IgniteSystemProperties.IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT;
import static
org.apache.ignite.internal.processors.platform.client.ClientProtocolVersionFeature.BITMAP_FEATURES;
import static
org.apache.ignite.internal.processors.platform.client.ClientProtocolVersionFeature.PARTITION_AWARENESS;
@@ -47,7 +49,13 @@ import static
org.apache.ignite.internal.processors.platform.client.ClientProtoc
*/
public class ClientRequestHandler implements ClientListenerRequestHandler {
/** Timeout to wait for async requests completion, to handle them as
regular sync requests. */
- private static final long ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS = 10L;
+ public static final long DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS = 10L;
+
+ /** */
+ private final long asyncReqWaitTimeout = IgniteSystemProperties.getLong(
+ IGNITE_THIN_CLIENT_ASYNC_REQUESTS_WAIT_TIMEOUT,
+ DFLT_ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS
+ );
/** Client context. */
private final ClientConnectionContext ctx;
@@ -122,10 +130,13 @@ public class ClientRequestHandler implements
ClientListenerRequestHandler {
if (req0.isAsync(ctx)) {
IgniteInternalFuture<ClientResponse> fut = req0.processAsync(ctx);
+ if (asyncReqWaitTimeout <= 0)
+ return new ClientAsyncResponse(req0.requestId(), fut);
+
try {
// Give request a chance to be executed and response processed
by the current thread,
// so we can avoid any performance drops caused by async
requests execution.
- return fut.get(ASYNC_REQUEST_WAIT_TIMEOUT_MILLIS);
+ return fut.get(asyncReqWaitTimeout);
}
catch (IgniteFutureTimeoutCheckedException ignored) {
return new ClientAsyncResponse(req0.requestId(), fut);
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/SecurityContextInternalFuturePropagationTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/SecurityContextInternalFuturePropagationTest.java
new file mode 100644
index 00000000000..2ab9eb496ca
--- /dev/null
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/SecurityContextInternalFuturePropagationTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.security;
+
+import java.security.Permissions;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import com.google.common.collect.ImmutableSet;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientAuthorizationException;
+import org.apache.ignite.client.ClientCache;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.IgniteClientFuture;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.ClientConnectorConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.TestRecordingCommunicationSpi;
+import
org.apache.ignite.internal.processors.cache.distributed.GridCacheModuloAffinityFunction;
+import
org.apache.ignite.internal.processors.cache.distributed.dht.preloader.GridDhtPartitionsSingleMessage;
+import org.apache.ignite.internal.processors.security.impl.TestSecurityData;
+import
org.apache.ignite.internal.processors.security.impl.TestSecurityPluginProvider;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.plugin.security.SecurityPermissionSet;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static java.util.Collections.singletonMap;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+import static
org.apache.ignite.internal.processors.cache.distributed.GridCacheModuloAffinityFunction.IDX_ATTR;
+import static
org.apache.ignite.plugin.security.SecurityPermission.ADMIN_CLUSTER_STATE;
+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.CACHE_REMOVE;
+import static
org.apache.ignite.plugin.security.SecurityPermission.JOIN_AS_SERVER;
+import static
org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.NO_PERMISSIONS;
+import static
org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.create;
+
+/** */
+@RunWith(Parameterized.class)
+public class SecurityContextInternalFuturePropagationTest extends
GridCommonAbstractTest {
+ /** */
+ private static final int PRELOADED_KEY_CNT = 100;
+
+ /** */
+ private static final AtomicInteger KEY_CNTR = new AtomicInteger();
+
+ /** */
+ @Parameterized.Parameter()
+ public Function<ClientCache<Object, Object>, IgniteClientFuture<?>> op;
+
+ /** */
+ @Parameterized.Parameters()
+ public static List<Function<ClientCache<Object, Object>,
IgniteClientFuture<?>>> data() {
+ return Arrays.asList(
+ cache -> cache.getAllAsync(ImmutableSet.of(nextKey(), nextKey())),
// 0
+ cache -> cache.getAndPutAsync(nextKey(), 0), // 1
+ cache -> cache.getAndPutIfAbsentAsync(nextKey(), 0), // 2
+ cache -> cache.getAndPutIfAbsentAsync(PRELOADED_KEY_CNT +
nextKey(), 0), // 3
+ cache -> cache.getAndRemoveAsync(nextKey()), // 4
+ cache -> cache.getAndReplaceAsync(nextKey(), 0), // 5
+ cache -> cache.getAsync(nextKey()), // 6
+ cache -> cache.putAllAsync(new HashMap<>() {{ put(nextKey(), 0);
put(nextKey(), 0); }}), // 7
+ cache -> cache.putAsync(nextKey(), 0), // 8
+ cache -> cache.putIfAbsentAsync(PRELOADED_KEY_CNT + nextKey(), 0),
// 9
+ cache -> cache.removeAsync(nextKey()), // 10
+ cache -> {
+ int key = nextKey();
+ return cache.removeAsync(key, key);
+ }, // 11
+ cache -> cache.replaceAsync(nextKey(), 0), // 12
+ cache -> {
+ int key = nextKey();
+
+ return cache.replaceAsync(key, key, 0);
+ } // 13
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setCommunicationSpi(new TestRecordingCommunicationSpi())
+ .setUserAttributes(singletonMap(IDX_ATTR,
getTestIgniteInstanceIndex(igniteInstanceName)))
+ .setClientConnectorConfiguration(new ClientConnectorConfiguration()
+ .setThreadPoolSize(1))
+ .setPluginProviders(new TestSecurityPluginProvider(
+ igniteInstanceName,
+ "",
+ create()
+ .defaultAllowAll(false)
+ .appendSystemPermissions(JOIN_AS_SERVER,
ADMIN_CLUSTER_STATE)
+ .appendCachePermissions(DEFAULT_CACHE_NAME, CACHE_CREATE)
+ .build(),
+ null,
+ false,
+ userData("forbidden_client", NO_PERMISSIONS),
+ userData("allowed_client", create()
+ .defaultAllowAll(false)
+ .appendCachePermissions(DEFAULT_CACHE_NAME, CACHE_READ,
CACHE_PUT, CACHE_REMOVE)
+ .build())
+ ));
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ super.afterTest();
+
+ stopAllGrids();
+ }
+
+ /** */
+ @Test
+ public void testSecurityContextInternalFuturePropagation() throws
Exception {
+ IgniteEx ignite = startGrids(2);
+
+ prepareCache(ignite);
+
+ TestRecordingCommunicationSpi spi =
TestRecordingCommunicationSpi.spi(grid(1));
+
+ spi.blockMessages(GridDhtPartitionsSingleMessage.class, ignite.name());
+
+ IgniteInternalFuture<IgniteEx> joinFut = GridTestUtils.runAsync(() ->
startGrid(2));
+
+ spi.waitForBlocked();
+
+ try (
+ IgniteClient allowedCli = startClient("allowed_client");
+ IgniteClient forbiddenCli = startClient("forbidden_client")
+ ) {
+ ClientCache<Object, Object> allowedCliCache =
allowedCli.cache(DEFAULT_CACHE_NAME);
+ ClientCache<Object, Object> forbiddenCliCache =
forbiddenCli.cache(DEFAULT_CACHE_NAME);
+
+ IgniteClientFuture<?> op0Fut = op.apply(allowedCliCache);
+
+ // The following operation previously was executed with security
context associated with the joining node user.
+ IgniteClientFuture<?> op1Fut = op.apply(allowedCliCache);
+
+ // Simulates failure of a chained operation.
+ IgniteClientFuture<?> op2Fut = op.apply(forbiddenCliCache);
+
+ // The failure of one operation in a chain should not leave the
entire chain in a broken state.
+ IgniteClientFuture<?> op3Fut = op.apply(allowedCliCache);
+
+ spi.stopBlock();
+
+ joinFut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+ op0Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+ op1Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+ try {
+ op2Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+ fail();
+ }
+ catch (Exception e) {
+ assertTrue(X.hasCause(e, "Authorization failed",
ClientException.class)
+ || X.hasCause(e, "User is not authorized to perform this
operation", ClientAuthorizationException.class));
+ }
+
+ op3Fut.get(getTestTimeout(), TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /** */
+ private void prepareCache(IgniteEx ignite) throws Exception {
+ ignite.createCache(new CacheConfiguration<>()
+ .setName(DEFAULT_CACHE_NAME)
+ .setAtomicityMode(TRANSACTIONAL)
+ .setReadFromBackup(false)
+ .setBackups(1)
+ .setAffinity(new GridCacheModuloAffinityFunction(2, 1)));
+
+ awaitPartitionMapExchange();
+
+ try (IgniteClient cli = startClient("allowed_client")) {
+ for (int i = 0; i < PRELOADED_KEY_CNT; i++)
+ cli.cache(DEFAULT_CACHE_NAME).put(i, i);
+ }
+ }
+
+ /** */
+ private static int nextKey() {
+ return KEY_CNTR.incrementAndGet();
+ }
+
+ /** */
+ private static IgniteClient startClient(String login) {
+ return Ignition.startClient(new ClientConfiguration()
+ .setAddresses("127.0.0.1:10800")
+ .setUserName(login)
+ .setUserPassword(""));
+ }
+
+ /** */
+ private static TestSecurityData userData(String login,
SecurityPermissionSet perms) {
+ return new TestSecurityData(
+ login,
+ "",
+ perms,
+ new Permissions()
+ );
+ }
+}
diff --git
a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
index d7cf6a08796..fae179367ec 100644
---
a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
+++
b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java
@@ -20,6 +20,7 @@ package org.apache.ignite.testsuites;
import
org.apache.ignite.internal.processors.security.IgniteSecurityProcessorTest;
import org.apache.ignite.internal.processors.security.InvalidServerTest;
import
org.apache.ignite.internal.processors.security.NodeSecurityContextPropagationTest;
+import
org.apache.ignite.internal.processors.security.SecurityContextInternalFuturePropagationTest;
import
org.apache.ignite.internal.processors.security.cache.CacheOperationPermissionCheckTest;
import
org.apache.ignite.internal.processors.security.cache.CacheOperationPermissionCreateDestroyCheckTest;
import
org.apache.ignite.internal.processors.security.cache.ContinuousQueryPermissionCheckTest;
@@ -143,6 +144,7 @@ import org.junit.runners.Suite;
NodeSecurityContextPropagationTest.class,
NodeJoinPermissionsTest.class,
ActivationOnJoinWithoutPermissionsWithPersistenceTest.class,
+ SecurityContextInternalFuturePropagationTest.class,
})
public class SecurityTestSuite {
/** */