From 57c58de0adeab8f3717ac78e69a9470060df748c Mon Sep 17 00:00:00 2001 From: Albumen Kevin Date: Sun, 26 Feb 2023 18:30:32 +0800 Subject: [PATCH] Fix qos command remote check (#11506) * Fix qos command remote check * update * Fix NPE --- .../dubbo/qos/command/CommandContext.java | 8 +- .../dubbo/qos/common/QosConfiguration.java | 33 ++++++++ ...faultAnonymousAccessPermissionChecker.java | 27 +++++- .../handler/ForeignHostPermitHandler.java | 36 +++----- ...tAnonymousAccessPermissionCheckerTest.java | 83 +++++++++++++++++++ 5 files changed, 156 insertions(+), 31 deletions(-) create mode 100644 dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionCheckerTest.java diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java index a45560becd..fdd92514b1 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java @@ -16,10 +16,10 @@ */ package org.apache.dubbo.qos.command; -import io.netty.channel.Channel; -import org.apache.dubbo.qos.permission.PermissionLevel; import org.apache.dubbo.qos.common.QosConfiguration; +import io.netty.channel.Channel; + public class CommandContext { private String commandName; @@ -93,8 +93,8 @@ public class CommandContext { this.qosConfiguration = qosConfiguration; } - public boolean hasPermission(PermissionLevel cmdRequiredPermissionLevel) { - return cmdRequiredPermissionLevel.getLevel() <= qosConfiguration.getAnonymousAccessPermissionLevel().getLevel(); + public QosConfiguration getQosConfiguration() { + return qosConfiguration; } public boolean isAllowAnonymousAccess(){ diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/common/QosConfiguration.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/common/QosConfiguration.java index fee7486913..915022c2df 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/common/QosConfiguration.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/common/QosConfiguration.java @@ -16,6 +16,12 @@ */ package org.apache.dubbo.qos.common; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.function.Predicate; + +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.qos.permission.PermissionLevel; public class QosConfiguration { @@ -26,6 +32,8 @@ public class QosConfiguration { // support specific ip and an ip range from CIDR specification private String acceptForeignIpWhitelist; + private Predicate acceptForeignIpWhitelistPredicate; + // this permission level for anonymous access, it will ignore the acceptForeignIp and acceptForeignIpWhitelist configurations // Access permission depends on the config anonymousAccessPermissionLevel and the cmd required permission level // the default value is Cmd.PermissionLevel.PUBLIC, can only access PUBLIC level cmd @@ -39,6 +47,27 @@ public class QosConfiguration { this.acceptForeignIp = builder.isAcceptForeignIp(); this.acceptForeignIpWhitelist = builder.getAcceptForeignIpWhitelist(); this.anonymousAccessPermissionLevel = builder.getAnonymousAccessPermissionLevel(); + buildPredicate(); + } + + private void buildPredicate() { + if (StringUtils.isNotEmpty(acceptForeignIpWhitelist)) { + this.acceptForeignIpWhitelistPredicate = Arrays.stream(acceptForeignIpWhitelist.split(",")) + .map(String::trim) + .filter(StringUtils::isNotEmpty) + .map(foreignIpPattern -> (Predicate) foreignIp -> { + try { + // hard code port to -1 + return NetUtils.matchIpExpression(foreignIpPattern, foreignIp, -1); + } catch (UnknownHostException ignore) { + // ignore illegal CIDR specification + } + return false; + }) + .reduce(Predicate::or).orElse(s -> false); + } else { + this.acceptForeignIpWhitelistPredicate = foreignIp -> false; + } } public boolean isAllowAnonymousAccess() { @@ -57,6 +86,10 @@ public class QosConfiguration { return acceptForeignIpWhitelist; } + public Predicate getAcceptForeignIpWhitelistPredicate() { + return acceptForeignIpWhitelistPredicate; + } + public boolean isAcceptForeignIp() { return acceptForeignIp; } 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 ab5aa79e7b..02c5781a7e 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 @@ -17,12 +17,37 @@ package org.apache.dubbo.qos.permission; import org.apache.dubbo.qos.command.CommandContext; +import org.apache.dubbo.qos.common.QosConfiguration; + +import io.netty.channel.Channel; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Optional; public class DefaultAnonymousAccessPermissionChecker implements PermissionChecker { public static final DefaultAnonymousAccessPermissionChecker INSTANCE = new DefaultAnonymousAccessPermissionChecker(); @Override public boolean access(CommandContext commandContext, PermissionLevel defaultCmdRequiredPermissionLevel) { - return commandContext.hasPermission(defaultCmdRequiredPermissionLevel); + final InetAddress inetAddress = Optional.ofNullable(commandContext.getRemote()) + .map(Channel::remoteAddress) + .map(InetSocketAddress.class::cast) + .map(InetSocketAddress::getAddress) + .orElse(null); + + QosConfiguration qosConfiguration = commandContext.getQosConfiguration(); + PermissionLevel currentLevel = qosConfiguration.getAnonymousAccessPermissionLevel(); + + // Local has private permission + if (inetAddress != null && inetAddress.isLoopbackAddress()) { + currentLevel = PermissionLevel.PRIVATE; + } else if (inetAddress != null && + qosConfiguration.getAcceptForeignIpWhitelistPredicate() + .test(inetAddress.getHostAddress())) { + currentLevel = PermissionLevel.PROTECTED; + } + + return currentLevel.getLevel() >= defaultCmdRequiredPermissionLevel.getLevel(); } } diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/ForeignHostPermitHandler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/ForeignHostPermitHandler.java index e385c4d6aa..b5b63f35ac 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/ForeignHostPermitHandler.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/ForeignHostPermitHandler.java @@ -16,21 +16,19 @@ */ package org.apache.dubbo.qos.server.handler; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.function.Predicate; + +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.qos.common.QosConfiguration; +import org.apache.dubbo.qos.common.QosConstants; + import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; -import org.apache.dubbo.common.utils.NetUtils; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.qos.common.QosConstants; -import org.apache.dubbo.qos.common.QosConfiguration; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.function.Predicate; public class ForeignHostPermitHandler extends ChannelHandlerAdapter { @@ -40,7 +38,7 @@ public class ForeignHostPermitHandler extends ChannelHandlerAdapter { // the whitelist of foreign IP when acceptForeignIp = false, the delimiter is colon(,) // support specific ip and an ip range from CIDR specification private final String acceptForeignIpWhitelist; - private Predicate whitelistPredicate = foreignIp -> false; + private Predicate whitelistPredicate; private final QosConfiguration qosConfiguration; @@ -48,21 +46,7 @@ public class ForeignHostPermitHandler extends ChannelHandlerAdapter { this.qosConfiguration = qosConfiguration; this.acceptForeignIp = qosConfiguration.isAcceptForeignIp(); this.acceptForeignIpWhitelist = qosConfiguration.getAcceptForeignIpWhitelist(); - if (StringUtils.isNotEmpty(acceptForeignIpWhitelist)) { - whitelistPredicate = Arrays.stream(acceptForeignIpWhitelist.split(",")) - .map(String::trim) - .filter(StringUtils::isNotEmpty) - .map(foreignIpPattern -> (Predicate) foreignIp -> { - try { - // hard code port to -1 - return NetUtils.matchIpExpression(foreignIpPattern, foreignIp, -1); - } catch (UnknownHostException ignore) { - // ignore illegal CIDR specification - } - return false; - }) - .reduce(Predicate::or).orElse(s -> false); - } + this.whitelistPredicate = qosConfiguration.getAcceptForeignIpWhitelistPredicate(); } @Override 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 new file mode 100644 index 0000000000..bb6c80caec --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/permission/DefaultAnonymousAccessPermissionCheckerTest.java @@ -0,0 +1,83 @@ +/* + * 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.dubbo.qos.permission; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +import org.apache.dubbo.qos.command.CommandContext; +import org.apache.dubbo.qos.common.QosConfiguration; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import io.netty.channel.Channel; + +class DefaultAnonymousAccessPermissionCheckerTest { + @Test + void testPermission() throws UnknownHostException { + InetAddress inetAddress = InetAddress.getByName("127.0.0.1"); + + InetSocketAddress socketAddress = Mockito.mock(InetSocketAddress.class); + Mockito.when(socketAddress.getAddress()).thenReturn(inetAddress); + Channel channel = Mockito.mock(Channel.class); + Mockito.when(channel.remoteAddress()).thenReturn(socketAddress); + CommandContext commandContext = Mockito.mock(CommandContext.class); + Mockito.when(commandContext.getRemote()).thenReturn(channel); + + QosConfiguration qosConfiguration = Mockito.mock(QosConfiguration.class); + Mockito.when(qosConfiguration.getAnonymousAccessPermissionLevel()).thenReturn(PermissionLevel.PUBLIC); + Mockito.when(qosConfiguration.getAcceptForeignIpWhitelistPredicate()).thenReturn(ip -> false); + + Mockito.when(commandContext.getQosConfiguration()).thenReturn(qosConfiguration); + + DefaultAnonymousAccessPermissionChecker checker = new DefaultAnonymousAccessPermissionChecker(); + 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)); + + inetAddress = InetAddress.getByName("1.1.1.1"); + Mockito.when(socketAddress.getAddress()).thenReturn(inetAddress); + + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.NONE)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PUBLIC)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PROTECTED)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PRIVATE)); + + Mockito.when(qosConfiguration.getAnonymousAccessPermissionLevel()).thenReturn(PermissionLevel.PROTECTED); + + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.NONE)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PUBLIC)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PROTECTED)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PRIVATE)); + + Mockito.when(qosConfiguration.getAnonymousAccessPermissionLevel()).thenReturn(PermissionLevel.NONE); + + 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)); + + Mockito.when(qosConfiguration.getAcceptForeignIpWhitelistPredicate()).thenReturn(ip -> true); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.NONE)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PUBLIC)); + Assertions.assertTrue(checker.access(commandContext, PermissionLevel.PROTECTED)); + Assertions.assertFalse(checker.access(commandContext, PermissionLevel.PRIVATE)); + } +}