This is an automated email from the ASF dual-hosted git repository.

mpetrov 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 f5e69ba922c IGNITE-19277 Added authorization of Ignite Cluster Node 
start/stop operations. (#10644)
f5e69ba922c is described below

commit f5e69ba922c80ce059658db7483fb3dee584e978
Author: Mikhail Petrov <[email protected]>
AuthorDate: Tue Apr 18 11:58:36 2023 +0300

    IGNITE-19277 Added authorization of Ignite Cluster Node start/stop 
operations. (#10644)
---
 .../ignite/internal/cluster/IgniteClusterImpl.java |  14 +
 .../ignite/plugin/security/SecurityPermission.java |   6 +
 .../ClusterNodeOperationPermissionTest.java        | 301 +++++++++++++++++++++
 .../security/impl/TestSecurityContext.java         |   2 +
 .../ignite/testsuites/SecurityTestSuite.java       |   4 +-
 5 files changed, 326 insertions(+), 1 deletion(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java
index e0231724940..b094d8df42b 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java
@@ -84,6 +84,8 @@ import static 
org.apache.ignite.internal.IgniteNodeAttributes.ATTR_MACS;
 import static 
org.apache.ignite.internal.processors.task.TaskExecutionOptions.options;
 import static 
org.apache.ignite.internal.util.nodestart.IgniteNodeStartUtils.parseFile;
 import static 
org.apache.ignite.internal.util.nodestart.IgniteNodeStartUtils.specifications;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.ADMIN_CLUSTER_NODE_START;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.ADMIN_CLUSTER_NODE_STOP;
 
 /**
  *
@@ -276,6 +278,8 @@ public class IgniteClusterImpl extends ClusterGroupAdapter 
implements IgniteClus
         guard();
 
         try {
+            ctx.security().authorize(ADMIN_CLUSTER_NODE_STOP);
+
             ctx.task().execute(IgniteKillTask.class, false, 
options().withProjection(nodes())).get();
         }
         catch (IgniteCheckedException e) {
@@ -291,6 +295,8 @@ public class IgniteClusterImpl extends ClusterGroupAdapter 
implements IgniteClus
         guard();
 
         try {
+            ctx.security().authorize(ADMIN_CLUSTER_NODE_STOP);
+
             ctx.task().execute(IgniteKillTask.class, false, 
options().withProjection(forNodeIds(ids).nodes())).get();
         }
         catch (IgniteCheckedException e) {
@@ -306,6 +312,8 @@ public class IgniteClusterImpl extends ClusterGroupAdapter 
implements IgniteClus
         guard();
 
         try {
+            ctx.security().authorize(ADMIN_CLUSTER_NODE_STOP);
+
             ctx.task().execute(IgniteKillTask.class, true, 
options().withProjection(nodes())).get();
         }
         catch (IgniteCheckedException e) {
@@ -321,6 +329,8 @@ public class IgniteClusterImpl extends ClusterGroupAdapter 
implements IgniteClus
         guard();
 
         try {
+            ctx.security().authorize(ADMIN_CLUSTER_NODE_STOP);
+
             ctx.task().execute(IgniteKillTask.class, true, 
options().withProjection(forNodeIds(ids).nodes())).get();
         }
         catch (IgniteCheckedException e) {
@@ -882,6 +892,8 @@ public class IgniteClusterImpl extends ClusterGroupAdapter 
implements IgniteClus
         guard();
 
         try {
+            ctx.security().authorize(ADMIN_CLUSTER_NODE_START);
+
             IgniteSshHelper sshHelper = IgniteComponentType.SSH.create(false);
 
             Map<String, Collection<IgniteRemoteStartSpecification>> specsMap = 
specifications(hosts, dflts);
@@ -920,6 +932,8 @@ public class IgniteClusterImpl extends ClusterGroupAdapter 
implements IgniteClus
 
                 if (neighbors != null) {
                     if (restart && !neighbors.isEmpty()) {
+                        ctx.security().authorize(ADMIN_CLUSTER_NODE_STOP);
+
                         try {
                             ctx.task().execute(IgniteKillTask.class, false, 
options(forNodes(neighbors).nodes())).get();
                         }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
 
b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
index ccd07a87dd1..5deb365d0bb 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecurityPermission.java
@@ -101,6 +101,12 @@ public enum SecurityPermission {
      */
     ADMIN_CLUSTER_STATE,
 
+    /** Start new cluster node permission. */
+    ADMIN_CLUSTER_NODE_START,
+
+    /** Stop/restart cluster node permission. */
+    ADMIN_CLUSTER_NODE_STOP,
+
     /** Permission to execute REFRESH STATISTICS command. */
     REFRESH_STATISTICS,
 
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cluster/ClusterNodeOperationPermissionTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cluster/ClusterNodeOperationPermissionTest.java
new file mode 100644
index 00000000000..6b4111bcb15
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/cluster/ClusterNodeOperationPermissionTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.cluster;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.ClientConnectionException;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.cluster.ClusterNode;
+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.IgniteConfiguration;
+import org.apache.ignite.configuration.ThinClientConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.processors.security.AbstractSecurityTest;
+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.plugin.security.SecurityException;
+import org.apache.ignite.plugin.security.SecurityPermissionSetBuilder;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_SUCCESS_FILE;
+import static 
org.apache.ignite.internal.processors.security.cluster.ClusterNodeOperationPermissionTest.Operation.RESTART;
+import static 
org.apache.ignite.internal.processors.security.cluster.ClusterNodeOperationPermissionTest.Operation.RESTART_ALL;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.ADMIN_CLUSTER_NODE_STOP;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.JOIN_AS_SERVER;
+import static 
org.apache.ignite.plugin.security.SecurityPermission.TASK_EXECUTE;
+import static 
org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.systemPermissions;
+import static 
org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+
+/** */
+public class ClusterNodeOperationPermissionTest extends AbstractSecurityTest {
+    /** */
+    private IgniteClient cli;
+
+    /** */
+    private IgniteProcessProxy ignite;
+
+    /** */
+    public static final String RESTART_FILE_PATH = 
"src/test/resources/ignite-restart.tmp";
+
+    /** */
+    private IgniteConfiguration configuration(int idx) throws Exception {
+        String login = getTestIgniteInstanceName(idx);
+
+        return getConfiguration(
+            login,
+            new TestSecurityPluginProvider(
+                login,
+                "",
+                 systemPermissions(JOIN_AS_SERVER),
+                null,
+                false,
+                new TestSecurityData("stop-allowed", clientPermissionsBuilder()
+                    .appendSystemPermissions(ADMIN_CLUSTER_NODE_STOP)
+                    .build()),
+                new TestSecurityData("stop-forbidden", 
clientPermissionsBuilder().build())
+            ))
+            .setClientConnectorConfiguration(new ClientConnectorConfiguration()
+                .setThinClientConfiguration(new ThinClientConfiguration()
+                    .setMaxActiveComputeTasksPerConnection(1)));
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        stopAllGrids();
+    }
+
+    /** */
+    @Test
+    public void testStartNodeAuthorization() throws Exception {
+        IgniteEx ignite = startGrid(configuration(0));
+
+        assertAuthorizationFailed(
+            () -> ignite.cluster().startNodes(new 
File("src/test/config/start-nodes.ini"), true, 1, 1),
+            SecurityException.class
+        );
+
+        assertAuthorizationFailed(() ->
+            ignite.cluster().startNodesAsync(new 
File("src/test/config/start-nodes.ini"), true, 1, 1).get(),
+            SecurityException.class
+        );
+
+        assertAuthorizationFailed(() ->
+            ignite.cluster().startNodes(Collections.emptyList(), 
Collections.emptyMap(), true, 1, 1),
+            SecurityException.class
+        );
+
+        assertAuthorizationFailed(() ->
+            ignite.cluster().startNodes(Collections.emptyList(), 
Collections.emptyMap(), true, 1, 1),
+            SecurityException.class
+        );
+    }
+
+    /** */
+    @Test
+    public void testStopNodeAuthorization() throws Exception {
+        for (Operation op : Operation.values()) {
+            doClusterNodeTest("stop-allowed", op, true);
+            doClusterNodeTest("stop-forbidden", op, false);
+        }
+    }
+
+    /**
+     * We are forced to initiate the test operation through the thin client, 
because otherwise the node stop/restart
+     * operations will halt the local test JVM.
+     */
+    private void doClusterNodeTest(String login, Operation op, boolean 
isSuccessExpected) throws Exception {
+        prepareCluster(login);
+
+        try {
+            if (isSuccessExpected) {
+                try {
+                    
cli.compute().execute(ClusterNodeOperationExecutor.class.getName(), op);
+                }
+                catch (ClientException e) {
+                    // It is possible for the Ignite node to shut down before 
the task execution response is sent.
+                    assertTrue(e.getMessage().contains("Task cancelled due to 
stopping of the grid"));
+                }
+
+                assertTrue(waitForCondition(() -> 
!ignite.getProcess().getProcess().isAlive(), getTestTimeout()));
+
+                assertTrue((op != RESTART && op != RESTART_ALL) || new 
File(RESTART_FILE_PATH).exists());
+            }
+            else {
+                assertAuthorizationFailed(
+                    () -> 
cli.compute().execute(ClusterNodeOperationExecutor.class.getName(), op),
+                    ClientException.class
+                );
+
+                assertTrue(ignite.getProcess().getProcess().isAlive());
+            }
+        }
+        finally {
+            stopCluster();
+        }
+    }
+
+    /** */
+    private void prepareCluster(String opExecutorLogin) throws Exception {
+        ignite = new IgniteProcessProxy(
+            configuration(0),
+            log,
+            null,
+            true,
+            singletonList("-D" + IGNITE_SUCCESS_FILE + "=" + RESTART_FILE_PATH)
+        );
+
+        AtomicReference<IgniteClient> cli = new AtomicReference<>();
+
+        assertTrue(waitForCondition(() -> {
+            try {
+                cli.set(startClient(opExecutorLogin));
+
+                return true;
+            }
+            catch (ClientConnectionException e) {
+                return false;
+            }
+        }, getTestTimeout()));
+
+        this.cli = cli.get();
+    }
+
+    /** */
+    private void stopCluster() throws Exception {
+        cli.close();
+
+        ignite.kill();
+
+        waitForCondition(() -> !ignite.getProcess().getProcess().isAlive(), 
getTestTimeout());
+
+        File restartFile = new File(RESTART_FILE_PATH);
+
+        if (restartFile.exists())
+            restartFile.delete();
+    }
+
+    /** */
+    private void assertAuthorizationFailed(RunnableX r, Class<? extends 
Exception> exCls) {
+        assertThrowsAnyCause(
+            log,
+            () -> {
+                r.run();
+
+                return null;
+            },
+            exCls,
+            "Authorization failed"
+        );
+    }
+
+    /** */
+    private IgniteClient startClient(String login) {
+        return Ignition.startClient(new ClientConfiguration()
+            .setAddresses("127.0.0.1:10800")
+            .setUserName(login)
+            .setUserPassword(""));
+    }
+
+    /** */
+    private SecurityPermissionSetBuilder clientPermissionsBuilder() {
+        return new SecurityPermissionSetBuilder()
+            .defaultAllowAll(false)
+            
.appendTaskPermissions(ClusterNodeOperationExecutor.class.getName(), 
TASK_EXECUTE);
+    }
+
+    /** */
+    public static final class ClusterNodeOperationExecutor extends 
ComputeTaskAdapter<Operation, Void> {
+        /** {@inheritDoc} */
+        @Override public @NotNull Map<? extends ComputeJob, ClusterNode> map(
+            List<ClusterNode> subgrid,
+            @Nullable Operation arg
+        ) throws IgniteException {
+            return Collections.singletonMap(new Job(arg), subgrid.get(0));
+        }
+
+        /** {@inheritDoc} */
+        @Override public @Nullable Void reduce(List<ComputeJobResult> results) 
throws IgniteException {
+            return null;
+        }
+
+        /** */
+        private static final class Job extends ComputeJobAdapter {
+            /** */
+            private final Operation op;
+
+            /** */
+            @IgniteInstanceResource
+            Ignite ignite;
+
+            /** */
+            public Job(Operation op) {
+                this.op = op;
+            }
+
+            /** {@inheritDoc} */
+            @Override public Object execute() throws IgniteException {
+                switch (op) {
+                    case STOP: 
ignite.cluster().stopNodes(singleton(ignite.cluster().localNode().id())); break;
+                    case STOP_ALL: ignite.cluster().stopNodes(); break;
+                    case RESTART: 
ignite.cluster().restartNodes(singleton(ignite.cluster().localNode().id())); 
break;
+                    case RESTART_ALL: ignite.cluster().restartNodes(); break;
+                }
+
+                return null;
+            }
+        }
+    }
+
+    /** */
+    public enum Operation {
+        /** */
+        STOP,
+
+        /** */
+        STOP_ALL,
+
+        /** */
+        RESTART,
+
+        /** */
+        RESTART_ALL
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityContext.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityContext.java
index 4abfd5d0f69..c02d4b557fb 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityContext.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityContext.java
@@ -73,6 +73,8 @@ public class TestSecurityContext implements SecurityContext, 
Serializable {
             case JOIN_AS_SERVER:
             case ADMIN_KILL:
             case ADMIN_USER_ACCESS:
+            case ADMIN_CLUSTER_NODE_STOP:
+            case ADMIN_CLUSTER_NODE_START:
                 return systemOperationAllowed(perm);
 
             default:
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 32e62d67ce9..9bd400f66d6 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
@@ -38,6 +38,7 @@ import 
org.apache.ignite.internal.processors.security.client.ThinClientPermissio
 import 
org.apache.ignite.internal.processors.security.client.ThinClientPermissionCheckTest;
 import 
org.apache.ignite.internal.processors.security.client.ThinClientSecurityContextOnRemoteNodeTest;
 import 
org.apache.ignite.internal.processors.security.client.ThinClientSslPermissionCheckTest;
+import 
org.apache.ignite.internal.processors.security.cluster.ClusterNodeOperationPermissionTest;
 import 
org.apache.ignite.internal.processors.security.cluster.ClusterStatePermissionTest;
 import 
org.apache.ignite.internal.processors.security.compute.ComputePermissionCheckTest;
 import 
org.apache.ignite.internal.processors.security.compute.closure.ComputeTaskCancelRemoteSecurityContextCheckTest;
@@ -132,7 +133,8 @@ import org.junit.runners.Suite;
     MultipleSSLContextsTest.class,
     MaintenanceModeNodeSecurityTest.class,
     ServiceAuthorizationTest.class,
-    ServiceStaticConfigTest.class
+    ServiceStaticConfigTest.class,
+    ClusterNodeOperationPermissionTest.class
 })
 public class SecurityTestSuite {
     /** */

Reply via email to