Repository: nats-io/nats-server (go)

Issue:
Leaf node interest propagation filtering with queue groups - Exact subject not matching
### Observed behavior

- A leaf node is connected to a hub through a user with restrictive permissions. 
- In this example case publish allow "services.device.remote-lock"
- A services is listening on the leaf  "services.device.remote-lock" with queue group
- A client is trying access the service from the hub publishing to  "services.device.remote-lock"
- The message is not received by the leaf

Null tests and context:
- Setup works when the user permissions have a wildcard in the last element - e.g  "services.device.*" - "services.device.>"
- Setup works when the service connects to the hub with the same user as the leaf node  (the leaf node interest propagation is the crux, not the security checks themselves)
- Setup works when no queue group is used
- Tracing interest propagation shows that the subject interest is not propagated to the hub



### Expected behavior

Subscription interest propagates even if subject allow filter matches the subject exactly.

Suspected reason: This could be a regression in 2.10.23 due to recent fixes to leaf node interest propagation with queue groups

### Server and client version

2.10.23
Nats CLI - main branch

### Host environment

Windows

### Steps to reproduce

Defect case
1. Start leaf and hub with the configuration files included here
2. nats --server=nats://127.0.0.1:4223 reply services.device.remote-lock OK
3. nats request --server=nats://127.0.0.1:4222 services.device.remote-lock Hello

Nulltest-1
2. nats --server=nats://127.0.0.1:4223 sub services.device.remote-lock
3. nats --server=nats://127.0.0.1:4222 pub services.device.remote-lock Hello

Nulltest-2
2. nats --server=nats://127.0.0.1:4222 --user=user2 --password=user2 reply services.device.remote-lock OK
3. nats --server=nats://127.0.0.1:4222 request  services.device.remote-lock Hello

[20241219_Leafnode_interest_graph_issue.zip](https://github.com/user-attachments/files/18196024/20241219_Leafnode_interest_graph_issue.zip)


Pull request description:
[FIXED] LeafNode: queue interest on leaf not propagated with permissions on hub
If the hub has a user with subscribe permissions on a literal subject that the leaf is trying to create a queue subscription on, the interest may not be propagated.

The issue was caused by the fact that we were checking the permissions on the key (that includes subject and queue name) instead of the subject itself.

Resolves #6281

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>

Patch:
diff --git a/server/leafnode.go b/server/leafnode.go
index b85ff0ca..0219f4fe 100644
--- a/server/leafnode.go
+++ b/server/leafnode.go
@@ -2329,8 +2329,16 @@ func (c *client) sendLeafNodeSubUpdate(key string, n int32) {
 				checkPerms = false
 			}
 		}
-		if checkPerms && !c.canSubscribe(key) {
-			return
+		if checkPerms {
+			var subject string
+			if sep := strings.IndexByte(key, ' '); sep != -1 {
+				subject = key[:sep]
+			} else {
+				subject = key
+			}
+			if !c.canSubscribe(subject) {
+				return
+			}
 		}
 	}
 	// If we are here we can send over to the other side.


Test patch:
diff --git a/server/leafnode_test.go b/server/leafnode_test.go
index 32500f45..b5f8266a 100644
--- a/server/leafnode_test.go
+++ b/server/leafnode_test.go
@@ -9077,7 +9077,7 @@ func TestLeafNodeBannerNoClusterNameIfNoCluster(t *testing.T) {
 	l.Unlock()
 }
 
-func TestLeafCredFormatting(t *testing.T) {
+func TestLeafNodeCredFormatting(t *testing.T) {
 	//create the operator/sys/account tree
 	oKP, err := nkeys.CreateOperator()
 	require_NoError(t, err)
@@ -9151,14 +9151,15 @@ func TestLeafCredFormatting(t *testing.T) {
 		require_NoError(t, file.Close())
 
 		template := fmt.Sprintf(`
-		listen: 127.0.0.1:-1
-		leaf { remotes: [ 
-			{ 
-				urls: [ nats-leaf://127.0.0.1:%d ]
-				credentials: "%s"
-			} 
-		] }`, o.LeafNode.Port, file.Name())
-
+			listen: 127.0.0.1:-1
+			leafnodes {
+				remotes: [
+					{
+						urls: [ nats-leaf://127.0.0.1:%d ]
+						credentials: "%s"
+					}
+				]
+			}`, o.LeafNode.Port, file.Name())
 		conf := createConfFile(t, []byte(template))
 		leaf, _ := RunServerWithConfig(conf)
 		defer leaf.Shutdown()
@@ -9172,3 +9173,59 @@ func TestLeafCredFormatting(t *testing.T) {
 	runLeaf(t, creds)
 	runLeaf(t, bytes.ReplaceAll(creds, []byte{'\n'}, []byte{'\r', '\n'}))
 }
+
+func TestLeafNodePermissionWithLiteralSubjectAndQueueInterest(t *testing.T) {
+	hconf := createConfFile(t, []byte(`
+		server_name: "HUB"
+		listen: "127.0.0.1:-1"
+		leafnodes {
+			listen: "127.0.0.1:-1"
+		}
+		accounts {
+			A {
+				users: [
+					{ user: "user", password: "pwd",
+						permissions: {
+							subscribe: { allow: ["_INBOX.>", "my.subject"] }
+							publish: {allow: [">"]}
+						}
+					}
+				]
+			}
+		}
+	`))
+	hub, ohub := RunServerWithConfig(hconf)
+	defer hub.Shutdown()
+
+	lconf := createConfFile(t, []byte(fmt.Sprintf(`
+		server_name: "LEAF"
+		listen: "127.0.0.1:-1"
+		leafnodes {
+			remotes: [
+				{url: "nats://user:pwd@127.0.0.1:%d", account: A}
+			]
+		}
+		accounts {
+			A { users: [{user: user, password: pwd}] }
+		}
+	`, ohub.LeafNode.Port)))
+	leaf, _ := RunServerWithConfig(lconf)
+	defer leaf.Shutdown()
+
+	checkLeafNodeConnected(t, hub)
+	checkLeafNodeConnected(t, leaf)
+
+	ncLeaf := natsConnect(t, leaf.ClientURL(), nats.UserInfo("user", "pwd"))
+	defer ncLeaf.Close()
+	natsQueueSub(t, ncLeaf, "my.subject", "queue", func(m *nats.Msg) {
+		m.Respond([]byte("OK"))
+	})
+	natsFlush(t, ncLeaf)
+
+	ncHub := natsConnect(t, hub.ClientURL(), nats.UserInfo("user", "pwd"))
+	defer ncHub.Close()
+
+	resp, err := ncHub.Request("my.subject", []byte("hello"), time.Second)
+	require_NoError(t, err)
+	require_Equal(t, "OK", string(resp.Data))
+}