diff --git a/auth/src/test/java/org/apache/rocketmq/auth/migration/v1/PlainAccessConfigTest.java b/auth/src/test/java/org/apache/rocketmq/auth/migration/v1/PlainAccessConfigTest.java new file mode 100644 index 00000000000..e6b00aa51e7 --- /dev/null +++ b/auth/src/test/java/org/apache/rocketmq/auth/migration/v1/PlainAccessConfigTest.java @@ -0,0 +1,142 @@ +/* + * 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.rocketmq.auth.migration.v1; + +import java.util.Arrays; +import java.util.List; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class PlainAccessConfigTest { + + @Test + public void testGettersAndSetters() { + PlainAccessConfig config = new PlainAccessConfig(); + config.setAccessKey("testAk"); + config.setSecretKey("testSk"); + config.setWhiteRemoteAddress("192.168.1.*"); + config.setAdmin(true); + config.setDefaultTopicPerm("PUB|SUB"); + config.setDefaultGroupPerm("SUB"); + + assertEquals("testAk", config.getAccessKey()); + assertEquals("testSk", config.getSecretKey()); + assertEquals("192.168.1.*", config.getWhiteRemoteAddress()); + assertTrue(config.isAdmin()); + assertEquals("PUB|SUB", config.getDefaultTopicPerm()); + assertEquals("SUB", config.getDefaultGroupPerm()); + } + + @Test + public void testTopicPermsAndGroupPerms() { + PlainAccessConfig config = new PlainAccessConfig(); + List topicPerms = Arrays.asList("TestTopic=PUB|SUB", "OrderTopic=PUB"); + List groupPerms = Arrays.asList("TestGroup=SUB"); + + config.setTopicPerms(topicPerms); + config.setGroupPerms(groupPerms); + + assertEquals(2, config.getTopicPerms().size()); + assertEquals("TestTopic=PUB|SUB", config.getTopicPerms().get(0)); + assertEquals(1, config.getGroupPerms().size()); + assertEquals("TestGroup=SUB", config.getGroupPerms().get(0)); + } + + @Test + public void testEqualsIdenticalConfigs() { + PlainAccessConfig c1 = new PlainAccessConfig(); + c1.setAccessKey("ak"); + c1.setSecretKey("sk"); + c1.setAdmin(true); + + PlainAccessConfig c2 = new PlainAccessConfig(); + c2.setAccessKey("ak"); + c2.setSecretKey("sk"); + c2.setAdmin(true); + + assertEquals(c1, c2); + assertEquals(c1.hashCode(), c2.hashCode()); + } + + @Test + public void testEqualsDifferentConfigs() { + PlainAccessConfig c1 = new PlainAccessConfig(); + c1.setAccessKey("ak1"); + + PlainAccessConfig c2 = new PlainAccessConfig(); + c2.setAccessKey("ak2"); + + assertNotEquals(c1, c2); + } + + @Test + public void testEqualsNull() { + PlainAccessConfig config = new PlainAccessConfig(); + assertNotEquals(null, config); + } + + @Test + public void testEqualsDifferentClass() { + PlainAccessConfig config = new PlainAccessConfig(); + assertNotEquals("not a config", config); + } + + @Test + public void testEqualsWithDifferentAdmin() { + PlainAccessConfig c1 = new PlainAccessConfig(); + c1.setAdmin(true); + + PlainAccessConfig c2 = new PlainAccessConfig(); + c2.setAdmin(false); + + assertNotEquals(c1, c2); + } + + @Test + public void testEqualsWithDifferentTopicPerms() { + PlainAccessConfig c1 = new PlainAccessConfig(); + c1.setAccessKey("ak"); + c1.setTopicPerms(Arrays.asList("A=PUB|SUB")); + + PlainAccessConfig c2 = new PlainAccessConfig(); + c2.setAccessKey("ak"); + c2.setTopicPerms(Arrays.asList("B=PUB|SUB")); + + assertNotEquals(c1, c2); + } + + @Test + public void testEqualsWithNullFields() { + PlainAccessConfig c1 = new PlainAccessConfig(); + PlainAccessConfig c2 = new PlainAccessConfig(); + assertEquals(c1, c2); + assertEquals(c1.hashCode(), c2.hashCode()); + } + + @Test + public void testToString() { + PlainAccessConfig config = new PlainAccessConfig(); + config.setAccessKey("ak"); + config.setAdmin(true); + String str = config.toString(); + assertTrue(str.contains("ak")); + assertTrue(str.contains("admin=true")); + } +} diff --git a/docs/cn/acl/user_guide.md b/docs/cn/acl/user_guide.md index 463a28d8ce4..3b427d507c0 100644 --- a/docs/cn/acl/user_guide.md +++ b/docs/cn/acl/user_guide.md @@ -84,86 +84,104 @@ RocketMQ的权限控制存储的默认实现是基于yml配置文件。用户可 ## 7. ACL mqadmin配置管理命令 -### 7.1 更新ACL配置文件中“account”的属性值 +RocketMQ 5.x 提供了以下 mqadmin 命令用于管理 ACL。所有命令均支持 `-h` 查看帮助。 -该命令的示例如下: +### 7.1 创建ACL规则 -sh mqadmin updateAclConfig -n 192.168.1.2:9876 -b 192.168.12.134:10911 -a RocketMQ -s 1234567809123 --t topicA=DENY,topicD=SUB -g groupD=DENY,groupB=SUB +该命令用于为指定用户(subject)创建一条ACL规则。 -说明:如果不存在则会在ACL Config YAML配置文件中创建;若存在,则会更新对应的“accounts”的属性值; -如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 +```shell +sh mqadmin createAcl -n 192.168.1.2:9876 -c DefaultCluster -s RocketMQ -r topic=TestTopic -a PUB -d ALLOW +``` | 参数 | 取值 | 含义 | | --- | --- | --- | -| n | eg:192.168.1.2:9876 | namesrv地址(必填) | -| c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | -| b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | -| a | eg:RocketMQ | Access Key值(必填) | -| s | eg:1234567809123 | Secret Key值(可选) | -| m | eg:true | 是否管理员账户(可选) | -| w | eg:192.168.0.* | whiteRemoteAddress,用户IP白名单(可选) | -| i | eg:DENY;PUB;SUB;PUB\|SUB | defaultTopicPerm,默认Topic权限(可选) | -| u | eg:DENY;PUB;SUB;PUB\|SUB | defaultGroupPerm,默认ConsumerGroup权限(可选) | -| t | eg:topicA=DENY,topicD=SUB | topicPerms,各个Topic的权限(可选) | -| g | eg:groupD=DENY,groupB=SUB | groupPerms,各个ConsumerGroup的权限(可选) | - -### 7.2 删除ACL配置文件里面的对应“account” -该命令的示例如下: - -sh mqadmin deleteAccessConfig -n 192.168.1.2:9876 -c DefaultCluster -a RocketMQ - -说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 -其中,参数"a"为Access Key的值,用以标识唯一账户id,因此该命令的参数中指定账户id即可。 +| n | eg:192.168.1.2:9876 | namesrv地址 | +| c | eg:DefaultCluster | 目标集群名称(与-b二选一) | +| b | eg:192.168.1.2:10911 | 目标broker地址(与-c二选一) | +| s | eg:RocketMQ | 用户标识(必填) | +| r | eg:topic=TestTopic | 资源描述,格式为 topic=xxx 或 group=xxx(必填) | +| a | eg:PUB | 允许的操作:PUB、SUB、PUB\|SUB(必填) | +| d | eg:ALLOW | 决策类型:ALLOW 或 DENY(必填) | +| i | eg:192.168.0.* | 源IP白名单(可选) | -| 参数 | 取值 | 含义 | -| --- | --- | --- | -| n | eg:192.168.1.2:9876 | namesrv地址(必填) | -| c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | -| b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | -| a | eg:RocketMQ | Access Key的值(必填) | +### 7.2 更新ACL规则 +更新已有的ACL规则,参数与 createAcl 相同。 + +```shell +sh mqadmin updateAcl -n 192.168.1.2:9876 -c DefaultCluster -s RocketMQ -r topic=TestTopic -a “PUB|SUB” -d ALLOW +``` -### 7.3 更新ACL配置文件里面中的全局白名单 -该命令的示例如下: +### 7.3 删除ACL规则 -sh mqadmin updateGlobalWhiteAddr -n 192.168.1.2:9876 -b 192.168.12.134:10911 -g 10.10.154.1,10.10.154.2 +删除指定用户的所有ACL规则,或删除指定用户在某资源上的规则。 -说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 -其中,参数"g"为全局IP白名的值,用以更新ACL配置文件中的“globalWhiteRemoteAddresses”字段的属性值。 +```shell +# 删除用户的所有ACL规则 +sh mqadmin deleteAcl -n 192.168.1.2:9876 -c DefaultCluster -s RocketMQ + +# 删除用户在某资源上的ACL规则 +sh mqadmin deleteAcl -n 192.168.1.2:9876 -c DefaultCluster -s RocketMQ -r topic=TestTopic +``` | 参数 | 取值 | 含义 | | --- | --- | --- | -| n | eg:192.168.1.2:9876 | namesrv地址(必填) | -| c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | -| b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | -| g | eg:10.10.154.1,10.10.154.2 | 全局IP白名单(必填) | +| n | eg:192.168.1.2:9876 | namesrv地址 | +| c | eg:DefaultCluster | 目标集群名称(与-b二选一) | +| b | eg:192.168.1.2:10911 | 目标broker地址(与-c二选一) | +| s | eg:RocketMQ | 用户标识(必填) | +| r | eg:topic=TestTopic | 资源描述,不填则删除该用户的所有规则(可选) | -### 7.4 查询集群/Broker的ACL配置文件版本信息 -该命令的示例如下: +### 7.4 查询ACL规则 -sh mqadmin clusterAclConfigVersion -n 192.168.1.2:9876 -c DefaultCluster +查询指定用户的ACL规则。 -说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 +```shell +sh mqadmin getAcl -n 192.168.1.2:9876 -c DefaultCluster -s RocketMQ +``` | 参数 | 取值 | 含义 | | --- | --- | --- | -| n | eg:192.168.1.2:9876 | namesrv地址(必填) | -| c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | -| b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | +| n | eg:192.168.1.2:9876 | namesrv地址 | +| c | eg:DefaultCluster | 目标集群名称(与-b二选一) | +| b | eg:192.168.1.2:10911 | 目标broker地址(与-c二选一) | +| s | eg:RocketMQ | 用户标识,不填则查询所有用户(可选) | + +### 7.5 列出ACL规则 -### 7.5 查询集群/Broker的ACL配置文件全部内容 -该命令的示例如下: +列出集群或Broker上的所有ACL规则,可按用户或资源过滤。 -sh mqadmin getAclConfig -n 192.168.1.2:9876 -c DefaultCluster +```shell +# 列出所有ACL +sh mqadmin listAcl -n 192.168.1.2:9876 -c DefaultCluster -说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 +# 按用户过滤 +sh mqadmin listAcl -n 192.168.1.2:9876 -c DefaultCluster -s RocketMQ + +# 按资源过滤 +sh mqadmin listAcl -n 192.168.1.2:9876 -c DefaultCluster -r topic=TestTopic +``` | 参数 | 取值 | 含义 | | --- | --- | --- | -| n | eg:192.168.1.2:9876 | namesrv地址(必填) | -| c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) | -| b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) | +| n | eg:192.168.1.2:9876 | namesrv地址 | +| c | eg:DefaultCluster | 目标集群名称(与-b二选一) | +| b | eg:192.168.1.2:10911 | 目标broker地址(与-c二选一) | +| s | eg:RocketMQ | 按用户过滤(可选) | +| r | eg:topic=TestTopic | 按资源过滤(可选) | + +### 7.6 复制ACL规则 + +将一个Broker上的ACL规则复制到另一个Broker。 -**特别注意**开启Acl鉴权认证后导致Master/Slave和Dledger模式下Broker同步数据异常的问题, -在社区[4.5.1]版本中已经修复,具体的PR链接为:#1149。 +```shell +sh mqadmin copyAcl -n 192.168.1.2:9876 -f 192.168.1.2:10911 -t 192.168.1.3:10911 +``` + +| 参数 | 取值 | 含义 | +| --- | --- | --- | +| n | eg:192.168.1.2:9876 | namesrv地址 | +| f | eg:192.168.1.2:10911 | 源Broker地址(必填) | +| t | eg:192.168.1.3:10911 | 目标Broker地址(必填) | +| s | eg:RocketMQ,testuser | 要复制的用户列表,逗号分隔(可选) | diff --git a/filter/src/main/java/org/apache/rocketmq/filter/util/BitsArray.java b/filter/src/main/java/org/apache/rocketmq/filter/util/BitsArray.java index 986685490e1..7a83a689a8c 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/util/BitsArray.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/util/BitsArray.java @@ -195,7 +195,7 @@ protected int position(int bitPos) { protected void checkBytePosition(int bytePos, BitsArray bitsArray) { checkInitialized(bitsArray); - if (bytePos > bitsArray.byteLength()) { + if (bytePos >= bitsArray.byteLength()) { throw new IllegalArgumentException("BytePos is greater than " + bytes.length); } if (bytePos < 0) { @@ -205,7 +205,7 @@ protected void checkBytePosition(int bytePos, BitsArray bitsArray) { protected void checkBitPosition(int bitPos, BitsArray bitsArray) { checkInitialized(bitsArray); - if (bitPos > bitsArray.bitLength()) { + if (bitPos >= bitsArray.bitLength()) { throw new IllegalArgumentException("BitPos is greater than " + bitLength); } if (bitPos < 0) { diff --git a/filter/src/test/java/org/apache/rocketmq/filter/util/BitsArrayTest.java b/filter/src/test/java/org/apache/rocketmq/filter/util/BitsArrayTest.java new file mode 100644 index 00000000000..e0b6558e5c8 --- /dev/null +++ b/filter/src/test/java/org/apache/rocketmq/filter/util/BitsArrayTest.java @@ -0,0 +1,237 @@ +/* + * 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.rocketmq.filter.util; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; + +public class BitsArrayTest { + + @Test + public void testCreateWithBitLength() { + BitsArray bits = BitsArray.create(10); + assertEquals(10, bits.bitLength()); + assertEquals(2, bits.byteLength()); + } + + @Test + public void testCreateWithBitLengthExactByte() { + BitsArray bits = BitsArray.create(16); + assertEquals(16, bits.bitLength()); + assertEquals(2, bits.byteLength()); + } + + @Test + public void testCreateWithBytes() { + byte[] data = new byte[] {(byte) 0xFF, (byte) 0x0F}; + BitsArray bits = BitsArray.create(data); + assertEquals(16, bits.bitLength()); + assertArrayEquals(data, bits.bytes()); + assertNotSame(data, bits.bytes()); + } + + @Test + public void testCreateWithBytesAndBitLength() { + byte[] data = new byte[] {(byte) 0xAA}; + BitsArray bits = BitsArray.create(data, 10); + assertEquals(10, bits.bitLength()); + assertEquals(1, bits.byteLength()); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWithNullBytes() { + BitsArray.create((byte[]) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWithEmptyBytes() { + BitsArray.create(new byte[0]); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWithNullBytesAndBitLength() { + BitsArray.create(null, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWithZeroBitLength() { + BitsArray.create(new byte[] {0x01}, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWithBitLengthLessThanBytes() { + BitsArray.create(new byte[] {0x01, 0x02}, 8); + } + + @Test + public void testSetBitAndGetBit() { + BitsArray bits = BitsArray.create(16); + assertFalse(bits.getBit(0)); + assertFalse(bits.getBit(7)); + + bits.setBit(0, true); + assertTrue(bits.getBit(0)); + + bits.setBit(7, true); + assertTrue(bits.getBit(7)); + + bits.setBit(0, false); + assertFalse(bits.getBit(0)); + } + + @Test + public void testSetByteAndGetByte() { + BitsArray bits = BitsArray.create(16); + bits.setByte(0, (byte) 0x55); + assertEquals((byte) 0x55, bits.getByte(0)); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetBitOutOfRange() { + BitsArray bits = BitsArray.create(8); + bits.setBit(9, true); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBitOutOfRange() { + BitsArray bits = BitsArray.create(8); + bits.getBit(9); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetBitNegative() { + BitsArray bits = BitsArray.create(8); + bits.setBit(-1, true); + } + + @Test(expected = RuntimeException.class) + public void testCheckUninitialized() { + BitsArray bits = BitsArray.create(0); + bits.getBit(0); + } + + @Test + public void testXorTwoArrays() { + BitsArray a = BitsArray.create(new byte[] {(byte) 0xFF}); + BitsArray b = BitsArray.create(new byte[] {(byte) 0x0F}); + a.xor(b); + assertEquals((byte) 0xF0, a.getByte(0)); + } + + @Test + public void testXorBitPosition() { + BitsArray bits = BitsArray.create(8); + bits.setBit(0, true); + bits.xor(0, true); + assertFalse(bits.getBit(0)); + + bits.xor(0, true); + assertTrue(bits.getBit(0)); + } + + @Test + public void testOrTwoArrays() { + BitsArray a = BitsArray.create(new byte[] {(byte) 0xA0}); + BitsArray b = BitsArray.create(new byte[] {(byte) 0x0A}); + a.or(b); + assertEquals((byte) 0xAA, a.getByte(0)); + } + + @Test + public void testOrBitPosition() { + BitsArray bits = BitsArray.create(8); + bits.setBit(3, false); + bits.or(3, true); + assertTrue(bits.getBit(3)); + } + + @Test + public void testAndTwoArrays() { + BitsArray a = BitsArray.create(new byte[] {(byte) 0xFF}); + BitsArray b = BitsArray.create(new byte[] {(byte) 0x0F}); + a.and(b); + assertEquals((byte) 0x0F, a.getByte(0)); + } + + @Test + public void testAndBitPosition() { + BitsArray bits = BitsArray.create(8); + bits.setBit(1, true); + bits.and(1, false); + assertFalse(bits.getBit(1)); + } + + @Test + public void testNotBitPosition() { + BitsArray bits = BitsArray.create(8); + bits.setBit(5, false); + bits.not(5); + assertTrue(bits.getBit(5)); + bits.not(5); + assertFalse(bits.getBit(5)); + } + + @Test + public void testClone() { + BitsArray original = BitsArray.create(new byte[] {(byte) 0xAB}); + BitsArray cloned = original.clone(); + assertEquals(original.bitLength(), cloned.bitLength()); + assertArrayEquals(original.bytes(), cloned.bytes()); + assertNotSame(original, cloned); + assertNotSame(original.bytes(), cloned.bytes()); + } + + @Test + public void testToString() { + BitsArray bits = BitsArray.create(new byte[] {(byte) 0x03}); + String result = bits.toString(); + assertTrue(result.contains("1")); + assertFalse(result.contains("null")); + } + + @Test + public void testLargeBitArray() { + BitsArray bits = BitsArray.create(1024); + assertEquals(1024, bits.bitLength()); + assertEquals(128, bits.byteLength()); + + bits.setBit(1023, true); + assertTrue(bits.getBit(1023)); + + bits.setBit(0, true); + assertTrue(bits.getBit(0)); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetByteAtExactByteLengthThrowsIAE() { + BitsArray bits = BitsArray.create(8); + assertEquals(1, bits.byteLength()); + bits.getByte(1); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBitAtExactBitLengthThrowsIAE() { + BitsArray bits = BitsArray.create(8); + assertEquals(8, bits.bitLength()); + bits.getBit(8); + } +}