From 1b838281a44cd5afec07ef3cd69eb42a36d935c2 Mon Sep 17 00:00:00 2001 From: Albumen Kevin Date: Wed, 19 Apr 2023 10:13:09 +0800 Subject: [PATCH] QoS Support anonymous command allow list (#12082) * QoS Support anonymous command allow list * Fix test --- .../dubbo/common/constants/QosConstants.java | 2 ++ .../dubbo/config/ApplicationConfig.java | 15 ++++++++++++ .../src/main/resources/META-INF/dubbo.xsd | 6 +++++ .../dubbo/qos/api/QosConfiguration.java | 18 ++++++++++++++ ...faultAnonymousAccessPermissionChecker.java | 14 ++++++++++- .../qos/protocol/QosProtocolWrapper.java | 3 +++ .../org/apache/dubbo/qos/server/Server.java | 7 ++++++ ...tAnonymousAccessPermissionCheckerTest.java | 24 ++++++++++++++++++- 8 files changed, 87 insertions(+), 2 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/QosConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/QosConstants.java index e02ec74a65..29b1993ca6 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/QosConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/QosConstants.java @@ -34,6 +34,8 @@ public interface QosConstants { String ANONYMOUS_ACCESS_PERMISSION_LEVEL = "qos.anonymous.access.permission.level"; + String ANONYMOUS_ACCESS_ALLOW_COMMANDS = "qos.anonymous.access.allow.commands"; + String QOS_ENABLE_COMPATIBLE = "qos-enable"; String QOS_HOST_COMPATIBLE = "qos-host"; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java index 63dcfb5375..7336c6d144 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java @@ -55,6 +55,7 @@ import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP_COMPATIBLE; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP_WHITELIST; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP_WHITELIST_COMPATIBLE; +import static org.apache.dubbo.common.constants.QosConstants.ANONYMOUS_ACCESS_ALLOW_COMMANDS; import static org.apache.dubbo.common.constants.QosConstants.ANONYMOUS_ACCESS_PERMISSION_LEVEL; import static org.apache.dubbo.common.constants.QosConstants.ANONYMOUS_ACCESS_PERMISSION_LEVEL_COMPATIBLE; import static org.apache.dubbo.common.constants.QosConstants.QOS_ENABLE; @@ -170,6 +171,11 @@ public class ApplicationConfig extends AbstractConfig { */ private String qosAnonymousAccessPermissionLevel; + /** + * the anonymous(any foreign ip) allow commands, default is empty, can not access any cmd + */ + private String qosAnonymousAllowCommands; + /** * Customized parameters */ @@ -472,6 +478,15 @@ public class ApplicationConfig extends AbstractConfig { this.qosAnonymousAccessPermissionLevel = qosAnonymousAccessPermissionLevel; } + @Parameter(key = ANONYMOUS_ACCESS_ALLOW_COMMANDS) + public String getQosAnonymousAllowCommands() { + return qosAnonymousAllowCommands; + } + + public void setQosAnonymousAllowCommands(String qosAnonymousAllowCommands) { + this.qosAnonymousAllowCommands = qosAnonymousAllowCommands; + } + /** * The format is the same as the springboot, including: getQosEnableCompatible(), getQosPortCompatible(), getQosAcceptForeignIpCompatible(). * diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd index 8ba022c68f..141b5d5169 100644 --- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd +++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd @@ -547,6 +547,12 @@ + + + + + + diff --git a/dubbo-plugin/dubbo-qos-api/src/main/java/org/apache/dubbo/qos/api/QosConfiguration.java b/dubbo-plugin/dubbo-qos-api/src/main/java/org/apache/dubbo/qos/api/QosConfiguration.java index 476079aa14..69f9b10e19 100644 --- a/dubbo-plugin/dubbo-qos-api/src/main/java/org/apache/dubbo/qos/api/QosConfiguration.java +++ b/dubbo-plugin/dubbo-qos-api/src/main/java/org/apache/dubbo/qos/api/QosConfiguration.java @@ -38,6 +38,9 @@ public class QosConfiguration { // the default value is Cmd.PermissionLevel.PUBLIC, can only access PUBLIC level cmd private PermissionLevel anonymousAccessPermissionLevel = PermissionLevel.PUBLIC; + // the allow commands for anonymous access, the delimiter is colon(,) + private String anonymousAllowCommands; + private QosConfiguration() { } @@ -46,6 +49,7 @@ public class QosConfiguration { this.acceptForeignIp = builder.isAcceptForeignIp(); this.acceptForeignIpWhitelist = builder.getAcceptForeignIpWhitelist(); this.anonymousAccessPermissionLevel = builder.getAnonymousAccessPermissionLevel(); + this.anonymousAllowCommands = builder.getAnonymousAllowCommands(); buildPredicate(); } @@ -93,6 +97,10 @@ public class QosConfiguration { return acceptForeignIp; } + public String getAnonymousAllowCommands() { + return anonymousAllowCommands; + } + public static Builder builder() { return new Builder(); } @@ -103,6 +111,7 @@ public class QosConfiguration { private boolean acceptForeignIp; private String acceptForeignIpWhitelist; private PermissionLevel anonymousAccessPermissionLevel = PermissionLevel.PUBLIC; + private String anonymousAllowCommands; private Builder() { } @@ -127,6 +136,11 @@ public class QosConfiguration { return this; } + public Builder anonymousAllowCommands(String anonymousAllowCommands) { + this.anonymousAllowCommands = anonymousAllowCommands; + return this; + } + public QosConfiguration build() { return new QosConfiguration(this); } @@ -146,5 +160,9 @@ public class QosConfiguration { public PermissionLevel getAnonymousAccessPermissionLevel() { return anonymousAccessPermissionLevel; } + + public String getAnonymousAllowCommands() { + return anonymousAllowCommands; + } } } diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionChecker.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionChecker.java index 8efd756e00..61736f2247 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionChecker.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionChecker.java @@ -16,13 +16,16 @@ */ package org.apache.dubbo.qos.permission; -import io.netty.channel.Channel; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.qos.api.CommandContext; import org.apache.dubbo.qos.api.PermissionLevel; import org.apache.dubbo.qos.api.QosConfiguration; +import io.netty.channel.Channel; + import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.Optional; public class DefaultAnonymousAccessPermissionChecker implements PermissionChecker { @@ -37,6 +40,15 @@ public class DefaultAnonymousAccessPermissionChecker implements PermissionChecke .orElse(null); QosConfiguration qosConfiguration = commandContext.getQosConfiguration(); + String anonymousAllowCommands = qosConfiguration.getAnonymousAllowCommands(); + if (StringUtils.isNotEmpty(anonymousAllowCommands) && + Arrays.stream(anonymousAllowCommands.split(",")) + .filter(StringUtils::isNotEmpty) + .map(String::trim) + .anyMatch(cmd -> cmd.equals(commandContext.getCommandName()))) { + return true; + } + PermissionLevel currentLevel = qosConfiguration.getAnonymousAccessPermissionLevel(); // Local has private permission diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java index ef2f35c3dd..127fa8bb42 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/protocol/QosProtocolWrapper.java @@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static org.apache.dubbo.common.constants.LoggerCodeConstants.QOS_FAILED_START_SERVER; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP_WHITELIST; +import static org.apache.dubbo.common.constants.QosConstants.ANONYMOUS_ACCESS_ALLOW_COMMANDS; import static org.apache.dubbo.common.constants.QosConstants.ANONYMOUS_ACCESS_PERMISSION_LEVEL; import static org.apache.dubbo.common.constants.QosConstants.QOS_ENABLE; import static org.apache.dubbo.common.constants.QosConstants.QOS_HOST; @@ -119,6 +120,7 @@ public class QosProtocolWrapper implements Protocol, ScopeModelAware { boolean acceptForeignIp = Boolean.parseBoolean(url.getParameter(ACCEPT_FOREIGN_IP, "false")); String acceptForeignIpWhitelist = url.getParameter(ACCEPT_FOREIGN_IP_WHITELIST, StringUtils.EMPTY_STRING); String anonymousAccessPermissionLevel = url.getParameter(ANONYMOUS_ACCESS_PERMISSION_LEVEL, PermissionLevel.PUBLIC.name()); + String anonymousAllowCommands = url.getParameter(ANONYMOUS_ACCESS_ALLOW_COMMANDS, StringUtils.EMPTY_STRING); Server server = frameworkModel.getBeanFactory().getBean(Server.class); if (server.isStarted()) { @@ -130,6 +132,7 @@ public class QosProtocolWrapper implements Protocol, ScopeModelAware { server.setAcceptForeignIp(acceptForeignIp); server.setAcceptForeignIpWhitelist(acceptForeignIpWhitelist); server.setAnonymousAccessPermissionLevel(anonymousAccessPermissionLevel); + server.setAnonymousAllowCommands(anonymousAllowCommands); server.start(); } catch (Throwable throwable) { diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/Server.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/Server.java index 4dc03d1cd7..65e7e3b9fa 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/Server.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/Server.java @@ -58,6 +58,8 @@ public class Server { private String anonymousAccessPermissionLevel = PermissionLevel.NONE.name(); + private String anonymousAllowCommands = StringUtils.EMPTY_STRING; + private EventLoopGroup boss; private EventLoopGroup worker; @@ -108,6 +110,7 @@ public class Server { .acceptForeignIp(acceptForeignIp) .acceptForeignIpWhitelist(acceptForeignIpWhitelist) .anonymousAccessPermissionLevel(anonymousAccessPermissionLevel) + .anonymousAllowCommands(anonymousAllowCommands) .build() )); } @@ -167,6 +170,10 @@ public class Server { this.anonymousAccessPermissionLevel = anonymousAccessPermissionLevel; } + public void setAnonymousAllowCommands(String anonymousAllowCommands) { + this.anonymousAllowCommands = anonymousAllowCommands; + } + public String getWelcome() { return welcome; } diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionCheckerTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionCheckerTest.java index 11f466ca39..eef2013fde 100644 --- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionCheckerTest.java +++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionCheckerTest.java @@ -16,10 +16,11 @@ */ package org.apache.dubbo.qos.permission; -import io.netty.channel.Channel; import org.apache.dubbo.qos.api.CommandContext; import org.apache.dubbo.qos.api.PermissionLevel; import org.apache.dubbo.qos.api.QosConfiguration; + +import io.netty.channel.Channel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -79,5 +80,26 @@ class DefaultAnonymousAccessPermissionCheckerTest { Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PUBLIC)); Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PROTECTED)); Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PRIVATE)); + + Mockito.when(qosConfiguration.getAcceptForeignIpWhitelistPredicate()).thenReturn(ip -> false); + Mockito.when(qosConfiguration.getAnonymousAllowCommands()).thenReturn("test1,test2"); + + Mockito.when(commandContext.getCommandName()).thenReturn("test1"); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.NONE)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PUBLIC)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PROTECTED)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PRIVATE)); + + Mockito.when(commandContext.getCommandName()).thenReturn("test2"); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.NONE)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PUBLIC)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PROTECTED)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PRIVATE)); + + Mockito.when(commandContext.getCommandName()).thenReturn("test"); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.NONE)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PUBLIC)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PROTECTED)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PRIVATE)); } }