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

rzo1 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/storm.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ebe6e717 Check the calling principal against the topology owner in 
uploadNewCredentials (#9001)
5ebe6e717 is described below

commit 5ebe6e717698472c703235853767b179817aafe6
Author: Richard Zowalla <[email protected]>
AuthorDate: Mon Aug 24 10:52:36 2026 +0200

    Check the calling principal against the topology owner in 
uploadNewCredentials (#9001)
---
 .../org/apache/storm/daemon/nimbus/Nimbus.java     | 28 +++++----
 .../org/apache/storm/daemon/nimbus/NimbusTest.java | 66 +++++++++++++++++++++-
 2 files changed, 80 insertions(+), 14 deletions(-)

diff --git 
a/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java 
b/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java
index db2957804..682db2338 100644
--- a/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java
+++ b/storm-server/src/main/java/org/apache/storm/daemon/nimbus/Nimbus.java
@@ -3862,22 +3862,26 @@ public class Nimbus implements Iface, Shutdownable, 
DaemonCommon {
             checkAuthorization(topoName, topoConf, "uploadNewCredentials");
             String realPrincipal = (String) 
topoConf.get(Config.TOPOLOGY_SUBMITTER_PRINCIPAL);
             String realUser = (String) 
topoConf.get(Config.TOPOLOGY_SUBMITTER_USER);
-            String expectedOwner = null;
-            if (credentials.is_set_topoOwner()) {
-                expectedOwner = credentials.get_topoOwner();
-            } else {
-                Principal p = ReqContext.context().principal();
-                if (p != null) {
-                    expectedOwner = principalToLocal.toLocal(p);
-                }
+            String caller = null;
+            Principal p = ReqContext.context().principal();
+            if (p != null) {
+                caller = principalToLocal.toLocal(p);
             }
-            // expectedOwner being null means that security is disabled (which 
why are we uploading credentials with security disabled???
-            if (expectedOwner == null) {
+            // caller being null means that security is disabled (which why 
are we uploading credentials with security disabled???
+            if (caller == null) {
                 LOG.warn("Please check you settings. Credentials are being 
uploaded to {} with security disabled.", topoId);
-            } else if (!realPrincipal.equals(expectedOwner) && 
!realUser.equals(expectedOwner)) {
-                throw new AuthorizationException(topoId + " is expected to be 
owned by " + expectedOwner
+            } else if (!realPrincipal.equals(caller) && 
!realUser.equals(caller)) {
+                throw new AuthorizationException(topoId + " is expected to be 
owned by " + caller
                     + " but is actually owned by " + realPrincipal);
             }
+            // topoOwner is just the owner the client expects, so it can only 
reject a mismatch, never stand in for the caller.
+            if (credentials.is_set_topoOwner()) {
+                String expectedOwner = credentials.get_topoOwner();
+                if (!expectedOwner.equals(realPrincipal) && 
!expectedOwner.equals(realUser)) {
+                    throw new AuthorizationException(topoId + " is expected to 
be owned by " + expectedOwner
+                        + " but is actually owned by " + realPrincipal);
+                }
+            }
 
             synchronized (credUpdateLock) {
                 //Merge the old credentials so creds nimbus created are not 
lost.
diff --git 
a/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java 
b/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java
index 49baebbd2..f24468d4c 100644
--- a/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java
+++ b/storm-server/src/test/java/org/apache/storm/daemon/nimbus/NimbusTest.java
@@ -31,8 +31,7 @@ import java.util.Optional;
 import java.util.Set;
 import javax.security.auth.Subject;
 
-import javax.security.auth.Subject;
-
+import com.codahale.metrics.Meter;
 import net.minidev.json.JSONValue;
 import org.apache.commons.io.FileUtils;
 import org.apache.storm.Config;
@@ -42,6 +41,7 @@ import org.apache.storm.blobstore.KeySequenceNumber;
 import org.apache.storm.blobstore.LocalFsBlobStore;
 import org.apache.storm.cluster.IStormClusterState;
 import org.apache.storm.generated.AuthorizationException;
+import org.apache.storm.generated.Credentials;
 import org.apache.storm.generated.InvalidTopologyException;
 import org.apache.storm.generated.KeyNotFoundException;
 import org.apache.storm.generated.ListBlobsResult;
@@ -71,6 +71,7 @@ import org.apache.storm.utils.ServerUtils;
 import org.apache.storm.utils.Time;
 import org.apache.storm.utils.Utils;
 import org.apache.storm.utils.WrappedAuthorizationException;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
@@ -85,6 +86,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -95,6 +97,7 @@ import static org.mockito.Mockito.when;
 
 class NimbusTest {
     private static final String BLOB_FILE_KEY = "file-key";
+    private static final String TOPO_NAME = "topo";
     private static final String TOPO_ID = "topology1-1-1";
 
     @Mock
@@ -111,6 +114,8 @@ class NimbusTest {
     private ILeaderElector leaderElector;
     @Mock
     private IGroupMappingServiceProvider groupMapper;
+    @Mock
+    private TopoCache topoCache;
 
     private Nimbus nimbus;
 
@@ -122,6 +127,11 @@ class NimbusTest {
         nimbus = new Nimbus(conf, iNimbus, stormClusterState, nimbusInfo, 
localBlobStore, leaderElector, groupMapper, metricRegistry);
     }
 
+    @AfterEach
+    public void tearDown() {
+        ReqContext.reset();
+    }
+
     @Test
     public void testMemoryLoadLargerThanMaxHeapSize() {
         // Topology will not be able to be successfully scheduled: Config 
TOPOLOGY_WORKER_MAX_HEAP_SIZE_MB=128.0 < 129.0,
@@ -272,6 +282,58 @@ class NimbusTest {
         verify(localBlobStore, never()).listKeys();
     }
 
+    @Test
+    void testUploadNewCredentialsRejectsACallerWhoIsNotTheOwner() throws 
Exception {
+        Nimbus nimbus = makeNimbusOwningTopology(TOPO_NAME, TOPO_ID, "alice");
+        setCaller("bob");
+
+        // bob claims the topology is owned by alice, which it is, but bob is 
not alice
+        Credentials creds = new Credentials(Map.of("key", "value"));
+        creds.set_topoOwner("alice");
+
+        assertThrows(AuthorizationException.class, () -> 
nimbus.uploadNewCredentials(TOPO_NAME, creds));
+        verify(stormClusterState, never()).setCredentials(eq(TOPO_ID), any(), 
any());
+    }
+
+    @Test
+    void testUploadNewCredentialsAcceptsTheOwner() throws Exception {
+        Nimbus nimbus = makeNimbusOwningTopology(TOPO_NAME, TOPO_ID, "alice");
+        setCaller("alice");
+
+        Credentials creds = new Credentials(Map.of("key", "value"));
+        creds.set_topoOwner("alice");
+        nimbus.uploadNewCredentials(TOPO_NAME, creds);
+
+        verify(stormClusterState).setCredentials(eq(TOPO_ID), eq(creds), 
any());
+    }
+
+    @Test
+    void testUploadNewCredentialsRejectsAnOwnerMismatchClaimedByTheOwner() 
throws Exception {
+        Nimbus nimbus = makeNimbusOwningTopology(TOPO_NAME, TOPO_ID, "alice");
+        setCaller("alice");
+
+        // alice expects the topology to be owned by bob, so the push must not 
happen
+        Credentials creds = new Credentials(Map.of("key", "value"));
+        creds.set_topoOwner("bob");
+
+        assertThrows(AuthorizationException.class, () -> 
nimbus.uploadNewCredentials(TOPO_NAME, creds));
+        verify(stormClusterState, never()).setCredentials(eq(TOPO_ID), any(), 
any());
+    }
+
+    private Nimbus makeNimbusOwningTopology(String topoName, String topoId, 
String owner) throws Exception {
+        Map<String, Object> topoConf = new HashMap<>();
+        topoConf.put(Config.TOPOLOGY_SUBMITTER_PRINCIPAL, owner);
+        topoConf.put(Config.TOPOLOGY_SUBMITTER_USER, owner);
+        
when(stormClusterState.getTopoId(topoName)).thenReturn(Optional.of(topoId));
+        when(topoCache.readTopoConf(eq(topoId), any())).thenReturn(topoConf);
+        when(metricRegistry.registerMeter(anyString())).thenReturn(new 
Meter());
+
+        Map<String, Object> conf = 
Map.of(DaemonConfig.NIMBUS_MONITOR_FREQ_SECS, 10,
+            Config.STORM_PRINCIPAL_TO_LOCAL_PLUGIN, 
DefaultPrincipalToLocal.class.getName());
+        return new Nimbus(conf, iNimbus, stormClusterState, nimbusInfo, 
localBlobStore, topoCache, leaderElector, groupMapper,
+            metricRegistry);
+    }
+
     @Test
     void testValidateUploadedJarLocationRejectsLocationsOutsideTheInbox() 
throws Exception {
         Path inbox = Files.createTempDirectory("nimbus-inbox");

Reply via email to