Skip to content

Commit

Permalink
IGNITE-15966 Fixes unrecoverable failures in case security context no…
Browse files Browse the repository at this point in the history
…t found.
  • Loading branch information
petrov-mg committed Nov 30, 2021
1 parent 85bb788 commit 71c1ead
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 16 deletions.
Expand Up @@ -980,19 +980,24 @@ public void checkUserOperation(UserManagementOperation op) throws IgniteAccessCo

SecuritySubject subj = ctx.security().securityContext().subject();

if (subj.type() == REMOTE_NODE) {
throw new IgniteAccessControlException("User management operations initiated on behalf of" +
" the Ignite node are not expected.");
}
try {
if (subj.type() == REMOTE_NODE) {
throw new IgniteAccessControlException("User management operations initiated on behalf of" +
" the Ignite node are not expected.");
}

if (!User.DFAULT_USER_NAME.equals(subj.login())
&& !(UserManagementOperation.OperationType.UPDATE == op.type() && subj.login().equals(op.user().name())))
throw new IgniteAccessControlException("User management operations are not allowed for user. " +
"[curUser=" + subj.login() + ']');
if (!User.DFAULT_USER_NAME.equals(subj.login())
&& !(UserManagementOperation.OperationType.UPDATE == op.type() && subj.login().equals(op.user().name())))
throw new IgniteAccessControlException("User management operations are not allowed for user. " +
"[curUser=" + subj.login() + ']');

if (op.type() == UserManagementOperation.OperationType.REMOVE
&& User.DFAULT_USER_NAME.equals(op.user().name()))
throw new IgniteAccessControlException("Default user cannot be removed.");
if (op.type() == UserManagementOperation.OperationType.REMOVE
&& User.DFAULT_USER_NAME.equals(op.user().name()))
throw new IgniteAccessControlException("Default user cannot be removed.");
}
catch (SecurityException e) {
throw new IgniteAccessControlException("Failed to perform user management operation.", e);
}
}

/**
Expand Down
Expand Up @@ -17,6 +17,7 @@

package org.apache.ignite.internal.processors.security;

import java.net.InetSocketAddress;
import java.security.Security;
import java.util.Collection;
import java.util.Map;
Expand All @@ -42,7 +43,9 @@
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityException;
import org.apache.ignite.plugin.security.SecurityPermission;
import org.apache.ignite.plugin.security.SecurityPermissionSet;
import org.apache.ignite.plugin.security.SecuritySubject;
import org.apache.ignite.plugin.security.SecuritySubjectType;
import org.apache.ignite.spi.IgniteNodeValidationResult;
import org.apache.ignite.spi.discovery.DiscoveryDataBag;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -163,10 +166,8 @@ else if (dfltSecCtx.subject().id().equals(subjId))
else
res = secCtxs.computeIfAbsent(subjId, uuid -> nodeSecurityContext(marsh, U.resolveClassLoader(ctx.config()), node));

if (res == null) {
throw new IllegalStateException("Failed to find security context " +
"for subject with given ID : " + subjId);
}
if (res == null)
res = new UnknownUserSecurityContext(subjId);

return withContext(res);
}
Expand Down Expand Up @@ -231,6 +232,11 @@ void restoreDefaultContext() {

assert secCtx != null;

if (secCtx instanceof UnknownUserSecurityContext) {
throw new SecurityException("Failed to obtain user security context. The user has probably been deleted" +
" in the middle of Ignite operation execution [subjId=" + secCtx.subject().id() + ']');
}

secPrc.authorize(name, perm, secCtx);
}

Expand Down Expand Up @@ -425,4 +431,92 @@ private IgniteNodeValidationResult validateSecProcClass(ClusterNode node) {
public GridSecurityProcessor securityProcessor() {
return secPrc;
}

/**
* Security context implementation that is used to handle situation when user security context cannot be obtained
* based on the user subject ID. It can happen if user information is dropped in the middle of Ignite operation or
* security plugin has incorrect implementation. This implementation throws exception on every attempt to get
* any specific use security information from it.
*/
private static class UnknownUserSecurityContext implements SecurityContext {
/** */
private static final long serialVersionUID = 0L;

/** */
private final SecuritySubject secSubj;

/** */
public UnknownUserSecurityContext(UUID id) {
secSubj = new UnknownSecuritySubject(id);
}

/** {@inheritDoc} */
@Override public SecuritySubject subject() {
return secSubj;
}

/** {@inheritDoc} */
@Override public boolean taskOperationAllowed(String taskClsName, SecurityPermission perm) {
return false;
}

/** {@inheritDoc} */
@Override public boolean cacheOperationAllowed(String cacheName, SecurityPermission perm) {
return false;
}

/** {@inheritDoc} */
@Override public boolean serviceOperationAllowed(String srvcName, SecurityPermission perm) {
return false;
}

/** {@inheritDoc} */
@Override public boolean systemOperationAllowed(SecurityPermission perm) {
return false;
}

/** */
private static class UnknownSecuritySubject implements SecuritySubject {
/** */
private static final long serialVersionUID = 0L;

/** */
private final UUID secSubjId;

/** */
private final String errMsg;

/** */
public UnknownSecuritySubject(UUID secSubjId) {
this.secSubjId = secSubjId;

errMsg = "Failed to obtain security information for the current user [subjId=" + secSubjId + ']';
}

/** {@inheritDoc} */
@Override public UUID id() {
return secSubjId;
}

/** {@inheritDoc} */
@Override public SecuritySubjectType type() {
throw new SecurityException(errMsg);
}

/** {@inheritDoc} */
@Override public Object login() {
throw new SecurityException(errMsg);
}

/** {@inheritDoc} */
@Override public InetSocketAddress address() {
throw new SecurityException(errMsg);
}

/** {@inheritDoc} */
@Override public SecurityPermissionSet permissions() {
throw new SecurityException(errMsg);
}
}
}
}
@@ -0,0 +1,169 @@
/*
* 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.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteException;
import org.apache.ignite.Ignition;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.client.IgniteClientFuture;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.cluster.ClusterState;
import org.apache.ignite.compute.ComputeJob;
import org.apache.ignite.compute.ComputeJobAdapter;
import org.apache.ignite.compute.ComputeJobResult;
import org.apache.ignite.compute.ComputeTaskAdapter;
import org.apache.ignite.configuration.ClientConfiguration;
import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.ThinClientConfiguration;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.testframework.GridTestUtils;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.ignite.internal.processors.authentication.AuthenticationProcessorSelfTest.authenticate;
import static org.apache.ignite.internal.processors.authentication.AuthenticationProcessorSelfTest.withSecurityContextOnAllNodes;

/** Tests scenario when {@link SecurityContext} cannot be obtained on remote node. */
public class UnknownUserSecurityContextTest extends GridCommonAbstractTest {
/** */
private static CountDownLatch taskExecutionStartedLatch;

/** */
private static CountDownLatch taskExecutionUnblockedLatch;

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

cleanPersistenceDir();
}

/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);

cfg.setAuthenticationEnabled(true);

cfg.setClientConnectorConfiguration(new ClientConnectorConfiguration()
.setThinClientConfiguration(new ThinClientConfiguration()
.setMaxActiveComputeTasksPerConnection(1)));

cfg.setDataStorageConfiguration(new DataStorageConfiguration()
.setDefaultDataRegionConfiguration(new DataRegionConfiguration()
.setPersistenceEnabled(true)));

return cfg;
}

/** */
@Test
public void testUserDropDuringOperation() throws Exception {
startGrids(2);

grid(0).cluster().state(ClusterState.ACTIVE);

grid(0).createCache(DEFAULT_CACHE_NAME);

String user = "cli";
String pwd = "pwd";

try (AutoCloseable ignored = withSecurityContextOnAllNodes(authenticate(grid(0), "ignite", "ignite"))) {
grid(0).context().security().createUser(user, pwd.toCharArray());
}

IgniteClient cli = Ignition.startClient(new ClientConfiguration().setAddresses("127.0.0.1:10800")
.setUserName(user)
.setUserPassword(pwd));

taskExecutionStartedLatch = new CountDownLatch(1);
taskExecutionUnblockedLatch = new CountDownLatch(1);

IgniteClientFuture<Object> fut = cli.compute().executeAsync2(TestTask.class.getName(), grid(1).localNode().id());

taskExecutionStartedLatch.await();

try (AutoCloseable ignored = withSecurityContextOnAllNodes(authenticate(grid(0), "ignite", "ignite"))) {
grid(0).context().security().dropUser(user);
}

taskExecutionUnblockedLatch.countDown();

GridTestUtils.assertThrowsAnyCause(
log, () -> fut.get(getTestTimeout(), MILLISECONDS),
ExecutionException.class,
"Failed to obtain user security context. The user has probably been deleted in the middle of Ignite operation execution");
}

/** */
public static class TestTask extends ComputeTaskAdapter<UUID, Void> {
/** {@inheritDoc} */
@Override public @NotNull Map<? extends ComputeJob, ClusterNode> map(
List<ClusterNode> subgrid,
@Nullable UUID nodeId
) throws IgniteException {
taskExecutionStartedLatch.countDown();

try {
taskExecutionUnblockedLatch.await();
}
catch (InterruptedException e) {
throw new IgniteException(e);
}

for (ClusterNode node : subgrid) {
if (node.id().equals(nodeId)) {
return Collections.singletonMap(new ComputeJobAdapter() {
@IgniteInstanceResource
private Ignite ignite;

@Override public void cancel() {
// No-op.
}

@Override public Serializable execute() {
ignite.cache(DEFAULT_CACHE_NAME).put("key", "val");

return null;
}
}, node);
}
}

return null;
}

/** {@inheritDoc} */
@Override public @Nullable Void reduce(List<ComputeJobResult> results) throws IgniteException {
return null;
}
}
}
Expand Up @@ -19,6 +19,7 @@

import org.apache.ignite.internal.processors.security.IgniteSecurityProcessorTest;
import org.apache.ignite.internal.processors.security.InvalidServerTest;
import org.apache.ignite.internal.processors.security.UnknownUserSecurityContextTest;
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;
Expand Down Expand Up @@ -132,7 +133,8 @@
MaintenanceModeNodeSecurityTest.class,
DaemonNodeBasicSecurityTest.class,
ServiceAuthorizationTest.class,
ServiceStaticConfigTest.class
ServiceStaticConfigTest.class,
UnknownUserSecurityContextTest.class
})
public class SecurityTestSuite {
/** */
Expand Down

0 comments on commit 71c1ead

Please sign in to comment.