diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java index be68810f4a..b081f9a530 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java @@ -147,6 +147,10 @@ public abstract class AbstractDirectory implements Directory { this(url, null, isUrlFromRegistry); } + public AbstractDirectory(URL url, RouterChain routerChain, boolean isUrlFromRegistry, URL consumerUrl) { + this(addConsumerUrl(url, consumerUrl), null, isUrlFromRegistry); + } + public AbstractDirectory(URL url, RouterChain routerChain, boolean isUrlFromRegistry) { if (url == null) { throw new IllegalArgumentException("url == null"); @@ -252,6 +256,13 @@ public abstract class AbstractDirectory implements Directory { } } + private static URL addConsumerUrl(URL url, URL consumerUrl) { + Map referMap = new HashMap<>(); + referMap.put(CONSUMER_URL_KEY, consumerUrl.toString()); + url = url.putAttribute(REFER_KEY, referMap); + return url; + } + @Override public URL getUrl() { return url; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/AuthPolicy.java b/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/AuthPolicy.java index a8841e61d5..99f843607b 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/AuthPolicy.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/AuthPolicy.java @@ -19,5 +19,6 @@ package org.apache.dubbo.common.ssl; public enum AuthPolicy { NONE, SERVER_AUTH, - CLIENT_AUTH + CLIENT_AUTH_STRICT, + CLIENT_AUTH_PERMISSIVE } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/impl/SSLConfigCertProvider.java b/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/impl/SSLConfigCertProvider.java index 23923c45ed..76b0efcddf 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/impl/SSLConfigCertProvider.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/ssl/impl/SSLConfigCertProvider.java @@ -59,7 +59,7 @@ public class SSLConfigCertProvider implements CertProvider { ? IOUtils.toByteArray(sslConfig.getServerTrustCertCollectionPathStream()) : null, sslConfig.getServerKeyPassword(), - AuthPolicy.CLIENT_AUTH); + AuthPolicy.CLIENT_AUTH_STRICT); } catch (IOException e) { logger.warn( LoggerCodeConstants.CONFIG_SSL_PATH_LOAD_FAILED, diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java b/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java index 822d81e934..20f0037699 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java @@ -16,6 +16,10 @@ */ package org.apache.dubbo.config; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP_COMPATIBLE; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP_WHITELIST_COMPATIBLE; import static org.apache.dubbo.common.constants.QosConstants.QOS_ENABLE_COMPATIBLE; @@ -156,4 +160,15 @@ public interface Constants { String DEFAULT_NATIVE_PROXY = "jdk"; String DEFAULT_APP_NAME = "DEFAULT_DUBBO_APP"; + + String MESH_KEY = "mesh"; + String SECURITY_KEY = "security"; + String XDS_CLUSTER_KEY = "cluster"; + String XDS_CLUSTER_VALUE = "xds"; + + Set SUPPORT_MESH_TYPE = new HashSet() { + { + addAll(Arrays.asList("istio")); + } + }; } diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml index 4fdaa61842..12490c4d9e 100644 --- a/dubbo-config/dubbo-config-api/pom.xml +++ b/dubbo-config/dubbo-config-api/pom.xml @@ -251,10 +251,5 @@ resteasy-jackson-provider test - - org.apache.dubbo - dubbo-xds - ${project.parent.version} - diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java index 9cdf9bde49..dd2c65eea0 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java @@ -53,7 +53,6 @@ import org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol; import org.apache.dubbo.rpc.service.GenericService; import org.apache.dubbo.rpc.stub.StubSuppliers; import org.apache.dubbo.rpc.support.ProtocolUtils; -import org.apache.dubbo.xds.PilotExchanger; import java.beans.Transient; import java.util.ArrayList; @@ -477,7 +476,8 @@ public class ReferenceConfig extends ReferenceConfigBase { private T createProxy(Map referenceParameters) { urls.clear(); - meshModeHandleUrl(referenceParameters); + // TODO: Maybe not need this logic. + // meshModeHandleUrl(referenceParameters); if (StringUtils.isNotEmpty(url)) { // user specified URL, could be peer-to-peer address, or register center's address. @@ -631,6 +631,7 @@ public class ReferenceConfig extends ReferenceConfigBase { if (isInjvm() != null && isInjvm()) { u = u.addParameter(LOCAL_PROTOCOL, true); } + ConfigValidationUtils.loadMeshConfig(u, referenceParameters); urls.add(u.putAttribute(REFER_KEY, referenceParameters)); } } @@ -656,16 +657,6 @@ public class ReferenceConfig extends ReferenceConfigBase { private void createInvoker() { if (urls.size() == 1) { URL curUrl = urls.get(0); - - if (curUrl.getParameter("registry", "null").startsWith("xds")) { - // TODO: The PilotExchanger requests xds resources asynchronously, - // and the xdsDirectory call filter chain may have an exception with invoker null, - // which needs to be synchronized later. - // move to deployer - curUrl = curUrl.addParameter("xds", true); - PilotExchanger.initialize(curUrl); - } - invoker = protocolSPI.refer(interfaceClass, curUrl); // registry url, mesh-enable and unloadClusterRelated is true, not need Cluster. if (!UrlUtils.isRegistry(curUrl) && !curUrl.getParameter(UNLOAD_CLUSTER_RELATED, false)) { diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java index 4b13e2dcfc..b5dfe8d220 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java @@ -607,6 +607,10 @@ public class ServiceConfig extends ServiceConfigBase { ProtocolConfig protocolConfig, List registryURLs, RegisterTypeEnum registerType) { Map map = buildAttributes(protocolConfig); + for (URL u : registryURLs) { + ConfigValidationUtils.loadMeshConfig(u, map); + } + // remove null key and null value map.keySet().removeIf(key -> StringUtils.isEmpty(key) || StringUtils.isEmpty(map.get(key))); // init serviceMetadata attachments diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java index 810e862cd3..4c440f0e5b 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java @@ -37,6 +37,7 @@ import org.apache.dubbo.config.AbstractConfig; import org.apache.dubbo.config.AbstractInterfaceConfig; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ConfigCenterConfig; +import org.apache.dubbo.config.Constants; import org.apache.dubbo.config.ConsumerConfig; import org.apache.dubbo.config.MetadataReportConfig; import org.apache.dubbo.config.MethodConfig; @@ -126,6 +127,8 @@ import static org.apache.dubbo.config.Constants.ORGANIZATION; import static org.apache.dubbo.config.Constants.OWNER; import static org.apache.dubbo.config.Constants.REGISTER_KEY; import static org.apache.dubbo.config.Constants.STATUS_KEY; +import static org.apache.dubbo.config.Constants.XDS_CLUSTER_KEY; +import static org.apache.dubbo.config.Constants.XDS_CLUSTER_VALUE; import static org.apache.dubbo.monitor.Constants.LOGSTAT_PROTOCOL; import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; import static org.apache.dubbo.registry.Constants.SUBSCRIBE_KEY; @@ -219,7 +222,6 @@ public class ConfigValidationUtils { map.put(PROTOCOL_KEY, DUBBO_PROTOCOL); } List urls = UrlUtils.parseURLs(address, map); - for (URL url : urls) { url = URLBuilder.from(url) .addParameter(REGISTRY_KEY, url.getProtocol()) @@ -240,6 +242,17 @@ public class ConfigValidationUtils { return genCompatibleRegistries(interfaceConfig.getScopeModel(), registryList, provider); } + public static void loadMeshConfig(URL url, Map map) { + String registry = url.getParameter(REGISTRY_KEY); + if (StringUtils.isNotEmpty(registry) && Constants.SUPPORT_MESH_TYPE.contains(registry)) { + map.put(Constants.MESH_KEY, registry); + map.put(XDS_CLUSTER_KEY, XDS_CLUSTER_VALUE); + if (url.hasParameter(Constants.SECURITY_KEY)) { + map.put(Constants.SECURITY_KEY, url.getParameter(Constants.SECURITY_KEY)); + } + } + } + private static List genCompatibleRegistries(ScopeModel scopeModel, List registryList, boolean provider) { List result = new ArrayList<>(registryList.size()); registryList.forEach(registryURL -> { diff --git a/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml b/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml index bfe5d422ce..ff2b42a3fe 100644 --- a/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml +++ b/dubbo-demo/dubbo-demo-api/dubbo-demo-api-consumer/pom.xml @@ -98,6 +98,11 @@ dubbo-serialization-fastjson2 ${project.version} + + org.apache.dubbo + dubbo-plugin-cluster-mergeable + ${project.version} + org.apache.logging.log4j log4j-slf4j-impl diff --git a/dubbo-demo/dubbo-demo-interface/src/main/java/org/apache/dubbo/demo/RestDemoService.java b/dubbo-demo/dubbo-demo-interface/src/main/java/org/apache/dubbo/demo/RestDemoService.java index 0405b739f2..f12536bfd1 100644 --- a/dubbo-demo/dubbo-demo-interface/src/main/java/org/apache/dubbo/demo/RestDemoService.java +++ b/dubbo-demo/dubbo-demo-interface/src/main/java/org/apache/dubbo/demo/RestDemoService.java @@ -65,7 +65,7 @@ public interface RestDemoService { Boolean testBody2(Boolean b); @POST - @Path("/testBody4") + @Path("/testBody3") @Consumes({MediaType.TEXT_PLAIN}) TestPO testBody2(TestPO b); diff --git a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml index ae8b187b0b..f48c27881d 100644 --- a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml +++ b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml @@ -30,7 +30,7 @@ true 5.1.0 - 3.8.4 + 3.8.3 @@ -157,7 +157,7 @@ ch.qos.logback logback-core - 1.5.3 + 1.4.14 compile @@ -204,7 +204,7 @@ org.graalvm.buildtools native-maven-plugin - 0.10.1 + 0.10.0 ${project.build.outputDirectory} diff --git a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml index d57cfa6033..c705425d54 100644 --- a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml +++ b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml @@ -30,7 +30,7 @@ true 5.1.0 - 3.8.4 + 3.8.3 @@ -157,7 +157,7 @@ ch.qos.logback logback-core - 1.5.3 + 1.4.14 compile @@ -204,7 +204,7 @@ org.graalvm.buildtools native-maven-plugin - 0.10.1 + 0.10.0 ${project.build.outputDirectory} diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml index c0891cdce2..9179bd6ff6 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/pom.xml @@ -37,12 +37,6 @@ ${project.parent.version} - - org.apache.dubbo - dubbo-xds - ${project.version} - - org.apache.dubbo dubbo-registry-zookeeper @@ -107,12 +101,6 @@ org.springframework.boot spring-boot-starter - - - org.springframework.boot - spring-boot-starter-logging - - diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/src/main/resources/application.yml index 20bee1f724..10d0ec74e7 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/src/main/resources/application.yml +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-consumer/src/main/resources/application.yml @@ -32,4 +32,7 @@ dubbo: metadata-report: address: zookeeper://127.0.0.1:2181 +logging: + pattern: + level: '%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]' diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-interface/src/main/java/org/apache/dubbo/springboot/demo/DemoService2.java b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-interface/src/main/java/org/apache/dubbo/springboot/demo/DemoService2.java new file mode 100644 index 0000000000..75a5c18d55 --- /dev/null +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-interface/src/main/java/org/apache/dubbo/springboot/demo/DemoService2.java @@ -0,0 +1,21 @@ +/* + * 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.springboot.demo; + +public interface DemoService2 { + String sayHello(String name); +} diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml index 2c0d523449..cb5280bb62 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/pom.xml @@ -31,6 +31,18 @@ + + org.apache.dubbo + dubbo-rpc-triple + ${project.parent.version} + + + + org.apache.dubbo + dubbo-xds + ${project.parent.version} + + org.apache.dubbo dubbo-demo-spring-boot-interface diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/DemoServiceImpl.java b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/DemoServiceImpl.java index 6468826fd0..f95197e81f 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/DemoServiceImpl.java +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/DemoServiceImpl.java @@ -23,7 +23,7 @@ import org.apache.dubbo.springboot.demo.DemoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@DubboService +@DubboService(parameters = {"security", "mTLS,sa_jwt"}) public class DemoServiceImpl implements DemoService { private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class); diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/FooApplication.java b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/FooApplication.java new file mode 100644 index 0000000000..b96af3da25 --- /dev/null +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/FooApplication.java @@ -0,0 +1,66 @@ +/* + * 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.springboot.demo.provider; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.apache.dubbo.springboot.demo.DemoService2; +import org.apache.dubbo.xds.istio.IstioConstant; + +import java.util.Scanner; +import java.util.concurrent.CountDownLatch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication +@EnableDubbo(scanBasePackages = {"org.apache.dubbo.springboot.demo.provider"}) +public class FooApplication { + + // @DubboReference(cluster = "xds", providedBy = "httpbin") + @DubboReference( + lazy = true, + parameters = {"security", "mTLS,sa_jwt"}) + private DemoService2 demoService; + + public static void main(String[] args) throws Exception { + System.setProperty(IstioConstant.WORKLOAD_NAMESPACE_KEY, "foo"); + IstioConstant.KUBERNETES_SA_PATH = "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"; + System.setProperty(IstioConstant.SERVICE_NAME_KEY, "httpbin"); + + System.setProperty("NAMESPACE", "foo"); + System.setProperty("SERVICE_NAME", "httpbin"); + System.setProperty("API_SERVER_PATH", "https://127.0.0.1:6443"); + System.setProperty("SA_CA_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + System.setProperty( + "SA_TOKEN_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"); + + ConfigurableApplicationContext context = SpringApplication.run(FooApplication.class, args); + FooApplication application = context.getBean(FooApplication.class); + application.sayHello(); + new CountDownLatch(1).await(); + } + + public void sayHello() throws InterruptedException { + new Scanner(System.in).nextLine(); + while (true) { + Thread.sleep(2000); + System.out.println(demoService.sayHello("hello from foo")); + } + } +} diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml index b1789be60f..4b4fb29cb9 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml +++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml @@ -22,22 +22,9 @@ dubbo: application: name: ${spring.application.name} protocol: - name: dubbo - port: -1 + name: tri + port: 12346 + host: 192.168.0.103 registry: - id: zk-registry - address: zookeeper://127.0.0.1:2181 - config-center: - address: zookeeper://127.0.0.1:2181 - metadata-report: - address: zookeeper://127.0.0.1:2181 - metrics: - protocol: prometheus - enable-jvm: true - enable-registry: true - aggregation: - enabled: true - prometheus: - exporter: - enabled: true - enable-metadata: true + id: isitod + address: istio://istiod.istio-system.svc:15012?security=mTLS diff --git a/dubbo-demo/dubbo-demo-spring-boot/pom.xml b/dubbo-demo/dubbo-demo-spring-boot/pom.xml index 1075039282..90860bd948 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/pom.xml +++ b/dubbo-demo/dubbo-demo-spring-boot/pom.xml @@ -35,7 +35,7 @@ true 2.7.18 2.7.18 - 1.12.4 + 1.12.2 diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/Dockerfile b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/Dockerfile new file mode 100644 index 0000000000..a5b9420665 --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/Dockerfile @@ -0,0 +1,18 @@ +# 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. + +FROM openjdk:8-jdk +ADD ./target/dubbo-demo-xds-consumer-3.3.0-beta.2-SNAPSHOT.jar dubbo-demo-xds-consumer-3.3.0-beta.2-SNAPSHOT.jar +CMD java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=31000 /dubbo-demo-xds-consumer-3.3.0-beta.2-SNAPSHOT.jar diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml new file mode 100644 index 0000000000..74359e00be --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml @@ -0,0 +1,146 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-demo-xds + ${revision} + ../pom.xml + + + dubbo-demo-xds-consumer + + + true + + + + + org.apache.dubbo + dubbo-rpc-triple + ${project.parent.version} + + + org.apache.dubbo + dubbo-demo-xds-interface + ${project.parent.version} + + + + org.apache.dubbo + dubbo-xds + ${project.version} + + + + org.apache.dubbo + dubbo-registry-zookeeper + ${project.version} + + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${project.version} + + + + org.apache.dubbo + dubbo-metadata-report-zookeeper + ${project.version} + + + + org.apache.dubbo + dubbo-config-spring + ${project.version} + + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.version} + + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.version} + + + + org.apache.dubbo + dubbo-serialization-fastjson2 + ${project.version} + + + + + org.apache.dubbo + dubbo-tracing-otel-zipkin-spring-boot-starter + ${project.version} + + + + org.apache.dubbo + dubbo-qos + ${project.version} + + + + org.apache.dubbo + dubbo-spring-boot-starter + ${project.version} + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + + + repackage + + + + + + + + diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java new file mode 100644 index 0000000000..4030a361ea --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java @@ -0,0 +1,60 @@ +/* + * 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.xds.demo.consumer; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.apache.dubbo.xds.demo.DemoService; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Service; + +@SpringBootApplication +@Service +@EnableDubbo +public class XdsConsumerApplication { + @DubboReference(providedBy = "dubbo-demo-xds-provider") + private DemoService demoService; + + public static void main(String[] args) throws InterruptedException { + // System.setProperty(IstioConstant.WORKLOAD_NAMESPACE_KEY, "dubbo-demo"); + // // System.setProperty("API_SERVER_PATH", "https://127.0.0.1:6443"); + // System.setProperty("SA_CA_PATH", "/Users/smzdm/hjf/xds/resources/ca.crt"); + // System.setProperty("SA_TOKEN_PATH", "/Users/smzdm/hjf/xds/resources/token"); + // System.setProperty("NAMESPACE", "dubbo-demo"); + // IstioConstant.KUBERNETES_SA_PATH = "/Users/smzdm/hjf/xds/resources/token"; + // System.setProperty(IstioConstant.PILOT_CERT_PROVIDER_KEY, "istiod"); + ConfigurableApplicationContext context = SpringApplication.run(XdsConsumerApplication.class, args); + XdsConsumerApplication application = context.getBean(XdsConsumerApplication.class); + while (true) { + try { + String result = application.doSayHello("world"); + System.out.println("result: " + result); + + } catch (Exception e) { + e.printStackTrace(); + } + Thread.sleep(2000); + } + } + + public String doSayHello(String name) { + return demoService.sayHello(name); + } +} diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml new file mode 100644 index 0000000000..20b035f70f --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml @@ -0,0 +1,35 @@ +# 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. + +spring: + application: + name: dubbo-demo-xds-consumer + +dubbo: + application: + name: ${spring.application.name} +# qos-enable: false + protocol: + name: tri + port: 50051 + registry: + address: istio://istiod.istio-system.svc:15012?security=mTLS # istio://istiod.istio-system.svc:15012 +# config-center: +# address: zookeeper://127.0.0.1:2181 +# metadata-report: +# address: zookeeper://127.0.0.1:2181 + + diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-interface/pom.xml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-interface/pom.xml new file mode 100644 index 0000000000..616941a5e7 --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-interface/pom.xml @@ -0,0 +1,33 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-demo-xds + ${revision} + ../pom.xml + + + dubbo-demo-xds-interface + + + true + + + diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-interface/src/main/java/org/apache/dubbo/xds/demo/DemoService.java b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-interface/src/main/java/org/apache/dubbo/xds/demo/DemoService.java new file mode 100644 index 0000000000..247fad7baf --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-interface/src/main/java/org/apache/dubbo/xds/demo/DemoService.java @@ -0,0 +1,22 @@ +/* + * 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.xds.demo; + +public interface DemoService { + + String sayHello(String name); +} diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/Dockerfile b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/Dockerfile new file mode 100644 index 0000000000..bbb7309c28 --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/Dockerfile @@ -0,0 +1,18 @@ +# 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. + +FROM openjdk:8-jdk +ADD ./target/dubbo-demo-xds-provider-3.3.0-beta.2-SNAPSHOT.jar dubbo-demo-xds-provider-3.3.0-beta.2-SNAPSHOT.jar +CMD java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=31001 /dubbo-demo-xds-provider-3.3.0-beta.2-SNAPSHOT.jar diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/pom.xml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/pom.xml new file mode 100644 index 0000000000..79f70e7999 --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/pom.xml @@ -0,0 +1,147 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-demo-xds + ${revision} + ../pom.xml + + + dubbo-demo-xds-provider + + + true + + + + + org.apache.dubbo + dubbo-rpc-triple + ${project.parent.version} + + + + org.apache.dubbo + dubbo-xds + ${project.parent.version} + + + + org.apache.dubbo + dubbo-demo-xds-interface + ${project.parent.version} + + + + org.apache.dubbo + dubbo-registry-zookeeper + ${project.version} + + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${project.version} + + + + org.apache.dubbo + dubbo-metadata-report-zookeeper + ${project.version} + + + + org.apache.dubbo + dubbo-config-spring + ${project.version} + + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.version} + + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.version} + + + + org.apache.dubbo + dubbo-serialization-fastjson2 + ${project.version} + + + + + org.apache.dubbo + dubbo-tracing-otel-zipkin-spring-boot-starter + ${project.version} + + + + org.apache.dubbo + dubbo-qos + ${project.version} + + + + org.apache.dubbo + dubbo-spring-boot-starter + ${project.version} + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + + + repackage + + + + + + + + diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/java/org/apache/dubbo/xds/demo/provider/DemoServiceImpl.java b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/java/org/apache/dubbo/xds/demo/provider/DemoServiceImpl.java new file mode 100644 index 0000000000..63f5bb58a0 --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/java/org/apache/dubbo/xds/demo/provider/DemoServiceImpl.java @@ -0,0 +1,37 @@ +/* + * 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.xds.demo.provider; + +import org.apache.dubbo.config.annotation.DubboService; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.xds.demo.DemoService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@DubboService +public class DemoServiceImpl implements DemoService { + + private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class); + + @Override + public String sayHello(String name) { + logger.info("Hello " + name + ", request from consumer: " + + RpcContext.getContext().getRemoteAddress()); + return "hello" + name; + } +} diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/ProviderApplication.java b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/java/org/apache/dubbo/xds/demo/provider/XdsProviderApplication.java similarity index 75% rename from dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/ProviderApplication.java rename to dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/java/org/apache/dubbo/xds/demo/provider/XdsProviderApplication.java index db237d38ba..c3449aac76 100644 --- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/ProviderApplication.java +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/java/org/apache/dubbo/xds/demo/provider/XdsProviderApplication.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.springboot.demo.provider; +package org.apache.dubbo.xds.demo.provider; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; @@ -24,10 +24,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -@EnableDubbo(scanBasePackages = {"org.apache.dubbo.springboot.demo.provider"}) -public class ProviderApplication { - public static void main(String[] args) throws Exception { - SpringApplication.run(ProviderApplication.class, args); +@EnableDubbo(scanBasePackages = {"org.apache.dubbo.xds.demo.provider"}) +public class XdsProviderApplication { + public static void main(String[] args) throws InterruptedException { + // System.setProperty(IstioConstant.PILOT_CERT_PROVIDER_KEY, "istiod"); + SpringApplication.run(XdsProviderApplication.class, args); System.out.println("dubbo service started"); new CountDownLatch(1).await(); } diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml new file mode 100644 index 0000000000..bf2e8628bf --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml @@ -0,0 +1,33 @@ +# 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. + +spring: + application: + name: dubbo-demo-xds-provider + +dubbo: + application: + name: ${spring.application.name} +# qos-enable: false + protocol: + name: tri + port: 50051 + registry: + address: istio://istiod.istio-system.svc:15012?security=mTLS # istio://istiod.istio-system.svc:15012 +# config-center: +# address: zookeeper://127.0.0.1:2181 +# metadata-report: +# address: zookeeper://127.0.0.1:2181 diff --git a/dubbo-demo/dubbo-demo-xds/pom.xml b/dubbo-demo/dubbo-demo-xds/pom.xml new file mode 100644 index 0000000000..97d40fb7d7 --- /dev/null +++ b/dubbo-demo/dubbo-demo-xds/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-demo + ${revision} + + + dubbo-demo-xds + pom + + dubbo-demo-xds-interface + dubbo-demo-xds-consumer + dubbo-demo-xds-provider + + + + true + 2.7.18 + 2.7.18 + 1.12.4 + + + + + + org.apache.dubbo + dubbo-rpc-triple + ${project.parent.version} + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + + + + io.micrometer + micrometer-core + ${micrometer-core.version} + + + + diff --git a/dubbo-demo/pom.xml b/dubbo-demo/pom.xml index 30b2a1e5dc..8182e9d6b7 100644 --- a/dubbo-demo/pom.xml +++ b/dubbo-demo/pom.xml @@ -37,6 +37,7 @@ dubbo-demo-triple dubbo-demo-native dubbo-demo-spring-boot + dubbo-demo-xds diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index e27da32179..087b6b4987 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -1032,6 +1032,46 @@ META-INF/dubbo/internal/org.apache.dubbo.xds.bootstrap.XdsCertificateSigner + + + META-INF/dubbo/internal/org.apache.dubbo.xds.listener.LdsListener + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.listener.CdsListener + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.CertSource + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.RequestAuthorizer + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.ServiceIdentitySource + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.TrustSource + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleProvider + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleFactory + + + + META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.resolver.CredentialResolver + + + + META-INF/dubbo/internal/org.apache.dubbo.remoting.api.ChannelContextListener + diff --git a/dubbo-plugin/dubbo-security/src/test/java/org/apache/dubbo/security/cert/CertDeployerListenerTest.java b/dubbo-plugin/dubbo-security/src/test/java/org/apache/dubbo/security/cert/CertDeployerLdsListenerTest.java similarity index 99% rename from dubbo-plugin/dubbo-security/src/test/java/org/apache/dubbo/security/cert/CertDeployerListenerTest.java rename to dubbo-plugin/dubbo-security/src/test/java/org/apache/dubbo/security/cert/CertDeployerLdsListenerTest.java index a5876c4918..d45ba132d7 100644 --- a/dubbo-plugin/dubbo-security/src/test/java/org/apache/dubbo/security/cert/CertDeployerListenerTest.java +++ b/dubbo-plugin/dubbo-security/src/test/java/org/apache/dubbo/security/cert/CertDeployerLdsListenerTest.java @@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedConstruction; import org.mockito.Mockito; -class CertDeployerListenerTest { +class CertDeployerLdsListenerTest { @Test void testEmpty1() { AtomicReference reference = new AtomicReference<>(); diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ChannelContextListener.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ChannelContextListener.java new file mode 100644 index 0000000000..610d7ea90c --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/ChannelContextListener.java @@ -0,0 +1,40 @@ +/* + * 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.remoting.api; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; + +/** + * Listeners listening to connection events. + * Do not do heavy jobs in listeners to avoid blocking the workers. + */ +@SPI(scope = ExtensionScope.APPLICATION) +public interface ChannelContextListener { + + /** + * On connection established + * @param channelContext channelContext + */ + void onConnect(Object channelContext); + + /** + * On connection disconnected + * @param channelContext channelContext + */ + void onDisconnect(Object channelContext); +} diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java index 6082db24a0..b4ffd640a0 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java @@ -17,30 +17,39 @@ package org.apache.dubbo.remoting.transport.netty4; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.ChannelHandler; +import org.apache.dubbo.remoting.api.ChannelContextListener; import java.net.InetSocketAddress; +import java.util.List; import java.util.Map; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class NettyChannelHandler extends ChannelInboundHandlerAdapter { - private static final Logger logger = LoggerFactory.getLogger(NettyChannelHandler.class); + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(NettyChannelHandler.class); private final Map dubboChannels; private final URL url; private final ChannelHandler handler; - public NettyChannelHandler(Map dubboChannels, URL url, ChannelHandler handler) { + private final List contextListeners; + + public NettyChannelHandler( + Map dubboChannels, + URL url, + ChannelHandler handler, + List listeners) { this.dubboChannels = dubboChannels; this.url = url; this.handler = handler; + this.contextListeners = listeners; } @Override @@ -51,7 +60,13 @@ public class NettyChannelHandler extends ChannelInboundHandlerAdapter { dubboChannels.put( NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel); handler.connected(channel); - + contextListeners.forEach(listener -> { + try { + listener.onConnect(ctx); + } catch (Exception e) { + logger.warn("99-1", "", "", "", "Failed to invoke listener when channel connect:", e); + } + }); if (logger.isInfoEnabled()) { logger.info("The connection of " + channel.getRemoteAddress() + " -> " + channel.getLocalAddress() + " is established."); @@ -75,6 +90,13 @@ public class NettyChannelHandler extends ChannelInboundHandlerAdapter { } } finally { NettyChannel.removeChannel(ctx.channel()); + contextListeners.forEach(listener -> { + try { + listener.onDisconnect(ctx); + } catch (Exception e) { + logger.warn("99-1", "", "", "", "Failed to invoke listener when channel disconnect:", e); + } + }); } } } diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java index 4af9649fc7..fb1f4c4293 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyPortUnificationServer.java @@ -26,13 +26,17 @@ import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.ChannelHandler; import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.api.ChannelContextListener; import org.apache.dubbo.remoting.api.WireProtocol; import org.apache.dubbo.remoting.api.pu.AbstractPortUnificationServer; import org.apache.dubbo.remoting.transport.dispatcher.ChannelHandlers; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ModuleModel; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -76,6 +80,8 @@ public class NettyPortUnificationServer extends AbstractPortUnificationServer { private EventLoopGroup workerGroup; private final Map dubboChannels = new ConcurrentHashMap<>(); + private final List listeners; + public NettyPortUnificationServer(URL url, ChannelHandler handler) throws RemotingException { super(url, ChannelHandlers.wrap(handler, url)); @@ -84,6 +90,11 @@ public class NettyPortUnificationServer extends AbstractPortUnificationServer { // the handler will be wrapped: MultiMessageHandler->HeartbeatHandler->handler // read config before destroy serverShutdownTimeoutMills = ConfigurationUtils.getServerShutdownTimeout(getUrl().getOrDefaultModuleModel()); + listeners = (url.getScopeModel() == null + ? FrameworkModel.defaultModel().defaultApplication() + : ((ModuleModel) url.getScopeModel()).getApplicationModel()) + .getExtensionLoader(ChannelContextListener.class) + .getActivateExtensions(); } @Override @@ -124,8 +135,8 @@ public class NettyPortUnificationServer extends AbstractPortUnificationServer { protected void initChannel(SocketChannel ch) throws Exception { // Do not add idle state handler here, because it should be added in the protocol handler. final ChannelPipeline p = ch.pipeline(); - NettyChannelHandler nettyChannelHandler = - new NettyChannelHandler(dubboChannels, getUrl(), NettyPortUnificationServer.this); + NettyChannelHandler nettyChannelHandler = new NettyChannelHandler( + dubboChannels, getUrl(), NettyPortUnificationServer.this, listeners); NettyPortUnificationServerHandler puHandler = new NettyPortUnificationServerHandler( getUrl(), true, diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java index 9f5bd0a075..fe21c095bb 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java @@ -177,7 +177,7 @@ public class NettyServer extends AbstractServer { .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childHandler(new ChannelInitializer() { @Override - protected void initChannel(SocketChannel ch) throws Exception { + protected void initChannel(SocketChannel ch) { int closeTimeout = UrlUtils.getCloseTimeout(getUrl()); NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); ch.pipeline().addLast("negotiation", new SslServerTlsHandler(getUrl())); diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java index 1eff7fc84b..41aa582390 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java @@ -72,7 +72,7 @@ public class NettyServerHandler extends ChannelDuplexHandler { } handler.connected(channel); - if (logger.isInfoEnabled()) { + if (logger.isInfoEnabled() && channel != null) { logger.info("The connection of " + channel.getRemoteAddress() + " -> " + channel.getLocalAddress() + " is established."); } diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslContexts.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslContexts.java index beb7e61142..74ad735ee4 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslContexts.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslContexts.java @@ -63,7 +63,7 @@ public class SslContexts { if (serverTrustCertStream != null) { sslClientContextBuilder.trustManager(serverTrustCertStream); - if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.CLIENT_AUTH) { + if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.CLIENT_AUTH_STRICT) { sslClientContextBuilder.clientAuth(ClientAuth.REQUIRE); } else { sslClientContextBuilder.clientAuth(ClientAuth.OPTIONAL); diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslServerTlsHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslServerTlsHandler.java index 519c45c6de..751cab6006 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslServerTlsHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ssl/SslServerTlsHandler.java @@ -115,7 +115,8 @@ public class SslServerTlsHandler extends ByteToMessageDecoder { return; } - if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.NONE) { + if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.NONE + || providerConnectionConfig.getAuthPolicy() == AuthPolicy.CLIENT_AUTH_PERMISSIVE) { channelHandlerContext.pipeline().remove(this); return; } diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/BaseFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/BaseFilter.java index 02c643abf5..1c6c5b7353 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/BaseFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/BaseFilter.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc; public interface BaseFilter { + /** * Always call invoker.invoke() in the implementation to hand over the request to the next filter node. */ diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java index afad51c598..25b46190be 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java @@ -74,6 +74,8 @@ public interface Constants { String TOKEN_KEY = "token"; + String ID_TOKEN_KEY = "identity.token"; + String INTERFACE = "interface"; String INTERFACES = "interfaces"; diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java index d1c2be2973..66ec39eddf 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ssl/SslContexts.java @@ -63,7 +63,7 @@ public class SslContexts { if (serverTrustCertStream != null) { sslClientContextBuilder.trustManager(serverTrustCertStream); - if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.CLIENT_AUTH) { + if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.CLIENT_AUTH_STRICT) { sslClientContextBuilder.clientAuth(ClientAuth.REQUIRE); } else { sslClientContextBuilder.clientAuth(ClientAuth.OPTIONAL); diff --git a/dubbo-xds/pom.xml b/dubbo-xds/pom.xml index 5fe64b4d68..0c8307dcef 100644 --- a/dubbo-xds/pom.xml +++ b/dubbo-xds/pom.xml @@ -27,8 +27,10 @@ jar ${project.artifactId} The xds module of dubbo project + false + 3.25.1 @@ -86,12 +88,7 @@ log4j-slf4j-impl test - - io.grpc - grpc-api - 1.61.0 - compile - + io.grpc grpc-protobuf @@ -141,6 +138,97 @@ ${project.parent.version} test + + io.kubernetes + client-java + 10.0.1 + + + com.auth0 + java-jwt + 3.18.2 + + + com.auth0 + jwks-rsa + 0.18.0 + + + org.apache.dubbo + dubbo-config-api + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-rpc-triple + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-registry-zookeeper + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.parent.version} + test + + + + com.fasterxml.jackson.core + jackson-databind + 2.12.3 + + + + commons-io + commons-io + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${maven_protobuf_plugin_version} + + com.google.protobuf:protoc:${protobuf-java_version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc_version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + + + + kr.motd.maven + os-maven-plugin + ${maven_os_plugin_version} + + + diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java index d756fd047c..ac20bb2ddc 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java @@ -24,9 +24,12 @@ import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.protocol.AbstractProtocol; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import io.envoyproxy.envoy.config.core.v3.Node; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; @@ -46,6 +49,8 @@ public class AdsObserver { protected StreamObserver requestObserver; + private CompletableFuture future = new CompletableFuture<>(); + private final Map observedResources = new ConcurrentHashMap<>(); public AdsObserver(URL url, Node node) { @@ -61,22 +66,42 @@ public class AdsObserver { public void request(DiscoveryRequest discoveryRequest) { if (requestObserver == null) { - requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this)); + requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this, future)); } requestObserver.onNext(discoveryRequest); observedResources.put(discoveryRequest.getTypeUrl(), discoveryRequest); + try { + // TODO:This is to make the child thread receive the information. + // Maybe Using CountDownLatch would be better + String name = Thread.currentThread().getName(); + if ("main".equals(name)) { + future.get(600, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } } private static class ResponseObserver implements StreamObserver { private AdsObserver adsObserver; - public ResponseObserver(AdsObserver adsObserver) { + private CompletableFuture future; + + public ResponseObserver(AdsObserver adsObserver, CompletableFuture future) { this.adsObserver = adsObserver; + this.future = future; } @Override public void onNext(DiscoveryResponse discoveryResponse) { - System.out.println("Receive message from server"); + logger.info("Receive message from server"); + if (future != null) { + future.complete(null); + } XdsListener xdsListener = adsObserver.listeners.get(discoveryResponse.getTypeUrl()); xdsListener.process(discoveryResponse); adsObserver.requestObserver.onNext(buildAck(discoveryResponse)); @@ -122,7 +147,8 @@ public class AdsObserver { try { xdsChannel = new XdsChannel(url); if (xdsChannel.getChannel() != null) { - requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this)); + // Child thread not need to wait other child thread. + requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this, null)); observedResources.values().forEach(requestObserver::onNext); return; } else { diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/NodeBuilder.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/NodeBuilder.java index 7dfe2bc27e..cb28dfe2c2 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/NodeBuilder.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/NodeBuilder.java @@ -19,6 +19,11 @@ package org.apache.dubbo.xds; import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.xds.istio.IstioEnv; +import java.util.HashMap; +import java.util.Map; + +import com.google.protobuf.Struct; +import com.google.protobuf.Value; import io.envoyproxy.envoy.config.core.v3.Node; public class NodeBuilder { @@ -32,10 +37,22 @@ public class NodeBuilder { String podName = IstioEnv.getInstance().getPodName(); String podNamespace = IstioEnv.getInstance().getWorkloadNameSpace(); String svcName = IstioEnv.getInstance().getIstioMetaClusterId(); + String saName = IstioEnv.getInstance().getServiceAccountName(); + + Map metadataMap = new HashMap<>(2); + + metadataMap.put( + "ISTIO_META_NAMESPACE", + Value.newBuilder().setStringValue(podNamespace).build()); + metadataMap.put( + "SERVICE_ACCOUNT", Value.newBuilder().setStringValue(saName).build()); + + Struct metadata = Struct.newBuilder().putAllFields(metadataMap).build(); // id -> sidecar~ip~{POD_NAME}~{NAMESPACE_NAME}.svc.cluster.local // cluster -> {SVC_NAME} return Node.newBuilder() + .setMetadata(metadata) .setId("sidecar~" + NetUtils.getLocalHost() + "~" + podName + "~" + podNamespace + SVC_CLUSTER_LOCAL) .setCluster(svcName) .build(); diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java index 76942fae9b..2ee7e4d3f4 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java @@ -19,11 +19,13 @@ package org.apache.dubbo.xds; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.utils.ConcurrentHashSet; import org.apache.dubbo.xds.directory.XdsDirectory; +import org.apache.dubbo.xds.protocol.XdsResourceListener; import org.apache.dubbo.xds.protocol.impl.CdsProtocol; import org.apache.dubbo.xds.protocol.impl.EdsProtocol; import org.apache.dubbo.xds.protocol.impl.LdsProtocol; import org.apache.dubbo.xds.protocol.impl.RdsProtocol; import org.apache.dubbo.xds.resource.XdsCluster; +import org.apache.dubbo.xds.resource.XdsEndpoint; import org.apache.dubbo.xds.resource.XdsRouteConfiguration; import org.apache.dubbo.xds.resource.XdsVirtualHost; @@ -31,7 +33,10 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; +import java.util.stream.Collectors; + +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; public class PilotExchanger { @@ -61,23 +66,31 @@ public class PilotExchanger { int pollingTimeout = url.getParameter("pollingTimeout", 10); adsObserver = new AdsObserver(url, NodeBuilder.build()); - // rds resources callback - Consumer> rdsCallback = (xdsRouteConfigurations) -> { - xdsRouteConfigurations.forEach(xdsRouteConfiguration -> { - xdsRouteConfiguration.getVirtualHosts().forEach((serviceName, xdsVirtualHost) -> { - this.xdsVirtualHostMap.put(serviceName, xdsVirtualHost); - // when resource update, notify subscribers - if (rdsListeners.containsKey(serviceName)) { - for (XdsDirectory listener : rdsListeners.get(serviceName)) { - listener.onRdsChange(serviceName, xdsVirtualHost); - } - } - }); - }); - }; + this.rdsProtocol = + new RdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel()); + this.edsProtocol = + new EdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel()); + this.ldsProtocol = + new LdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel()); + this.cdsProtocol = + new CdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel()); - // eds resources callback - Consumer> edsCallback = (xdsClusters) -> { + XdsResourceListener pilotRdsListener = + xdsRouteConfigurations -> xdsRouteConfigurations.forEach(xdsRouteConfiguration -> xdsRouteConfiguration + .getVirtualHosts() + .forEach((serviceName, xdsVirtualHost) -> { + this.xdsVirtualHostMap.put(serviceName, xdsVirtualHost); + // when resource update, notify subscribers + if (rdsListeners.containsKey(serviceName)) { + for (XdsDirectory listener : rdsListeners.get(serviceName)) { + listener.onRdsChange(serviceName, xdsVirtualHost); + } + } + })); + + XdsResourceListener pilotEdsListener = clusterLoadAssignments -> { + List xdsClusters = + clusterLoadAssignments.stream().map(this::parseCluster).collect(Collectors.toList()); xdsClusters.forEach(xdsCluster -> { this.xdsClusterMap.put(xdsCluster.getName(), xdsCluster); if (cdsListeners.containsKey(xdsCluster.getName())) { @@ -87,21 +100,16 @@ public class PilotExchanger { } }); }; - this.rdsProtocol = new RdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, rdsCallback); - this.edsProtocol = new EdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, edsCallback); - - this.ldsProtocol = new LdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout); - this.cdsProtocol = new CdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout); + this.rdsProtocol.registerListen(pilotRdsListener); + this.edsProtocol.registerListen(pilotEdsListener); // lds resources callback,listen to all rds resources in the callback function - Consumer> ldsCallback = rdsProtocol::subscribeResource; - ldsProtocol.setUpdateCallback(ldsCallback); - ldsProtocol.subscribeListeners(); + this.ldsProtocol.registerListen(rdsProtocol.getLdsListener()); + this.cdsProtocol.registerListen(edsProtocol.getCdsListener()); // cds resources callback,listen to all cds resources in the callback function - Consumer> cdsCallback = edsProtocol::subscribeResource; - cdsProtocol.setUpdateCallback(cdsCallback); - cdsProtocol.subscribeClusters(); + this.cdsProtocol.subscribeClusters(); + this.ldsProtocol.subscribeListeners(); } public static Map getXdsVirtualHostMap() { @@ -151,6 +159,10 @@ public class PilotExchanger { } } + public static PilotExchanger createInstance(URL url) { + return new PilotExchanger(url); + } + public static boolean isEnabled() { return GLOBAL_PILOT_EXCHANGER != null; } @@ -158,4 +170,27 @@ public class PilotExchanger { public void destroy() { this.adsObserver.destroy(); } + + private XdsCluster parseCluster(ClusterLoadAssignment cluster) { + XdsCluster xdsCluster = new XdsCluster(); + + xdsCluster.setName(cluster.getClusterName()); + + List xdsEndpoints = cluster.getEndpointsList().stream() + .flatMap(e -> e.getLbEndpointsList().stream()) + .map(LbEndpoint::getEndpoint) + .map(this::parseEndpoint) + .collect(Collectors.toList()); + + xdsCluster.setXdsEndpoints(xdsEndpoints); + + return xdsCluster; + } + + private XdsEndpoint parseEndpoint(io.envoyproxy.envoy.config.endpoint.v3.Endpoint endpoint) { + XdsEndpoint xdsEndpoint = new XdsEndpoint(); + xdsEndpoint.setAddress(endpoint.getAddress().getSocketAddress().getAddress()); + xdsEndpoint.setPortValue(endpoint.getAddress().getSocketAddress().getPortValue()); + return xdsEndpoint; + } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsChannel.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsChannel.java index 4c47e94aaa..6e41ef0f42 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsChannel.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsChannel.java @@ -22,7 +22,8 @@ import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.url.component.URLAddress; import org.apache.dubbo.xds.bootstrap.Bootstrapper; import org.apache.dubbo.xds.bootstrap.BootstrapperImpl; -import org.apache.dubbo.xds.bootstrap.XdsCertificateSigner; +import org.apache.dubbo.xds.security.api.CertPair; +import org.apache.dubbo.xds.security.api.CertSource; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; @@ -52,7 +53,7 @@ public class XdsChannel { private URL url; - private static final String SECURE = "secure"; + private static final String SECURITY = "security"; private static final String PLAINTEXT = "plaintext"; @@ -71,15 +72,16 @@ public class XdsChannel { this.url = url; try { if (!url.getParameter(USE_AGENT, false)) { - if (PLAINTEXT.equals(url.getParameter(SECURE))) { + // TODO:Need to consider situation where only user sa_jwt + if (PLAINTEXT.equals(url.getParameter(SECURITY))) { managedChannel = NettyChannelBuilder.forAddress(url.getHost(), url.getPort()) .usePlaintext() .build(); } else { - XdsCertificateSigner signer = url.getOrDefaultApplicationModel() - .getExtensionLoader(XdsCertificateSigner.class) - .getExtension(url.getParameter("signer", "istio")); - XdsCertificateSigner.CertPair certPair = signer.GenerateCert(url); + CertSource signer = url.getOrDefaultApplicationModel() + .getExtensionLoader(CertSource.class) + .getExtension(url.getProtocol()); + CertPair certPair = signer.getCert(url, null); SslContext context = GrpcSslContexts.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .keyManager( diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsException.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsException.java new file mode 100644 index 0000000000..6447747b8f --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsException.java @@ -0,0 +1,48 @@ +/* + * 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.xds; + +public class XdsException extends RuntimeException { + + private Type type; + + public XdsException(Type type, String message) { + super("[" + type + "]" + message); + this.type = type; + } + + public XdsException(Type type, String message, Throwable cause) { + super("[" + type + "]" + message, cause); + this.type = type; + } + + public XdsException(Type type, Throwable cause) { + super("[" + type + "]", cause); + this.type = type; + } + + public enum Type { + EDS, + CDS, + SDS, + LDS, + RDS, + ADS, + SECURITY, + UNKNOWN + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsCluster.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsCluster.java index 5933c0614f..835bc13586 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsCluster.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsCluster.java @@ -31,4 +31,8 @@ public class XdsCluster extends AbstractCluster { XdsDirectory xdsDirectory = new XdsDirectory<>(directory); return new XdsClusterInvoker<>(xdsDirectory); } + + public boolean isAvailable() { + return true; + } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsClusterInvoker.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsClusterInvoker.java index c66b6c034a..ef5ff1777b 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsClusterInvoker.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/cluster/XdsClusterInvoker.java @@ -38,23 +38,30 @@ public class XdsClusterInvoker extends AbstractClusterInvoker { @Override protected Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { - Invoker invoker = select(loadbalance, invocation, invokers, null); - try { - return invokeWithContext(invoker, invocation); - } catch (Throwable e) { - if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception. - throw (RpcException) e; + while (true) { + Invoker invoker = select(loadbalance, invocation, invokers, null); + try { + return invokeWithContext(invoker, invocation); + } catch (Throwable e) { + if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception. + throw (RpcException) e; + } + throw new RpcException( + e instanceof RpcException ? ((RpcException) e).getCode() : 0, + "Xds invoke providers " + invoker.getUrl() + " " + + loadbalance.getClass().getSimpleName() + + " for service " + getInterface().getName() + + " method " + RpcUtils.getMethodName(invocation) + " on consumer " + + NetUtils.getLocalHost() + + " use dubbo version " + Version.getVersion() + + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), + e.getCause() != null ? e.getCause() : e); } - throw new RpcException( - e instanceof RpcException ? ((RpcException) e).getCode() : 0, - "Xds invoke providers " + invoker.getUrl() + " " - + loadbalance.getClass().getSimpleName() - + " for service " + getInterface().getName() - + " method " + RpcUtils.getMethodName(invocation) + " on consumer " - + NetUtils.getLocalHost() - + " use dubbo version " + Version.getVersion() - + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), - e.getCause() != null ? e.getCause() : e); } } + + @Override + public boolean isAvailable() { + return true; + } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/config/XdsApplicationDeployListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/config/XdsApplicationDeployListener.java new file mode 100644 index 0000000000..db2c0df35b --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/config/XdsApplicationDeployListener.java @@ -0,0 +1,60 @@ +/* + * 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.xds.config; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.deploy.ApplicationDeployListener; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.PilotExchanger; + +import java.util.Collection; + +import static org.apache.dubbo.config.Constants.SUPPORT_MESH_TYPE; + +public class XdsApplicationDeployListener implements ApplicationDeployListener { + @Override + public void onInitialize(ApplicationModel scopeModel) {} + + @Override + public void onStarting(ApplicationModel scopeModel) { + Collection registryConfigs = + scopeModel.getApplicationConfigManager().getRegistries(); + for (RegistryConfig registryConfig : registryConfigs) { + String protocol = registryConfig.getProtocol(); + if (StringUtils.isNotEmpty(protocol) && SUPPORT_MESH_TYPE.contains(protocol)) { + URL url = URL.valueOf(registryConfig.getAddress()); + url = url.setScopeModel(scopeModel); + scopeModel.getFrameworkModel().getBeanFactory().registerBean(PilotExchanger.createInstance(url)); + break; + } + } + } + + @Override + public void onStarted(ApplicationModel scopeModel) {} + + @Override + public void onStopping(ApplicationModel scopeModel) {} + + @Override + public void onStopped(ApplicationModel scopeModel) {} + + @Override + public void onFailure(ApplicationModel scopeModel, Throwable cause) {} +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java index 5c03161ced..188d04ed16 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java @@ -17,6 +17,8 @@ package org.apache.dubbo.xds.directory; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; @@ -50,7 +52,7 @@ public class XdsDirectory extends AbstractDirectory { private final String protocolName; - PilotExchanger pilotExchanger = PilotExchanger.getInstance(); + PilotExchanger pilotExchanger; private Protocol protocol; @@ -58,14 +60,18 @@ public class XdsDirectory extends AbstractDirectory { private final Map> xdsClusterMap = new ConcurrentHashMap<>(); + private static ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsDirectory.class); + public XdsDirectory(Directory directory) { - super(directory.getConsumerUrl(), true); + super(directory.getUrl(), null, true, directory.getConsumerUrl()); this.serviceType = directory.getInterface(); this.url = directory.getConsumerUrl(); this.applicationNames = url.getParameter("provided-by").split(","); - this.protocolName = url.getParameter("protocol", "dubbo"); + this.protocolName = url.getParameter("protocol", "tri"); this.protocol = directory.getProtocol(); super.routerChain = directory.getRouterChain(); + this.pilotExchanger = + url.getOrDefaultApplicationModel().getBeanFactory().getBean(PilotExchanger.class); // subscribe resource for (String applicationName : applicationNames) { @@ -159,23 +165,25 @@ public class XdsDirectory extends AbstractDirectory { xdsEndpoints.forEach(e -> { String ip = e.getAddress(); int port = e.getPortValue(); - URL url = new URL(this.protocolName, ip, port); + URL url = new URL(this.protocolName, ip, port, this.url.getParameters()); // set cluster name url = url.addParameter("clusterID", clusterName); // set load balance policy url = url.addParameter("loadbalance", lbPolicy); + url.setPath(this.serviceType.getName()); // cluster to invoker Invoker invoker = this.protocol.refer(this.serviceType, url); invokers.add(invoker); }); // TODO: Consider cases where some clients are not available - super.getInvokers().addAll(invokers); - // super.setInvokers(invokers); + // super.getInvokers().addAll(invokers); + // TODO: Need add new api which can add invokers, because a XdsDirectory need monitor multi clusters. + super.setInvokers(invokers); xdsCluster.setInvokers(invokers); } @Override public boolean isAvailable() { - return false; + return true; } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioConstant.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioConstant.java index d25d1e3abd..d294fc8413 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioConstant.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioConstant.java @@ -17,6 +17,9 @@ package org.apache.dubbo.xds.istio; public class IstioConstant { + + public static final String ISTIO_NAME = "istio"; + /** * Address of the spiffe certificate provider. Defaults to discoveryAddress */ @@ -25,7 +28,7 @@ public class IstioConstant { /** * CA and xDS services */ - public static final String DEFAULT_CA_ADDR = "localhost:15012"; + public static final String DEFAULT_CA_ADDR = "istiod.istio-system.svc:15012"; /** * The trust domain for spiffe certificates @@ -44,13 +47,13 @@ public class IstioConstant { /** * k8s jwt token */ - public static final String KUBERNETES_SA_PATH = ""; + public static String KUBERNETES_SA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"; - public static final String KUBERNETES_CA_PATH = "E:/k8s/ca.crt"; + public static final String KUBERNETES_CA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; - public static final String ISTIO_SA_PATH = "/var/run/secrets/tokens/istio-token"; + public static String ISTIO_SA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"; - public static final String ISTIO_CA_PATH = "/var/run/secrets/istio/root-cert.pem"; + public static final String ISTIO_CA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; public static final String KUBERNETES_NAMESPACE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; @@ -70,11 +73,19 @@ public class IstioConstant { */ public static final String SECRET_TTL_KEY = "SECRET_TTL"; + public static final String TRUST_TTL_KEY = "TRUST_TTL"; + + public static final String SERVICE_NAME_KEY = "SERVICE_NAME"; + + private static final String DEFAULT_SERVICE_NAME = "default"; + /** * The cert lifetime default value 24h0m0s */ public static final String DEFAULT_SECRET_TTL = "86400"; // 24 * 60 * 60 + public static final String DEFAULT_TRUST_TTL = "86400"; + /** * The grace period ratio for the cert rotation */ @@ -89,7 +100,7 @@ public class IstioConstant { public static final String PILOT_CERT_PROVIDER_KEY = "PILOT_CERT_PROVIDER"; - public static final String ISTIO_PILOT_CERT_PROVIDER = "istiod"; + public static final String PILOT_CERT_PROVIDER_ISTIO = "istiod"; public static final String DEFAULT_ISTIO_META_CLUSTER_ID = "Kubernetes"; diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioEnv.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioEnv.java index fae9187aeb..5e613113e5 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioEnv.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/IstioEnv.java @@ -28,15 +28,34 @@ import java.util.Optional; import org.apache.commons.io.FileUtils; import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_READ_FILE_ISTIO; +import static org.apache.dubbo.xds.istio.IstioConstant.CA_ADDR_KEY; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_CA_ADDR; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_ECC_SIG_ALG; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_ISTIO_META_CLUSTER_ID; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_JWT_POLICY; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_RSA_KEY_SIZE; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_SECRET_TTL; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_TRUST_DOMAIN; +import static org.apache.dubbo.xds.istio.IstioConstant.DEFAULT_TRUST_TTL; +import static org.apache.dubbo.xds.istio.IstioConstant.ECC_SIG_ALG_KEY; +import static org.apache.dubbo.xds.istio.IstioConstant.ISTIO_META_CLUSTER_ID_KEY; +import static org.apache.dubbo.xds.istio.IstioConstant.JWT_POLICY; import static org.apache.dubbo.xds.istio.IstioConstant.NS; +import static org.apache.dubbo.xds.istio.IstioConstant.RSA_KEY_SIZE_KEY; import static org.apache.dubbo.xds.istio.IstioConstant.SA; +import static org.apache.dubbo.xds.istio.IstioConstant.SECRET_TTL_KEY; import static org.apache.dubbo.xds.istio.IstioConstant.SPIFFE; +import static org.apache.dubbo.xds.istio.IstioConstant.TRUST_DOMAIN_KEY; +import static org.apache.dubbo.xds.istio.IstioConstant.TRUST_TTL_KEY; public class IstioEnv implements XdsEnv { private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(IstioEnv.class); private static final IstioEnv INSTANCE = new IstioEnv(); + /** + * TODO this can auto read from sa jwt + */ private String podName; private String caAddr; @@ -45,56 +64,82 @@ public class IstioEnv implements XdsEnv { private String trustDomain; + /** + * TODO this can auto read from sa jwt + */ private String workloadNameSpace; private int rasKeySize; private String eccSigAlg; - private int secretTTL; - private float secretGracePeriodRatio; private String istioMetaClusterId; + /** + * Who provides cert for istio pilot + */ private String pilotCertProvider; + /** + * TTL of cert pair. This will affect the frequency of cert refresh. + */ + private int secretTTL; + + /** + * The time start to try to refresh certs + */ + private long tryRefreshBeforeCertExpireAt; + + /** + * TTL of trust storage. This will affect the frequency of trust refresh. + * In istio, trust always refresh with cert pair + * because istio use cert chains as response for an CSR request. + */ + private long trustTTL; + + private String serviceAccountJwt; + + /** + * TODO this can auto read from sa jwt + */ + private String serviceAccountName; + + private boolean haveServiceAccount; + private IstioEnv() { - jwtPolicy = - Optional.ofNullable(System.getenv(IstioConstant.JWT_POLICY)).orElse(IstioConstant.DEFAULT_JWT_POLICY); - podName = Optional.ofNullable(System.getenv("POD_NAME")).orElse(System.getenv("HOSTNAME")); - trustDomain = Optional.ofNullable(System.getenv(IstioConstant.TRUST_DOMAIN_KEY)) - .orElse(IstioConstant.DEFAULT_TRUST_DOMAIN); - workloadNameSpace = Optional.ofNullable(System.getenv(IstioConstant.WORKLOAD_NAMESPACE_KEY)) - .orElseGet(() -> { - File namespaceFile = new File(IstioConstant.KUBERNETES_NAMESPACE_PATH); - if (namespaceFile.canRead()) { - try { - return FileUtils.readFileToString(namespaceFile, StandardCharsets.UTF_8); - } catch (IOException e) { - logger.error(REGISTRY_ERROR_READ_FILE_ISTIO, "", "", "read namespace file error", e); - } - } - return IstioConstant.DEFAULT_WORKLOAD_NAMESPACE; - }); - caAddr = Optional.ofNullable(System.getenv(IstioConstant.CA_ADDR_KEY)).orElse(IstioConstant.DEFAULT_CA_ADDR); - rasKeySize = Integer.parseInt(Optional.ofNullable(System.getenv(IstioConstant.RSA_KEY_SIZE_KEY)) - .orElse(IstioConstant.DEFAULT_RSA_KEY_SIZE)); - eccSigAlg = Optional.ofNullable(System.getenv(IstioConstant.ECC_SIG_ALG_KEY)) - .orElse(IstioConstant.DEFAULT_ECC_SIG_ALG); - secretTTL = Integer.parseInt(Optional.ofNullable(System.getenv(IstioConstant.SECRET_TTL_KEY)) - .orElse(IstioConstant.DEFAULT_SECRET_TTL)); + jwtPolicy = getStringProp(JWT_POLICY, DEFAULT_JWT_POLICY); + podName = Optional.ofNullable(getStringProp("POD_NAME", (String) null)).orElse(getStringProp("HOSTNAME", "")); + trustDomain = getStringProp(TRUST_DOMAIN_KEY, DEFAULT_TRUST_DOMAIN); + + workloadNameSpace = getStringProp(IstioConstant.WORKLOAD_NAMESPACE_KEY, () -> { + File namespaceFile = new File(IstioConstant.KUBERNETES_NAMESPACE_PATH); + if (namespaceFile.canRead()) { + try { + return FileUtils.readFileToString(namespaceFile, StandardCharsets.UTF_8); + } catch (IOException e) { + logger.error(REGISTRY_ERROR_READ_FILE_ISTIO, "", "", "read namespace file error", e); + } + } + return IstioConstant.DEFAULT_WORKLOAD_NAMESPACE; + }); + caAddr = getStringProp(CA_ADDR_KEY, DEFAULT_CA_ADDR); + + rasKeySize = getIntProp(RSA_KEY_SIZE_KEY, DEFAULT_RSA_KEY_SIZE); + eccSigAlg = getStringProp(ECC_SIG_ALG_KEY, DEFAULT_ECC_SIG_ALG); + secretTTL = getIntProp(SECRET_TTL_KEY, DEFAULT_SECRET_TTL); + trustTTL = getIntProp(TRUST_TTL_KEY, DEFAULT_TRUST_TTL); + secretGracePeriodRatio = Float.parseFloat(Optional.ofNullable(System.getenv(IstioConstant.SECRET_GRACE_PERIOD_RATIO_KEY)) .orElse(IstioConstant.DEFAULT_SECRET_GRACE_PERIOD_RATIO)); - istioMetaClusterId = Optional.ofNullable(System.getenv(IstioConstant.ISTIO_META_CLUSTER_ID_KEY)) - .orElse(IstioConstant.DEFAULT_ISTIO_META_CLUSTER_ID); - pilotCertProvider = Optional.ofNullable(System.getenv(IstioConstant.PILOT_CERT_PROVIDER_KEY)) - .orElse(""); - + istioMetaClusterId = getStringProp(ISTIO_META_CLUSTER_ID_KEY, DEFAULT_ISTIO_META_CLUSTER_ID); + pilotCertProvider = getStringProp(IstioConstant.PILOT_CERT_PROVIDER_KEY, ""); + serviceAccountName = getStringProp(IstioConstant.SERVICE_NAME_KEY, "default"); if (getServiceAccount() == null) { - throw new UnsupportedOperationException("Unable to found kubernetes service account token file. " - + "Please check if work in Kubernetes and mount service account token file correctly."); + haveServiceAccount = false; + logger.info("Unable to found kubernetes service account token. Some istio-XDS feature may disabled."); } } @@ -132,13 +177,21 @@ public class IstioEnv implements XdsEnv { e); } } - // TODO:Subsequent implementation in the security section - return "null"; + + return null; + } + + public String getServiceAccountJwt() { + return serviceAccountJwt; } public String getCsrHost() { // spiffe:///ns//sa/ - return SPIFFE + trustDomain + NS + workloadNameSpace + SA + getServiceAccount(); + return SPIFFE + trustDomain + NS + workloadNameSpace + SA + getServiceAccountName(); + } + + public String getIstioMetaNamespace() { + return getCsrHost(); } public String getTrustDomain() { @@ -159,7 +212,7 @@ public class IstioEnv implements XdsEnv { } public boolean isECCFirst() { - return IstioConstant.DEFAULT_ECC_SIG_ALG.equals(eccSigAlg); + return DEFAULT_ECC_SIG_ALG.equals(eccSigAlg); } public int getSecretTTL() { @@ -174,9 +227,31 @@ public class IstioEnv implements XdsEnv { return istioMetaClusterId; } + public Long getTryRefreshBeforeCertExpireAt() { + return tryRefreshBeforeCertExpireAt; + } + + public String getPilotCertProvider() { + return pilotCertProvider; + } + + public long getTrustTTL() { + return trustTTL; + } + + public String getServiceAccountName() { + return serviceAccountName; + } + + // for test + @Deprecated + public void setToken(String saJwtToken) { + serviceAccountJwt = saJwtToken; + } + public String getCaCert() { File caFile; - if (IstioConstant.ISTIO_PILOT_CERT_PROVIDER.equals(pilotCertProvider)) { + if (IstioConstant.PILOT_CERT_PROVIDER_ISTIO.equals(pilotCertProvider)) { caFile = new File(IstioConstant.ISTIO_CA_PATH); } else { return null; @@ -191,4 +266,8 @@ public class IstioEnv implements XdsEnv { } return null; } + + public boolean haveServiceAccount() { + return haveServiceAccount; + } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/XdsEnv.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/XdsEnv.java index 21d430cb45..6cd6d46f47 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/XdsEnv.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/istio/XdsEnv.java @@ -16,7 +16,42 @@ */ package org.apache.dubbo.xds.istio; +import java.util.function.Supplier; + public interface XdsEnv { String getCluster(); + + default String getStringProp(String key, String defaultVal) { + String val = System.getenv(key); + if (val == null) { + val = System.getProperty(key); + } + if (val == null) { + val = defaultVal; + } + return val; + } + + default String getStringProp(String key, Supplier defaultValSupplier) { + String val = System.getenv(key); + if (val == null) { + val = System.getProperty(key); + } + if (val == null) { + val = defaultValSupplier.get(); + } + return val; + } + + default Integer getIntProp(String key, String defaultVal) { + String val = System.getenv(key); + if (val == null) { + val = System.getProperty(key); + } + if (val == null) { + val = defaultVal; + } + return Integer.valueOf(val); + } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/kubernetes/KubeApiClient.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/kubernetes/KubeApiClient.java new file mode 100644 index 0000000000..ddcce52d6d --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/kubernetes/KubeApiClient.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.xds.kubernetes; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import com.google.gson.reflect.TypeToken; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.apis.CustomObjectsApi; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.Watch; +import io.kubernetes.client.util.Watch.Response; +import io.kubernetes.client.util.credentials.AccessTokenAuthentication; + +public class KubeApiClient { + private final ApiClient apiClient; + + private final ErrorTypeAwareLogger errorTypeAwareLogger = + LoggerFactory.getErrorTypeAwareLogger(KubeApiClient.class); + + public KubeApiClient(ApplicationModel applicationModel) throws IOException { + KubeEnv kubeEnv = applicationModel.getBeanFactory().getBean(KubeEnv.class); + + apiClient = new ClientBuilder() + .setBasePath(kubeEnv.getApiServerPath()) + .setVerifyingSsl(kubeEnv.isEnableSsl()) + .setCertificateAuthority(kubeEnv.getServiceAccountCa()) + .setAuthentication(new AccessTokenAuthentication( + new String(kubeEnv.getServiceAccountToken(), StandardCharsets.UTF_8))) + .build(); + + apiClient.setConnectTimeout(kubeEnv.apiClientConnectTimeout()); + apiClient.setReadTimeout(kubeEnv.apiClientReadTimeout()); + + Configuration.setDefaultApiClient(apiClient); + } + + public Map getResourceAsMap(String apiGroup, String version, String namespace, String plural) { + CustomObjectsApi apiInstance = new CustomObjectsApi(); + try { + return (Map) apiInstance.listNamespacedCustomObject( + apiGroup, version, namespace, plural, null, null, null, null, null, null, null, null); + } catch (ApiException apiException) { + // log + throw new RuntimeException("Failed to get resource from ApiServer.", apiException); + } + } + + public Watch listenResource(String apiGroup, String version, String namespace, String plural) { + try { + CustomObjectsApi api = new CustomObjectsApi(); + return Watch.createWatch( + apiClient, + api.listNamespacedCustomObjectCall( + apiGroup, version, namespace, plural, null, null, null, null, null, null, null, true, null), + new TypeToken>() {}.getType()); + } catch (ApiException apiException) { + throw new RuntimeException("Failed to listen resource from ApiServer.", apiException); + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/kubernetes/KubeEnv.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/kubernetes/KubeEnv.java new file mode 100644 index 0000000000..6d7d2ddbd2 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/kubernetes/KubeEnv.java @@ -0,0 +1,188 @@ +/* + * 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.xds.kubernetes; + +import org.apache.dubbo.common.io.Bytes; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.istio.XdsEnv; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +public class KubeEnv implements XdsEnv { + + private String apiServerPath; + + private Boolean enableSsl; + + private String serviceAccountCaPath; + + private String serviceAccountTokenPath; + + private String namespace; + + private String serviceName; + + private String cluster; + + private Integer apiClientConnectTimeout; + + private Integer apiClientReadTimeout; + + public KubeEnv(ApplicationModel applicationModel) { + // get config from applicationModel ... + setDefault(); + } + + public void setDefault() { + if (StringUtils.isEmpty(apiServerPath)) { + apiServerPath = getStringProp("API_SERVER_PATH", "https://kubernetes.default.svc"); + } + if (enableSsl != null) { + enableSsl = true; + } + if (StringUtils.isEmpty(serviceAccountCaPath)) { + serviceAccountCaPath = getStringProp("SA_CA_PATH", "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"); + } + if (StringUtils.isEmpty(serviceAccountTokenPath)) { + serviceAccountTokenPath = + getStringProp("SA_TOKEN_PATH", "/var/run/secrets/kubernetes.io/serviceaccount/token"); + } + if (StringUtils.isEmpty(namespace)) { + namespace = getStringProp("NAMESPACE", "dubbo-demo"); + } + if (StringUtils.isEmpty(serviceName)) { + serviceName = getStringProp("SERVICE_NAME", ""); + } + if (apiClientConnectTimeout == null) { + apiClientConnectTimeout = getIntProp("API_CLIENT_CONNECT_TIMEOUT", "10000"); + } + if (apiClientReadTimeout == null) { + apiClientReadTimeout = getIntProp("API_CLIENT_READ_TIMEOUT", "30000"); + } + if (StringUtils.isEmpty(cluster)) { + cluster = getStringProp("CLUSTER", "cluster.local"); + } + if (enableSsl == null) { + enableSsl = true; + } + } + + public String getApiServerPath() { + return apiServerPath; + } + + public String getServiceAccountCaPath() { + return serviceAccountCaPath; + } + + public String getServiceAccountTokenPath() { + return serviceAccountTokenPath; + } + + public String getNamespace() { + return namespace; + } + + public String getServiceName() { + return serviceName; + } + + public byte[] getServiceAccountToken() throws IOException { + return readFileAsBytes(getServiceAccountTokenPath()); + } + + public byte[] getServiceAccountCa() throws IOException { + return readFileAsBytes(getServiceAccountCaPath()); + } + + private byte[] readFileAsBytes(String path) throws IOException { + File file = new File(path); + byte[] value = new byte[4096]; + if (!file.exists()) { + return new byte[0]; + } + try (FileInputStream in = new FileInputStream(file); ) { + int readBytes = in.read(value); + if (readBytes > 4096) { + throw new RuntimeException("Security resource size > 4096: Too long"); + } + value = Bytes.copyOf(value, readBytes); + } + + return value; + } + + public int apiClientConnectTimeout() { + return 10000; + } + + public int apiClientReadTimeout() { + return 30000; + } + + public boolean isEnableSsl() { + return enableSsl; + } + + public int getApiClientConnectTimeout() { + return apiClientConnectTimeout; + } + + public int getApiClientReadTimeout() { + return apiClientReadTimeout; + } + + public void setApiServerPath(String apiServerPath) { + this.apiServerPath = apiServerPath; + } + + public void setEnableSsl(boolean enableSsl) { + this.enableSsl = enableSsl; + } + + public void setServiceAccountCaPath(String serviceAccountPath) { + this.serviceAccountCaPath = serviceAccountPath; + } + + public void setServiceAccountTokenPath(String serviceAccountTokenPath) { + this.serviceAccountTokenPath = serviceAccountTokenPath; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public void setApiClientConnectTimeout(int apiClientConnectTimeout) { + this.apiClientConnectTimeout = apiClientConnectTimeout; + } + + public void setApiClientReadTimeout(int apiClientReadTimeout) { + this.apiClientReadTimeout = apiClientReadTimeout; + } + + @Override + public String getCluster() { + return cluster; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java new file mode 100644 index 0000000000..28283ff0df --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java @@ -0,0 +1,26 @@ +/* + * 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.xds.listener; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.xds.protocol.XdsResourceListener; + +import io.envoyproxy.envoy.config.cluster.v3.Cluster; + +@SPI(scope = ExtensionScope.APPLICATION) +public interface CdsListener extends XdsResourceListener {} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/DownstreamTlsConfigListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/DownstreamTlsConfigListener.java new file mode 100644 index 0000000000..5cb8114fbb --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/DownstreamTlsConfigListener.java @@ -0,0 +1,175 @@ +/* + * 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.xds.listener; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.XdsException; +import org.apache.dubbo.xds.security.authn.DownstreamTlsConfig; +import org.apache.dubbo.xds.security.authn.GeneralTlsConfig; +import org.apache.dubbo.xds.security.authn.TlsResourceResolver; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import io.envoyproxy.envoy.config.listener.v3.FilterChain; +import io.envoyproxy.envoy.config.listener.v3.Listener; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; + +@Activate +public class DownstreamTlsConfigListener implements LdsListener { + + protected static final String TLS = "tls"; + + protected static final String LDS_VIRTUAL_INBOUND = "virtualInbound"; + + protected static final String DOWNSTREAM_TLS_CONTEXT_TYPE = + "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext"; + + protected static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; + + protected static final String TRANSPORT_SOCKET_NAME_PLAINTEXT = "envoy.transport_sockets.raw_buffer"; + + private final XdsTlsConfigRepository repo; + + public DownstreamTlsConfigListener(ApplicationModel applicationModel) { + this.repo = applicationModel.getBeanFactory().getOrRegisterBean(XdsTlsConfigRepository.class); + } + + @Override + public void onResourceUpdate(List listeners) { + if (CollectionUtils.isEmpty(listeners)) { + return; + } + Map downstreamConfigs = new HashMap<>(4); + for (Listener listener : listeners) { + // only choose inbound listeners + if (!LDS_VIRTUAL_INBOUND.equals(listener.getName())) { + continue; + } + try { + int port = listener.getAddress().getSocketAddress().getPortValue(); + List filterChains = listener.getFilterChainsList(); + boolean supportTls = false; + boolean supportPlainText = false; + DownstreamTlsConfig downstreamTlsConfig = null; + + for (FilterChain filterChain : filterChains) { + if (TRANSPORT_SOCKET_NAME_TLS.equals( + filterChain.getTransportSocket().getName())) { + supportTls = true; + } + + if (TRANSPORT_SOCKET_NAME_PLAINTEXT.equals( + filterChain.getTransportSocket().getName())) { + supportPlainText = true; + } + + Any any = filterChain.getTransportSocket().getTypedConfig(); + + if (DOWNSTREAM_TLS_CONTEXT_TYPE.equals(any.getTypeUrl())) { + DownstreamTlsContext downstreamTlsContext; + try { + downstreamTlsContext = any.unpack(DownstreamTlsContext.class); + } catch (InvalidProtocolBufferException e) { + throw new RuntimeException(e); + } + + CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext(); + GeneralTlsConfig tlsConfig = + TlsResourceResolver.resolveCommonTlsConfig(String.valueOf(port), commonTlsContext); + + downstreamTlsConfig = new DownstreamTlsConfig( + tlsConfig, + downstreamTlsContext + .getRequireClientCertificate() + .getValue(), + downstreamTlsContext.getRequireSni().getValue(), + downstreamTlsContext.getSessionTimeout().getNanos()); + downstreamConfigs.put(String.valueOf(port), downstreamTlsConfig); + break; + } + } + + if (downstreamTlsConfig == null) { + downstreamConfigs.put(String.valueOf(port), new DownstreamTlsConfig(TlsType.DISABLE)); + } else { + if (supportTls && supportPlainText) { + downstreamTlsConfig.setTlsType(TlsType.PERMISSIVE); + } + if (!supportTls) { + downstreamTlsConfig.setTlsType(TlsType.DISABLE); + } + if (supportTls && !supportPlainText) { + downstreamTlsConfig.setTlsType(TlsType.STRICT); + } + } + } catch (Exception e) { + throw new XdsException( + XdsException.Type.LDS, + "Invalid UpstreamTlsContext config provided for port:" + + listener.getAddress().getSocketAddress().getPortValue(), + e); + } + repo.updateInbound(downstreamConfigs); + } + } + + public enum TlsType { + STRICT(0, "Strict Mode"), + PERMISSIVE(1, "Permissive Mode"), + DISABLE(2, "Disable Mode"), + ; + public static Map map = new HashMap<>(); + + static { + for (TlsType tlsEnum : TlsType.values()) { + map.put(tlsEnum.code, tlsEnum); + } + } + + private int code; + private String msg; + + TlsType(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public static TlsType getFromCode(int code) { + return map.get(code); + } + + @Override + public String toString() { + return "TlsType{" + "code=" + code + ", msg='" + msg + '\'' + '}'; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java new file mode 100644 index 0000000000..af75561899 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java @@ -0,0 +1,26 @@ +/* + * 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.xds.listener; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.xds.protocol.XdsResourceListener; + +import io.envoyproxy.envoy.config.listener.v3.Listener; + +@SPI(scope = ExtensionScope.APPLICATION) +public interface LdsListener extends XdsResourceListener {} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/ListenerConstants.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/ListenerConstants.java new file mode 100644 index 0000000000..37bae7e677 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/ListenerConstants.java @@ -0,0 +1,28 @@ +/* + * 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.xds.listener; + +public class ListenerConstants { + + public static final String LDS_VIRTUAL_INBOUND = "virtualInbound"; + + public static final String LDS_CONNECTION_MANAGER = "envoy.filters.network.http_connection_manager"; + + public static final String LDS_JWT_FILTER = "envoy.filters.http.jwt_authn"; + + public static final String LDS_RBAC_FILTER = "envoy.filters.http.rbac"; +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java new file mode 100644 index 0000000000..d398a5f925 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java @@ -0,0 +1,90 @@ +/* + * 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.xds.listener; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.XdsException; +import org.apache.dubbo.xds.XdsException.Type; +import org.apache.dubbo.xds.security.authn.TlsResourceResolver; +import org.apache.dubbo.xds.security.authn.UpstreamTlsConfig; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.google.protobuf.InvalidProtocolBufferException; +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext; + +@Activate +public class UpstreamTlsConfigListener implements CdsListener { + + private static final String TRANSPORT_SOCKET_NAME = "envoy.transport_sockets.tls"; + + private static final String UPSTREAM_TLS_CONFIG_NAME = + "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"; + + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(UpstreamTlsConfigListener.class); + + private final XdsTlsConfigRepository tlsConfigRepository; + + public UpstreamTlsConfigListener(ApplicationModel application) { + this.tlsConfigRepository = application.getBeanFactory().getOrRegisterBean(XdsTlsConfigRepository.class); + } + + @Override + public void onResourceUpdate(List resource) { + Map configs = new ConcurrentHashMap<>(16); + for (Cluster cluster : resource) { + String serviceName = cluster.getName(); + try { + if (!TRANSPORT_SOCKET_NAME.equals(cluster.getTransportSocket().getName())) { + // No TLS config found in this cluster. + configs.put(serviceName, new UpstreamTlsConfig()); + logger.debug( + "No TLS config provided for this service to connect upstream cluster:" + cluster.getName()); + continue; + } + String typeUrl = cluster.getTransportSocket().getTypedConfig().getTypeUrl(); + + if (!UPSTREAM_TLS_CONFIG_NAME.equals(typeUrl)) { + logger.info("Unknown TLS config type:" + typeUrl); + continue; + } + + UpstreamTlsContext tlsContext = + cluster.getTransportSocket().getTypedConfig().unpack(UpstreamTlsContext.class); + CommonTlsContext commonTlsContext = tlsContext.getCommonTlsContext(); + + configs.put( + serviceName, + new UpstreamTlsConfig( + TlsResourceResolver.resolveCommonTlsConfig(serviceName, commonTlsContext), + tlsContext.getSni(), + tlsContext.getAllowRenegotiation())); + tlsConfigRepository.updateOutbound(configs); + } catch (InvalidProtocolBufferException invalidProtocolBufferException) { + throw new XdsException( + Type.CDS, "Invalid UpstreamTlsContext config provided for cluster:" + cluster.getName()); + } + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/XdsTlsConfigRepository.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/XdsTlsConfigRepository.java new file mode 100644 index 0000000000..553eb2cea0 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/XdsTlsConfigRepository.java @@ -0,0 +1,56 @@ +/* + * 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.xds.listener; + +import org.apache.dubbo.xds.security.authn.DownstreamTlsConfig; +import org.apache.dubbo.xds.security.authn.UpstreamTlsConfig; + +import java.util.Collections; +import java.util.Map; + +public class XdsTlsConfigRepository { + + public XdsTlsConfigRepository() {} + + /** + * inbound ports -> configs + * Indicates the TLS configuration for inbound connections. + */ + private volatile Map downstreamConfigs = Collections.emptyMap(); + + /** + * clusterName -> configs + * Indicates the TLS configuration for outbound connection to certain cluster. + */ + private volatile Map upstreamConfigs = Collections.emptyMap(); + + public void updateInbound(Map downstreamType) { + this.downstreamConfigs = downstreamType; + } + + public void updateOutbound(Map upstreamType) { + this.upstreamConfigs = upstreamType; + } + + public DownstreamTlsConfig getDownstreamConfig(String port) { + return downstreamConfigs.get(port); + } + + public UpstreamTlsConfig getUpstreamConfig(String clusterName) { + return upstreamConfigs.get(clusterName); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java index 43a8e36c96..7f04e1d8a5 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java @@ -20,6 +20,7 @@ import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.ConcurrentHashMapUtils; import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.AdsObserver; import org.apache.dubbo.xds.XdsListener; @@ -32,6 +33,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; @@ -70,13 +72,22 @@ public abstract class AbstractProtocol implements XdsProtocol, XdsListener { protected Map resourcesMap = new ConcurrentHashMap<>(); - public AbstractProtocol(AdsObserver adsObserver, Node node, int checkInterval) { + protected List> resourceListeners = new CopyOnWriteArrayList<>(); + + protected ApplicationModel applicationModel; + + public AbstractProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) { this.adsObserver = adsObserver; this.node = node; this.checkInterval = checkInterval; + this.applicationModel = applicationModel; adsObserver.addListener(this); } + public void registerListen(XdsResourceListener listener) { + this.resourceListeners.add(listener); + } + /** * Abstract method to obtain Type-URL from sub-class * @@ -163,13 +174,18 @@ public abstract class AbstractProtocol implements XdsProtocol, XdsListener { .build(); } + // protected abstract Map decodeDiscoveryResponse(DiscoveryResponse response); + protected abstract Map decodeDiscoveryResponse(DiscoveryResponse response); @Override public final void process(DiscoveryResponse discoveryResponse) { - Map newResult = decodeDiscoveryResponse(discoveryResponse); + // Map newResult = decodeDiscoveryResponse(discoveryResponse); Map oldResource = resourcesMap; // discoveryResponseListener(oldResource, newResult); + + Map newResult = decodeDiscoveryResponse(discoveryResponse); + resourceListeners.forEach(l -> l.onResourceUpdate(new ArrayList<>(newResult.values()))); resourcesMap = newResult; } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsResourceListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsResourceListener.java new file mode 100644 index 0000000000..fa9a4998f2 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsResourceListener.java @@ -0,0 +1,24 @@ +/* + * 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.xds.protocol; + +import java.util.List; + +public interface XdsResourceListener { + + void onResourceUpdate(List resource); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java index 037f1c7fc8..512fbeb5b7 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java @@ -18,26 +18,34 @@ package org.apache.dubbo.xds.protocol.impl; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.AdsObserver; +import org.apache.dubbo.xds.listener.CdsListener; import org.apache.dubbo.xds.protocol.AbstractProtocol; +import org.apache.dubbo.xds.resource.XdsCluster; +import org.apache.dubbo.xds.resource.XdsEndpoint; -import java.util.HashMap; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS; -public class CdsProtocol extends AbstractProtocol { +public class CdsProtocol extends AbstractProtocol { private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(CdsProtocol.class); public void setUpdateCallback(Consumer> updateCallback) { @@ -46,8 +54,11 @@ public class CdsProtocol extends AbstractProtocol { private Consumer> updateCallback; - public CdsProtocol(AdsObserver adsObserver, Node node, int checkInterval) { - super(adsObserver, node, checkInterval); + public CdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) { + super(adsObserver, node, checkInterval, applicationModel); + List ldsListeners = + applicationModel.getExtensionLoader(CdsListener.class).getActivateExtensions(); + ldsListeners.forEach(this::registerListen); } @Override @@ -59,20 +70,63 @@ public class CdsProtocol extends AbstractProtocol { subscribeResource(null); } + // @Override + // protected Map decodeDiscoveryResponse(DiscoveryResponse response) { + // if (getTypeUrl().equals(response.getTypeUrl())) { + // Set set = response.getResourcesList().stream() + // .map(CdsProtocol::unpackCluster) + // .filter(Objects::nonNull) + // .map(Cluster::getName) + // .collect(Collectors.toSet()); + // updateCallback.accept(set); + // // Map listenerDecodeResult = new ConcurrentHashMap<>(); + // // listenerDecodeResult.put(emptyResourceName, new ListenerResult(set)); + // // return listenerDecodeResult; + // } + // return new HashMap<>(); + // } + @Override - protected Map decodeDiscoveryResponse(DiscoveryResponse response) { + protected Map decodeDiscoveryResponse(DiscoveryResponse response) { if (getTypeUrl().equals(response.getTypeUrl())) { - Set set = response.getResourcesList().stream() + return response.getResourcesList().stream() .map(CdsProtocol::unpackCluster) .filter(Objects::nonNull) - .map(Cluster::getName) - .collect(Collectors.toSet()); - updateCallback.accept(set); - // Map listenerDecodeResult = new ConcurrentHashMap<>(); - // listenerDecodeResult.put(emptyResourceName, new ListenerResult(set)); - // return listenerDecodeResult; + .collect(Collectors.toMap(Cluster::getName, Function.identity())); } - return new HashMap<>(); + return Collections.emptyMap(); + } + + private ClusterLoadAssignment unpackClusterLoadAssignment(Any any) { + try { + return any.unpack(ClusterLoadAssignment.class); + } catch (InvalidProtocolBufferException e) { + logger.error(REGISTRY_ERROR_RESPONSE_XDS, "", "", "Error occur when decode xDS response.", e); + return null; + } + } + + public XdsCluster parseCluster(ClusterLoadAssignment cluster) { + XdsCluster xdsCluster = new XdsCluster(); + + xdsCluster.setName(cluster.getClusterName()); + + List xdsEndpoints = cluster.getEndpointsList().stream() + .flatMap(e -> e.getLbEndpointsList().stream()) + .map(LbEndpoint::getEndpoint) + .map(this::parseEndpoint) + .collect(Collectors.toList()); + + xdsCluster.setXdsEndpoints(xdsEndpoints); + + return xdsCluster; + } + + public XdsEndpoint parseEndpoint(io.envoyproxy.envoy.config.endpoint.v3.Endpoint endpoint) { + XdsEndpoint xdsEndpoint = new XdsEndpoint(); + xdsEndpoint.setAddress(endpoint.getAddress().getSocketAddress().getAddress()); + xdsEndpoint.setPortValue(endpoint.getAddress().getSocketAddress().getPortValue()); + return xdsEndpoint; } private static Cluster unpackCluster(Any any) { diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java index 65b1586882..dc00015f14 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java @@ -18,16 +18,15 @@ package org.apache.dubbo.xds.protocol.impl; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.AdsObserver; import org.apache.dubbo.xds.protocol.AbstractProtocol; -import org.apache.dubbo.xds.resource.XdsCluster; -import org.apache.dubbo.xds.resource.XdsEndpoint; +import org.apache.dubbo.xds.protocol.XdsResourceListener; -import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Consumer; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import com.google.protobuf.Any; @@ -35,25 +34,21 @@ import com.google.protobuf.InvalidProtocolBufferException; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.core.v3.Node; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; -import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS; -public class EdsProtocol extends AbstractProtocol { +public class EdsProtocol extends AbstractProtocol { private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(EdsProtocol.class); - public void setUpdateCallback(Consumer> updateCallback) { - this.updateCallback = updateCallback; - } + private XdsResourceListener clusterListener = clusters -> { + Set clusterNames = clusters.stream().map(Cluster::getName).collect(Collectors.toSet()); + this.subscribeResource(clusterNames); + }; - private Consumer> updateCallback; - - public EdsProtocol( - AdsObserver adsObserver, Node node, int checkInterval, Consumer> updateCallback) { - super(adsObserver, node, checkInterval); - this.updateCallback = updateCallback; + public EdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) { + super(adsObserver, node, checkInterval, applicationModel); } @Override @@ -61,54 +56,19 @@ public class EdsProtocol extends AbstractProtocol { return "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; } - @Override - protected Map decodeDiscoveryResponse(DiscoveryResponse response) { - List clusters = parse(response); - updateCallback.accept(clusters); - - // if (getTypeUrl().equals(response.getTypeUrl())) { - // return response.getResourcesList().stream() - // .map(EdsProtocol::unpackClusterLoadAssignment) - // .filter(Objects::nonNull) - // .collect(Collectors.toConcurrentMap( - // ClusterLoadAssignment::getClusterName, this::decodeResourceToEndpoint)); - // } - return new HashMap<>(); + public XdsResourceListener getCdsListener() { + return clusterListener; } - public List parse(DiscoveryResponse response) { + @Override + protected Map decodeDiscoveryResponse(DiscoveryResponse response) { if (!getTypeUrl().equals(response.getTypeUrl())) { return null; } - return response.getResourcesList().stream() .map(EdsProtocol::unpackClusterLoadAssignment) .filter(Objects::nonNull) - .map(this::parseCluster) - .collect(Collectors.toList()); - } - - public XdsCluster parseCluster(ClusterLoadAssignment cluster) { - XdsCluster xdsCluster = new XdsCluster(); - - xdsCluster.setName(cluster.getClusterName()); - - List xdsEndpoints = cluster.getEndpointsList().stream() - .flatMap(e -> e.getLbEndpointsList().stream()) - .map(LbEndpoint::getEndpoint) - .map(this::parseEndpoint) - .collect(Collectors.toList()); - - xdsCluster.setXdsEndpoints(xdsEndpoints); - - return xdsCluster; - } - - public XdsEndpoint parseEndpoint(io.envoyproxy.envoy.config.endpoint.v3.Endpoint endpoint) { - XdsEndpoint xdsEndpoint = new XdsEndpoint(); - xdsEndpoint.setAddress(endpoint.getAddress().getSocketAddress().getAddress()); - xdsEndpoint.setPortValue(endpoint.getAddress().getSocketAddress().getPortValue()); - return xdsEndpoint; + .collect(Collectors.toConcurrentMap(ClusterLoadAssignment::getClusterName, Function.identity())); } private static ClusterLoadAssignment unpackClusterLoadAssignment(Any any) { diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java index 583b35f107..0fd6998b6d 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java @@ -18,15 +18,17 @@ package org.apache.dubbo.xds.protocol.impl; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.AdsObserver; +import org.apache.dubbo.xds.listener.LdsListener; import org.apache.dubbo.xds.protocol.AbstractProtocol; -import org.apache.dubbo.xds.resource.XdsVirtualHost; -import java.util.HashMap; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import com.google.protobuf.Any; @@ -40,17 +42,15 @@ import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS; -public class LdsProtocol extends AbstractProtocol { +public class LdsProtocol extends AbstractProtocol { + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(LdsProtocol.class); - public void setUpdateCallback(Consumer> updateCallback) { - this.updateCallback = updateCallback; - } - - private Consumer> updateCallback; - - public LdsProtocol(AdsObserver adsObserver, Node node, int checkInterval) { - super(adsObserver, node, checkInterval); + public LdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) { + super(adsObserver, node, checkInterval, applicationModel); + List ldsListeners = + applicationModel.getExtensionLoader(LdsListener.class).getActivateExtensions(); + ldsListeners.forEach(this::registerListen); } @Override @@ -63,19 +63,14 @@ public class LdsProtocol extends AbstractProtocol { } @Override - protected Map decodeDiscoveryResponse(DiscoveryResponse response) { + protected Map decodeDiscoveryResponse(DiscoveryResponse response) { if (getTypeUrl().equals(response.getTypeUrl())) { - Set set = response.getResourcesList().stream() + return response.getResourcesList().stream() .map(LdsProtocol::unpackListener) .filter(Objects::nonNull) - .flatMap(e -> decodeResourceToListener(e).stream()) - .collect(Collectors.toSet()); - updateCallback.accept(set); - // Map listenerDecodeResult = new ConcurrentHashMap<>(); - // listenerDecodeResult.put(emptyResourceName, new ListenerResult(set)); - return null; + .collect(Collectors.toConcurrentMap(Listener::getName, Function.identity())); } - return new HashMap<>(); + return Collections.emptyMap(); } private Set decodeResourceToListener(Listener resource) { diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java index de077fd7c2..ffaedfb9a0 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java @@ -18,46 +18,46 @@ package org.apache.dubbo.xds.protocol.impl; import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.AdsObserver; import org.apache.dubbo.xds.protocol.AbstractProtocol; +import org.apache.dubbo.xds.protocol.XdsResourceListener; import org.apache.dubbo.xds.resource.XdsRoute; import org.apache.dubbo.xds.resource.XdsRouteAction; import org.apache.dubbo.xds.resource.XdsRouteConfiguration; import org.apache.dubbo.xds.resource.XdsRouteMatch; import org.apache.dubbo.xds.resource.XdsVirtualHost; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Consumer; +import java.util.Set; import java.util.stream.Collectors; import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.config.listener.v3.Filter; +import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.route.v3.Route; import io.envoyproxy.envoy.config.route.v3.RouteAction; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; import io.envoyproxy.envoy.config.route.v3.RouteMatch; import io.envoyproxy.envoy.config.route.v3.VirtualHost; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS; -public class RdsProtocol extends AbstractProtocol { +public class RdsProtocol extends AbstractProtocol { private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RdsProtocol.class); - protected Consumer> updateCallback; - - public RdsProtocol( - AdsObserver adsObserver, - Node node, - int checkInterval, - Consumer> updateCallback) { - super(adsObserver, node, checkInterval); - this.updateCallback = updateCallback; + public RdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) { + super(adsObserver, node, checkInterval, applicationModel); } @Override @@ -65,19 +65,31 @@ public class RdsProtocol extends AbstractProtocol { return "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"; } + // @Override + // protected Map decodeDiscoveryResponse(DiscoveryResponse response) { + // List xdsRouteConfigurations = parse(response); + // System.out.println(xdsRouteConfigurations); + // updateCallback.accept(xdsRouteConfigurations); + // // if (getTypeUrl().equals(response.getTypeUrl())) { + // // return response.getResourcesList().stream() + // // .map(RdsProtocol::unpackRouteConfiguration) + // // .filter(Objects::nonNull) + // // .collect(Collectors.toConcurrentMap(RouteConfiguration::getName, + // // this::decodeResourceToListener)); + // // } + // return new HashMap<>(); + // } + @Override - protected Map decodeDiscoveryResponse(DiscoveryResponse response) { - List xdsRouteConfigurations = parse(response); - System.out.println(xdsRouteConfigurations); - updateCallback.accept(xdsRouteConfigurations); - // if (getTypeUrl().equals(response.getTypeUrl())) { - // return response.getResourcesList().stream() - // .map(RdsProtocol::unpackRouteConfiguration) - // .filter(Objects::nonNull) - // .collect(Collectors.toConcurrentMap(RouteConfiguration::getName, - // this::decodeResourceToListener)); - // } - return new HashMap<>(); + protected Map decodeDiscoveryResponse(DiscoveryResponse response) { + if (getTypeUrl().equals(response.getTypeUrl())) { + return response.getResourcesList().stream() + .map(RdsProtocol::unpackRouteConfiguration) + .filter(Objects::nonNull) + .collect(Collectors.toConcurrentMap(RouteConfiguration::getName, this::parseRouteConfiguration)); + } + + return Collections.emptyMap(); } public List parse(DiscoveryResponse response) { @@ -164,6 +176,40 @@ public class RdsProtocol extends AbstractProtocol { return xdsRouteAction; } + public XdsResourceListener getLdsListener() { + return ldsListener; + } + + private final XdsResourceListener ldsListener = resource -> { + Set set = resource.stream() + .flatMap(e -> listenerToConnectionManagerNames(e).stream()) + .collect(Collectors.toSet()); + this.subscribeResource(set); + }; + + private Set listenerToConnectionManagerNames(Listener resource) { + return resource.getFilterChainsList().stream() + .flatMap(e -> e.getFiltersList().stream()) + .map(Filter::getTypedConfig) + .map(this::unpackHttpConnectionManager) + .filter(Objects::nonNull) + .map(HttpConnectionManager::getRds) + .map(Rds::getRouteConfigName) + .collect(Collectors.toSet()); + } + + private HttpConnectionManager unpackHttpConnectionManager(Any any) { + try { + if (!any.is(HttpConnectionManager.class)) { + return null; + } + return any.unpack(HttpConnectionManager.class); + } catch (InvalidProtocolBufferException e) { + logger.error(REGISTRY_ERROR_RESPONSE_XDS, "", "", "Error occur when decode xDS response.", e); + return null; + } + } + private static RouteConfiguration unpackRouteConfiguration(Any any) { try { return any.unpack(RouteConfiguration.class); diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/registry/XdsServiceDiscovery.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/registry/XdsServiceDiscovery.java index febf221e3b..9e40fe6019 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/registry/XdsServiceDiscovery.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/registry/XdsServiceDiscovery.java @@ -23,8 +23,6 @@ import org.apache.dubbo.registry.client.ReflectionBasedServiceDiscovery; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.xds.PilotExchanger; -import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_INITIALIZE_XDS; - public class XdsServiceDiscovery extends ReflectionBasedServiceDiscovery { private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsServiceDiscovery.class); @@ -36,21 +34,21 @@ public class XdsServiceDiscovery extends ReflectionBasedServiceDiscovery { } public void doInitialize(URL registryURL) { - try { - exchanger = PilotExchanger.initialize(registryURL); - } catch (Throwable t) { - logger.error(REGISTRY_ERROR_INITIALIZE_XDS, "", "", t.getMessage(), t); - } + // try { + // exchanger = PilotExchanger.initialize(registryURL); + // } catch (Throwable t) { + // logger.error(REGISTRY_ERROR_INITIALIZE_XDS, "", "", t.getMessage(), t); + // } } public void doDestroy() { - try { - if (exchanger == null) { - return; - } - exchanger.destroy(); - } catch (Throwable t) { - logger.error(REGISTRY_ERROR_INITIALIZE_XDS, "", "", t.getMessage(), t); - } + // try { + // if (exchanger == null) { + // return; + // } + // exchanger.destroy(); + // } catch (Throwable t) { + // logger.error(REGISTRY_ERROR_INITIALIZE_XDS, "", "", t.getMessage(), t); + // } } } diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java index f28281b4b5..8c53e378e5 100644 --- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java @@ -18,6 +18,7 @@ package org.apache.dubbo.xds.router; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.utils.Holder; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; @@ -37,9 +38,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; +import static org.apache.dubbo.config.Constants.MESH_KEY; + public class XdsRouter extends AbstractStateRouter { - private final PilotExchanger pilotExchanger = PilotExchanger.getInstance(); + private final PilotExchanger pilotExchanger; private Map xdsVirtualHostMap = new ConcurrentHashMap<>(); @@ -47,6 +50,7 @@ public class XdsRouter extends AbstractStateRouter { public XdsRouter(URL url) { super(url); + pilotExchanger = url.getOrDefaultApplicationModel().getBeanFactory().getBean(PilotExchanger.class); } @Override @@ -60,8 +64,8 @@ public class XdsRouter extends AbstractStateRouter { throws RpcException { // return all invokers directly if xds is not used - // TODO:need to consider where to set ‘xds’ param - if (!url.getParameter("xds", false)) { + String meshType = url.getParameter(MESH_KEY); + if (StringUtils.isEmpty(meshType)) { return invokers; } @@ -76,7 +80,7 @@ public class XdsRouter extends AbstractStateRouter { private String matchCluster(Invocation invocation) { String cluster = null; - String serviceName = invocation.getInvoker().getUrl().getParameter("providedBy"); + String serviceName = invocation.getInvoker().getUrl().getParameter("provided-by"); XdsVirtualHost xdsVirtualHost = pilotExchanger.getXdsVirtualHostMap().get(serviceName); // match route @@ -85,12 +89,12 @@ public class XdsRouter extends AbstractStateRouter { String path = "/" + invocation.getInvoker().getUrl().getPath() + "/" + RpcUtils.getMethodName(invocation); if (xdsRoute.getRouteMatch().isMatch(path)) { cluster = xdsRoute.getRouteAction().getCluster(); - // if weighted cluster if (cluster == null) { cluster = computeWeightCluster(xdsRoute.getRouteAction().getClusterWeights()); } } + if (cluster != null) break; } return cluster; diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/CertificateConvertor.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/CertificateConvertor.java new file mode 100644 index 0000000000..6b64b4d9b5 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/CertificateConvertor.java @@ -0,0 +1,70 @@ +/* + * 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.xds.security; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.openssl.PEMParser; + +public class CertificateConvertor { + + private static final String END_CERTIFICATE = "-----END CERTIFICATE-----"; + + public static List readPemX509CertificateChains(List x590CertChains) + throws IOException, CertificateException { + List certs = new ArrayList<>(); + + for (String certChain : x590CertChains) { + String[] split = certChain.split(END_CERTIFICATE); + for (String c : split) { + certs.add(c + END_CERTIFICATE); + } + } + return readPemX509Certificates(certs); + } + + public static List readPemX509Certificates(List x509Certs) + throws IOException, CertificateException { + List certs = new ArrayList<>(); + JcaX509CertificateConverter converter = new JcaX509CertificateConverter(); + + for (String cert : x509Certs) { + X509CertificateHolder holder = readX509Certificate(cert); + certs.add(converter.getCertificate(holder)); + } + return certs; + } + + public static X509CertificateHolder readX509Certificate(File x509Cert) throws IOException { + PEMParser pemParser = new PEMParser(new FileReader(x509Cert)); + return (X509CertificateHolder) pemParser.readObject(); + } + + public static X509CertificateHolder readX509Certificate(String x509Cert) throws IOException { + PEMParser pemParser = new PEMParser(new StringReader(x509Cert)); + return (X509CertificateHolder) pemParser.readObject(); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/ProviderAuthFilter.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/ProviderAuthFilter.java new file mode 100644 index 0000000000..9cb1cca5c6 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/ProviderAuthFilter.java @@ -0,0 +1,58 @@ +/* + * 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.xds.security; + +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.security.api.RequestAuthorizer; + +import java.util.Arrays; +import java.util.List; + +@Activate(group = CommonConstants.PROVIDER) +public class ProviderAuthFilter implements Filter { + + private final List requestAuthorizers; + + public ProviderAuthFilter(ApplicationModel applicationModel) { + this.requestAuthorizers = + applicationModel.getExtensionLoader(RequestAuthorizer.class).getActivateExtensions(); + } + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + + String localSecurityConfig = invoker.getUrl().getParameter("security"); + if (StringUtils.isNotEmpty(localSecurityConfig)) { + List parts = Arrays.asList(localSecurityConfig.split(",")); + boolean enable = parts.stream().anyMatch("sa_jwt"::equals); + if (enable) { + for (RequestAuthorizer requestAuthorizer : requestAuthorizers) { + requestAuthorizer.validate(invocation); + } + } + } + return invoker.invoke(invocation); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/SecurityBeanConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/SecurityBeanConfig.java new file mode 100644 index 0000000000..bfe3f29d0e --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/SecurityBeanConfig.java @@ -0,0 +1,56 @@ +/* + * 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.xds.security; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ModuleModel; +import org.apache.dubbo.rpc.model.ScopeModelInitializer; +import org.apache.dubbo.xds.kubernetes.KubeApiClient; +import org.apache.dubbo.xds.kubernetes.KubeEnv; +import org.apache.dubbo.xds.security.api.XdsCertProvider; +import org.apache.dubbo.xds.security.authz.rule.source.MapRuleFactory; + +import java.io.IOException; + +public class SecurityBeanConfig implements ScopeModelInitializer { + + private ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(SecurityBeanConfig.class); + + @Override + public void initializeFrameworkModel(FrameworkModel frameworkModel) { + frameworkModel.getBeanFactory().getOrRegisterBean(XdsCertProvider.class); + } + + @Override + public void initializeApplicationModel(ApplicationModel applicationModel) { + KubeEnv env = applicationModel.getBeanFactory().getOrRegisterBean(KubeEnv.class); + try { + if (env.getServiceAccountToken().length > 0) { + applicationModel.getBeanFactory().getOrRegisterBean(KubeApiClient.class); + applicationModel.getBeanFactory().getOrRegisterBean(MapRuleFactory.class); + } + } catch (IOException e) { + logger.info("SecurityBeanConfig are not initialized because SA token not found."); + } + } + + @Override + public void initializeModuleModel(ModuleModel moduleModel) {} +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/AuthorizationException.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/AuthorizationException.java new file mode 100644 index 0000000000..c1232b7c05 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/AuthorizationException.java @@ -0,0 +1,32 @@ +/* + * 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.xds.security.api; + +public class AuthorizationException extends RuntimeException { + + public AuthorizationException(Throwable cause) { + super(cause); + } + + public AuthorizationException(String message) { + super(message); + } + + public AuthorizationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/CertPair.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/CertPair.java new file mode 100644 index 0000000000..03e3095bd8 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/CertPair.java @@ -0,0 +1,66 @@ +/* + * 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.xds.security.api; + +public class CertPair { + + private final String privateKey; + private final String publicKey; + private final String password; + private final long createTime; + private final long expireTime; + + public CertPair(String privateKey, String publicKey, long createTime, long expireTime) { + this.privateKey = privateKey; + this.publicKey = publicKey; + this.createTime = createTime; + this.expireTime = expireTime; + this.password = null; + } + + public CertPair(String privateKey, String publicKey, String password, long createTime, long expireTime) { + this.privateKey = privateKey; + this.publicKey = publicKey; + this.password = password; + this.createTime = createTime; + this.expireTime = expireTime; + } + + public String getPrivateKey() { + return privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public long getCreateTime() { + return createTime; + } + + public boolean isExpire() { + return System.currentTimeMillis() < expireTime; + } + + public long getExpireTime() { + return expireTime; + } + + public String getPassword() { + return password; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/CertSource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/CertSource.java new file mode 100644 index 0000000000..ead41430f7 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/CertSource.java @@ -0,0 +1,38 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.xds.security.authn.SecretConfig; + +import java.util.List; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface CertSource { + + /** + * Use selected config to generate cert pair + */ + CertPair getCert(URL url, SecretConfig secretConfig); + + /** + * Select one supported cert config for CertSource. Returns null if no supported cert config found. + */ + SecretConfig selectSupportedCertConfig(URL url, List secretConfig); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/DataSources.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/DataSources.java new file mode 100644 index 0000000000..a64bad08e0 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/DataSources.java @@ -0,0 +1,114 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.IOUtils; +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.common.utils.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import io.envoyproxy.envoy.config.core.v3.DataSource; + +public enum DataSources { + + /** + * this DataSource represents a file path + */ + LOCAL_FILE, + + /** + * this DataSource represents an environment variable + */ + ENVIRONMENT_VARIABLE, + + /** + * this DataSource represents an inline string + */ + INLINE_STRING, + + /** + * this DataSource represents inline bytes + */ + INLINE_BYTES; + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DataSources.class); + + public static Pair resolveDataSource(DataSource dataSource) { + if (dataSource.hasFilename()) { + return new Pair<>(dataSource.getFilename(), LOCAL_FILE); + } + if (dataSource.hasEnvironmentVariable()) { + return new Pair<>(dataSource.getEnvironmentVariable(), ENVIRONMENT_VARIABLE); + } + if (dataSource.hasInlineString()) { + return new Pair<>(dataSource.getInlineString(), INLINE_STRING); + } + if (dataSource.hasInlineBytes()) { + return new Pair<>(dataSource.getInlineBytes().toStringUtf8(), INLINE_BYTES); + } + throw new IllegalArgumentException("Unknown data source type"); + } + + public static String readActualValue(Pair dataSource) { + return readActualValue(dataSource, null); + } + + public static String readActualValue(Pair dataSource, FileWatcher watcher) { + switch (dataSource.getValue()) { + case LOCAL_FILE: + if (watcher != null) { + String value = new String(watcher.readWatchedFile(dataSource.getKey())); + if (StringUtils.isEmpty(value)) { + try { + watcher.registerWatch(dataSource.getKey()); + return new String(watcher.readWatchedFile(dataSource.getKey())); + } catch (Exception e) { + logger.warn("99-1", "", "", "Failed to register watch for file: " + dataSource.getKey(), e); + } + } + } + try { + return IOUtils.read( + Files.newInputStream(Paths.get(dataSource.getKey())), StandardCharsets.UTF_8.name()); + } catch (Exception e) { + logger.error("99-1", "", "", "Failed to read file: " + dataSource.getKey(), e); + return null; + } + case ENVIRONMENT_VARIABLE: + return System.getenv(dataSource.getKey()); + case INLINE_STRING: + case INLINE_BYTES: + // bytes were read as UTF-8 string + return dataSource.getKey(); + default: + throw new IllegalArgumentException("Unknown data source type"); + } + } + + public static String readActualValue(DataSource dataSource, FileWatcher watcher) { + return readActualValue(resolveDataSource(dataSource), watcher); + } + + public static String readActualValue(DataSource dataSource) { + return readActualValue(resolveDataSource(dataSource)); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/FileWatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/FileWatcher.java new file mode 100644 index 0000000000..e89611379a --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/FileWatcher.java @@ -0,0 +1,107 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.IOUtils; +import org.apache.dubbo.common.utils.LRUCache; +import org.apache.dubbo.common.utils.Pair; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; + +public class FileWatcher { + + private final LRUCache> filesToWatch = new LRUCache<>(256); + + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); + + public void registerWatch(String path) throws Exception { + registerWatch(path, 3000); + } + + public byte[] readWatchedFile(String path) { + Pair pair = filesToWatch.get(path); + if (pair == null) { + try { + registerWatch(path); + pair = filesToWatch.get(path); + } catch (Exception e) { + logger.warn("", "", "", "Failed to register watch file in path=" + path, e); + return null; + } + } + return pair == null ? null : pair.getLeft(); + } + + public void registerWatch(String path, long checkInterval) throws Exception { + FileAlterationObserver observer = new FileAlterationObserver(path); + FileAlterationMonitor monitor = new FileAlterationMonitor(checkInterval); + FileAlterationListener listener = new FileAlterationListenerAdaptor() { + @Override + public void onStart(FileAlterationObserver observer) { + try { + filesToWatch.put( + path, new Pair<>(IOUtils.toByteArray(Files.newInputStream(Paths.get(path))), monitor)); + } catch (IOException e) { + logger.warn("", "", "", "Failed to read file in path=" + path); + } + } + + @Override + public void onFileChange(File file) { + try { + filesToWatch.put( + path, new Pair<>(IOUtils.toByteArray(Files.newInputStream(file.toPath())), monitor)); + } catch (IOException e) { + logger.error("", e.getCause().toString(), "", "Failed to read changed file.", e); + } + } + + @Override + public void onFileCreate(File file) { + try { + filesToWatch.put( + path, new Pair<>(IOUtils.toByteArray(Files.newInputStream(file.toPath())), monitor)); + } catch (IOException e) { + logger.error("", e.getCause().toString(), "", "Failed to read newly create file.", e); + } + } + + @Override + public void onFileDelete(File file) { + Pair removed = filesToWatch.remove(path); + try { + removed.getRight().stop(); + } catch (Exception e) { + logger.error("", e.getCause().toString(), "", "Failed to stop watch deleted file.", e); + } + } + }; + observer.addListener(listener); + monitor.addObserver(observer); + monitor.start(); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/LocalSecretProvider.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/LocalSecretProvider.java new file mode 100644 index 0000000000..a1b45ea3a3 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/LocalSecretProvider.java @@ -0,0 +1,127 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.xds.security.authn.FileSecretConfig; +import org.apache.dubbo.xds.security.authn.SecretConfig; +import org.apache.dubbo.xds.security.authn.SecretConfig.ConfigType; +import org.apache.dubbo.xds.security.authn.SecretConfig.Source; + +import java.util.List; +import java.util.function.Predicate; + +@Activate +public class LocalSecretProvider implements CertSource, TrustSource { + + private ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); + + private FileWatcher watcher = new FileWatcher(); + + @Override + public CertPair getCert(URL url, SecretConfig secretConfig) { + + if (!(secretConfig instanceof FileSecretConfig)) { + throw new IllegalStateException("Given config not a FileSecret:" + secretConfig); + } + + FileSecretConfig fileSecretConfig = (FileSecretConfig) secretConfig; + + if (fileSecretConfig.getCertChain() == null) { + throw new IllegalStateException("CertChain can't be null:" + secretConfig); + } + if (fileSecretConfig.getPrivateKey() == null) { + throw new IllegalStateException("PrivateKey can't be null:" + secretConfig); + } + + String certChain = DataSources.readActualValue(fileSecretConfig.getCertChain(), watcher); + String privateKey = DataSources.readActualValue(fileSecretConfig.getPrivateKey(), watcher); + String password; + if (fileSecretConfig.getPassword() != null) { + password = DataSources.readActualValue(fileSecretConfig.getPassword(), watcher); + } else { + password = null; + } + // TODO how to determine expire time + return new CertPair(certChain, privateKey, password, System.currentTimeMillis(), Long.MAX_VALUE); + } + + @Override + public SecretConfig selectSupportedCertConfig(URL url, List secretConfigs) { + return selectSupportedConfig( + secretConfigs, + secretConfig -> ConfigType.CERT.equals(secretConfig.configType()) + && Source.LOCAL.equals(secretConfig.source())); + } + + @Override + public SecretConfig selectSupportedTrustConfig(URL url, List secretConfigs) { + return selectSupportedConfig( + secretConfigs, + secretConfig -> ConfigType.TRUST.equals(secretConfig.configType()) + && Source.LOCAL.equals(secretConfig.source())); + } + + @Override + public X509CertChains getTrustCerts(URL url, SecretConfig secretConfig) { + + if (!(secretConfig instanceof FileSecretConfig)) { + throw new IllegalStateException("Given config not a FileSecret:" + secretConfig); + } + FileSecretConfig config = (FileSecretConfig) secretConfig; + + if (config.getTrust() == null) { + throw new IllegalStateException("Trust can't be null:" + secretConfig); + } + String trust = DataSources.readActualValue(config.getTrust(), watcher); + // TODO how to determine expire time + return new X509CertChains(trust, System.currentTimeMillis(), Long.MAX_VALUE); + } + + private SecretConfig selectSupportedConfig(List secretConfig, Predicate selector) { + SecretConfig config = secretConfig.stream().filter(selector).findFirst().orElse(null); + if (config == null) { + return null; + } + FileSecretConfig secret = (FileSecretConfig) config; + try { + if (DataSources.LOCAL_FILE.equals(secret.getCertChain().getValue())) { + watcher.registerWatch(secret.getCertChain().getKey()); + } + if (DataSources.LOCAL_FILE.equals(secret.getPrivateKey().getValue())) { + watcher.registerWatch(secret.getPrivateKey().getKey()); + } + if (secret.getPassword() != null + && DataSources.LOCAL_FILE.equals(secret.getPassword().getValue())) { + watcher.registerWatch(secret.getPassword().getKey()); + } + } catch (Exception e) { + logger.warn( + "", + "", + "", + "Failed to watch local file secrets, SecretConfig are removed from list. config=" + secret, + e); + secretConfig.remove(config); + selectSupportedConfig(secretConfig, selector); + } + return secret; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/RequestAuthorizer.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/RequestAuthorizer.java new file mode 100644 index 0000000000..0dde342b3d --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/RequestAuthorizer.java @@ -0,0 +1,27 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.Invocation; + +@SPI(scope = ExtensionScope.APPLICATION) +public interface RequestAuthorizer { + + void validate(Invocation invocation) throws AuthorizationException; +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/ServiceIdentitySource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/ServiceIdentitySource.java new file mode 100644 index 0000000000..34f38abe21 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/ServiceIdentitySource.java @@ -0,0 +1,34 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; + +/** + * Service identity source. Provided JWT will attach to request and can be used for further authentication. + */ +@SPI(value = "noOp", scope = ExtensionScope.APPLICATION) +public interface ServiceIdentitySource { + + String SERVICE_IDENTITY_KEY = "serviceIdentity"; + + @Adaptive(value = {SERVICE_IDENTITY_KEY}) + String getToken(URL url); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/TrustSource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/TrustSource.java new file mode 100644 index 0000000000..b3622fb196 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/TrustSource.java @@ -0,0 +1,32 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.xds.security.authn.SecretConfig; + +import java.util.List; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface TrustSource { + + X509CertChains getTrustCerts(URL url, SecretConfig secretConfig); + + SecretConfig selectSupportedTrustConfig(URL url, List secretConfig); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/X509CertChains.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/X509CertChains.java new file mode 100644 index 0000000000..06a0cf65e1 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/X509CertChains.java @@ -0,0 +1,71 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.xds.istio.IstioEnv; +import org.apache.dubbo.xds.security.CertificateConvertor; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; + +public class X509CertChains { + + private final byte[] trustChainBytes; + + private final long createTime; + + private final long expireAt; + + public X509CertChains(List pemTrustChains) { + + StringBuilder builder = new StringBuilder(); + for (String str : pemTrustChains) { + builder.append(str); + } + + this.trustChainBytes = builder.toString().getBytes(StandardCharsets.UTF_8); + this.createTime = System.currentTimeMillis(); + this.expireAt = createTime + IstioEnv.getInstance().getTrustTTL(); + } + + public X509CertChains(String pemTrustChains, long createTime, long expireAt) { + this.trustChainBytes = pemTrustChains.getBytes(StandardCharsets.UTF_8); + this.createTime = createTime; + this.expireAt = expireAt; + } + + public List readAsCerts() throws CertificateException, IOException { + return CertificateConvertor.readPemX509CertificateChains( + Collections.singletonList(new String(trustChainBytes, StandardCharsets.UTF_8))); + } + + public byte[] readAsBytes() { + return trustChainBytes; + } + + public long getExpireAt() { + return expireAt; + } + + public long getCreateTime() { + return createTime; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/XdsCertProvider.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/XdsCertProvider.java new file mode 100644 index 0000000000..ee0b9b014d --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/api/XdsCertProvider.java @@ -0,0 +1,185 @@ +/* + * 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.xds.security.api; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.ssl.AuthPolicy; +import org.apache.dubbo.common.ssl.Cert; +import org.apache.dubbo.common.ssl.CertProvider; +import org.apache.dubbo.common.ssl.ProviderCert; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.xds.PilotExchanger; +import org.apache.dubbo.xds.istio.IstioEnv; +import org.apache.dubbo.xds.listener.XdsTlsConfigRepository; +import org.apache.dubbo.xds.security.authn.DownstreamTlsConfig; +import org.apache.dubbo.xds.security.authn.SecretConfig; +import org.apache.dubbo.xds.security.authn.UpstreamTlsConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; + +import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; + +@Activate +public class XdsCertProvider implements CertProvider { + + private final List trustSource; + + private final List certSource; + + private final XdsTlsConfigRepository configRepo; + + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsCertProvider.class); + + private final IstioEnv istioEnv = IstioEnv.getInstance(); + + public XdsCertProvider(FrameworkModel frameworkModel) { + this.configRepo = frameworkModel.getBeanFactory().getOrRegisterBean(XdsTlsConfigRepository.class); + if (frameworkModel.getBeanFactory().getBean(PilotExchanger.class) == null) { + logger.info("XdsCertProvider won't initialize because XDS Client not found."); + this.trustSource = Collections.emptyList(); + this.certSource = Collections.emptyList(); + return; + } + this.trustSource = frameworkModel.getExtensionLoader(TrustSource.class).getActivateExtensions(); + this.certSource = frameworkModel.getExtensionLoader(CertSource.class).getActivateExtensions(); + } + + @Override + public boolean isSupport(URL address) { + String side = address.getSide(); + if (CONSUMER.equals(side)) { + // TODO: If XDS URL can support version tag, key should be address.getServiceKey() + UpstreamTlsConfig upstreamConfig = configRepo.getUpstreamConfig(address.getServiceInterface()); + if (upstreamConfig == null || upstreamConfig.getGeneralTlsConfig() == null) { + return false; + } + List trustConfigs = + upstreamConfig.getGeneralTlsConfig().trustConfigs(); + List certConfigs = + upstreamConfig.getGeneralTlsConfig().certConfigs(); + + // At least one config provided by LDS + return !trustConfigs.isEmpty() || !certConfigs.isEmpty(); + } else if (PROVIDER.equals(side)) { + DownstreamTlsConfig downstreamConfig = configRepo.getDownstreamConfig(String.valueOf(address.getPort())); + if (downstreamConfig == null) { + return false; + } + List secretConfigs = + downstreamConfig.getGeneralTlsConfig().certConfigs(); + List certConfigs = + downstreamConfig.getGeneralTlsConfig().trustConfigs(); + + // At least one config provided by CDS + return !secretConfigs.isEmpty() || !certConfigs.isEmpty(); + } + throw new IllegalStateException("Can't determine side for url:" + address); + + // seems we don't need url to check here anymore + // if (TlsType.PERMISSIVE.equals(type)) { + // String security = address.getParameter("security"); + // String mesh = address.getParameter("mesh"); + // return mesh != null + // && security != null + // && Arrays.asList(security.split(",")).contains("mTLS"); + // } + } + + @Override + public ProviderCert getProviderConnectionConfig(URL localAddress) { + DownstreamTlsConfig downstreamConfig = configRepo.getDownstreamConfig(String.valueOf(localAddress.getPort())); + + if (downstreamConfig == null || downstreamConfig.getGeneralTlsConfig() == null) { + logger.warn("99-0", "", "", "DownstreamTlsConfig is null for localAddress:" + localAddress); + return null; + } + + CertPair cert = selectCertConfig( + localAddress, downstreamConfig.getGeneralTlsConfig().certConfigs()); + X509CertChains trust = selectTrustConfig( + localAddress, downstreamConfig.getGeneralTlsConfig().trustConfigs()); + + AuthPolicy authPolicy; + switch (downstreamConfig.getTlsType()) { + case STRICT: + authPolicy = AuthPolicy.CLIENT_AUTH_STRICT; + break; + case PERMISSIVE: + authPolicy = AuthPolicy.CLIENT_AUTH_PERMISSIVE; + break; + case DISABLE: + authPolicy = AuthPolicy.NONE; + break; + default: + throw new IllegalStateException("Unexpected Tls type: " + downstreamConfig.getTlsType()); + } + return new ProviderCert( + cert == null ? null : cert.getPublicKey().getBytes(StandardCharsets.UTF_8), + cert == null ? null : cert.getPrivateKey().getBytes(StandardCharsets.UTF_8), + trust == null ? null : trust.readAsBytes(), + cert == null ? null : cert.getPassword(), + authPolicy); + } + + @Override + public Cert getConsumerConnectionConfig(URL remoteAddress) { + UpstreamTlsConfig downstreamConfig = configRepo.getUpstreamConfig(remoteAddress.getServiceInterface()); + + if (downstreamConfig == null) { + logger.warn("99-0", "", "", "DownstreamTlsConfig is null for remoteUrl:" + remoteAddress); + return null; + } + + CertPair cert = selectCertConfig( + remoteAddress, downstreamConfig.getGeneralTlsConfig().certConfigs()); + X509CertChains trust = selectTrustConfig( + remoteAddress, downstreamConfig.getGeneralTlsConfig().trustConfigs()); + + return new ProviderCert( + cert == null ? null : cert.getPublicKey().getBytes(StandardCharsets.UTF_8), + cert == null ? null : cert.getPrivateKey().getBytes(StandardCharsets.UTF_8), + trust == null ? null : trust.readAsBytes(), + cert == null ? null : cert.getPassword(), + AuthPolicy.SERVER_AUTH); + } + + private CertPair selectCertConfig(URL address, List certConfigs) { + for (CertSource certSource : this.certSource) { + SecretConfig secretConfig = certSource.selectSupportedCertConfig(address, certConfigs); + if (secretConfig != null) { + return certSource.getCert(address, secretConfig); + } + } + return null; + } + + private X509CertChains selectTrustConfig(URL address, List certConfigs) { + for (TrustSource trustSource : this.trustSource) { + SecretConfig secretConfig = trustSource.selectSupportedTrustConfig(address, certConfigs); + if (secretConfig != null) { + return trustSource.getTrustCerts(address, secretConfig); + } + } + return null; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/DownstreamTlsConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/DownstreamTlsConfig.java new file mode 100644 index 0000000000..eda98bfeda --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/DownstreamTlsConfig.java @@ -0,0 +1,90 @@ +/* + * 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.xds.security.authn; + +import org.apache.dubbo.xds.listener.DownstreamTlsConfigListener.TlsType; + +/** + * TlsConfig for inbound connection + */ +public class DownstreamTlsConfig { + + private GeneralTlsConfig generalTlsConfig; + + private boolean requireClientCertificate; + + private boolean requireSni; + + private long sessionTimeout; + + private TlsType tlsType; + + public DownstreamTlsConfig( + GeneralTlsConfig generalTlsConfig, + boolean requireClientCertificate, + boolean requireSni, + long sessionTimeout) { + this.generalTlsConfig = generalTlsConfig; + this.requireClientCertificate = requireClientCertificate; + this.requireSni = requireSni; + this.sessionTimeout = sessionTimeout; + } + + public DownstreamTlsConfig(TlsType tlsType) { + this.tlsType = tlsType; + } + + public GeneralTlsConfig getGeneralTlsConfig() { + return generalTlsConfig; + } + + public void setGeneralTlsConfig(GeneralTlsConfig generalTlsConfig) { + this.generalTlsConfig = generalTlsConfig; + } + + public boolean isRequireClientCertificate() { + return requireClientCertificate; + } + + public void setRequireClientCertificate(boolean requireClientCertificate) { + this.requireClientCertificate = requireClientCertificate; + } + + public boolean isRequireSni() { + return requireSni; + } + + public void setRequireSni(boolean requireSni) { + this.requireSni = requireSni; + } + + public long getSessionTimeout() { + return sessionTimeout; + } + + public void setSessionTimeout(long sessionTimeout) { + this.sessionTimeout = sessionTimeout; + } + + public void setTlsType(TlsType tlsType) { + this.tlsType = tlsType; + } + + public TlsType getTlsType() { + return tlsType; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/FileSecretConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/FileSecretConfig.java new file mode 100644 index 0000000000..e1de724512 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/FileSecretConfig.java @@ -0,0 +1,114 @@ +/* + * 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.xds.security.authn; + +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.xds.security.api.DataSources; + +import io.envoyproxy.envoy.config.core.v3.DataSource; + +public class FileSecretConfig implements SecretConfig { + + private final String name; + + private final ConfigType configType; + + private final Pair certChain; + + private final Pair privateKey; + + private final Pair password; + + private final Pair trust; + + public FileSecretConfig(String name, DataSource certChain, DataSource privateKey, DataSource password) { + this.name = name; + this.configType = ConfigType.CERT; + this.certChain = DataSources.resolveDataSource(certChain); + this.privateKey = DataSources.resolveDataSource(privateKey); + if (password != null) { + this.password = DataSources.resolveDataSource(password); + } else { + this.password = null; + } + this.trust = null; + } + + public FileSecretConfig(String name, DataSource certChain, DataSource privateKey) { + this.name = name; + this.configType = ConfigType.CERT; + this.certChain = DataSources.resolveDataSource(certChain); + this.privateKey = DataSources.resolveDataSource(privateKey); + this.password = null; + this.trust = null; + } + + public FileSecretConfig(String name, DataSource trust) { + this.name = name; + this.configType = ConfigType.TRUST; + this.trust = DataSources.resolveDataSource(trust); + this.certChain = null; + this.password = null; + this.privateKey = null; + } + + @Override + public String name() { + return name; + } + + @Override + public ConfigType configType() { + return configType; + } + + @Override + public Source source() { + return Source.LOCAL; + } + + public String getName() { + return name; + } + + public Pair getCertChain() { + return certChain; + } + + public Pair getPrivateKey() { + return privateKey; + } + + public Pair getPassword() { + return password; + } + + public Pair getTrust() { + return trust; + } + + @Override + public String toString() { + return "FileSecret{" + "name='" + name + '\'' + ", configType=" + configType + ", certChain=" + certChain + + ", privateKey=" + privateKey + ", password=" + password + '}'; + } + + public enum DefaultNames { + LOCAL_TRUST, + LOCAL_CERT + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/GeneralTlsConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/GeneralTlsConfig.java new file mode 100644 index 0000000000..cc97c9b492 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/GeneralTlsConfig.java @@ -0,0 +1,68 @@ +/* + * 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.xds.security.authn; + +import java.util.Collections; +import java.util.List; + +public class GeneralTlsConfig { + + /** + * Name to identify this config, like port or cluster name + */ + private String name; + + private List certConfigs; + + private List trustConfigs; + + /** + * L7 protocols + */ + private List alpnProtocols; + + public GeneralTlsConfig(String name) { + this.name = name; + this.certConfigs = Collections.emptyList(); + this.trustConfigs = Collections.emptyList(); + this.alpnProtocols = Collections.emptyList(); + } + + public GeneralTlsConfig( + String name, List certConfigs, List trustConfigs, List alpnProtocols) { + this.name = name; + this.certConfigs = certConfigs; + this.trustConfigs = trustConfigs; + this.alpnProtocols = alpnProtocols; + } + + public String getName() { + return name; + } + + public List certConfigs() { + return certConfigs; + } + + public List trustConfigs() { + return trustConfigs; + } + + public List alpnProtocols() { + return alpnProtocols; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/SdsSecretConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/SdsSecretConfig.java new file mode 100644 index 0000000000..50b5aca26e --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/SdsSecretConfig.java @@ -0,0 +1,73 @@ +/* + * 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.xds.security.authn; + +import io.envoyproxy.envoy.config.core.v3.ApiConfigSource; + +public class SdsSecretConfig implements SecretConfig { + + private String configName; + + private ConfigType configType; + + private ApiConfigSource apiConfigSource; + + public SdsSecretConfig(String configName, ConfigType configType, ApiConfigSource apiConfigSource) { + this.configName = configName; + this.configType = configType; + this.apiConfigSource = apiConfigSource; + } + + @Override + public String name() { + return configName; + } + + @Override + public ConfigType configType() { + return configType; + } + + @Override + public Source source() { + return Source.SDS; + } + + public String getConfigName() { + return configName; + } + + public void setConfigName(String configName) { + this.configName = configName; + } + + public ConfigType getConfigType() { + return configType; + } + + public void setConfigType(ConfigType configType) { + this.configType = configType; + } + + public ApiConfigSource getApiConfigSource() { + return apiConfigSource; + } + + public void setApiConfigSource(ApiConfigSource apiConfigSource) { + this.apiConfigSource = apiConfigSource; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/SecretConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/SecretConfig.java new file mode 100644 index 0000000000..edbee825c3 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/SecretConfig.java @@ -0,0 +1,42 @@ +/* + * 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.xds.security.authn; + +public interface SecretConfig { + + /** + * name of this config + */ + String name(); + + /** + * this config indicates a cert or trust + */ + ConfigType configType(); + + Source source(); + + enum ConfigType { + TRUST, + CERT + } + + enum Source { + SDS, + LOCAL + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/TlsResourceResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/TlsResourceResolver.java new file mode 100644 index 0000000000..40c865d84f --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/TlsResourceResolver.java @@ -0,0 +1,93 @@ +/* + * 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.xds.security.authn; + +import org.apache.dubbo.xds.security.authn.FileSecretConfig.DefaultNames; +import org.apache.dubbo.xds.security.authn.SecretConfig.ConfigType; + +import java.util.ArrayList; +import java.util.List; + +import io.envoyproxy.envoy.config.core.v3.DataSource; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext; + +public class TlsResourceResolver { + + public static GeneralTlsConfig resolveCommonTlsConfig(String configName, CommonTlsContext commonTlsContext) { + List trustConfigs = new ArrayList<>(); + List certConfigs = new ArrayList<>(); + + // sds cert sources + List sdsCertConfigs = + commonTlsContext.getTlsCertificateSdsSecretConfigsList(); + sdsCertConfigs.forEach(sdsSecretConfig -> certConfigs.add(new SdsSecretConfig( + sdsSecretConfig.getName(), + ConfigType.CERT, + sdsSecretConfig.getSdsConfig().getApiConfigSource()))); + + // file cert sources + commonTlsContext.getTlsCertificatesList().forEach(tlsCertificate -> { + DataSource certChain = tlsCertificate.getCertificateChain(); + DataSource privateKey = tlsCertificate.getPrivateKey(); + certConfigs.add(new FileSecretConfig( + DefaultNames.LOCAL_CERT.name(), + privateKey, + certChain, + tlsCertificate.hasPassword() ? tlsCertificate.getPassword() : null)); + }); + + // sds trust sources + io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig sdsTrustConfig = + commonTlsContext.getValidationContextSdsSecretConfig(); + if (commonTlsContext.hasValidationContextSdsSecretConfig()) { + trustConfigs.add(new SdsSecretConfig( + sdsTrustConfig.getName(), + ConfigType.TRUST, + sdsTrustConfig.getSdsConfig().getApiConfigSource())); + } + + // file trust sources + if (commonTlsContext.hasValidationContext() + && commonTlsContext.getValidationContext().hasTrustedCa()) { + trustConfigs.add(new FileSecretConfig( + DefaultNames.LOCAL_TRUST.name(), + commonTlsContext.getValidationContext().getTrustedCa())); + } + + CombinedCertificateValidationContext combinedConfig = commonTlsContext.getCombinedValidationContext(); + if (commonTlsContext.hasCombinedValidationContext()) { + if (combinedConfig.hasValidationContextSdsSecretConfig()) { + io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig sdsConfig = + combinedConfig.getValidationContextSdsSecretConfig(); + trustConfigs.add(new SdsSecretConfig( + sdsConfig.getName(), + ConfigType.TRUST, + sdsConfig.getSdsConfig().getApiConfigSource())); + } + if (combinedConfig.hasDefaultValidationContext()) { + CertificateValidationContext defaultConfig = combinedConfig.getDefaultValidationContext(); + if (defaultConfig.hasTrustedCa()) { + trustConfigs.add( + new FileSecretConfig(DefaultNames.LOCAL_TRUST.name(), defaultConfig.getTrustedCa())); + } + } + } + return new GeneralTlsConfig(configName, trustConfigs, certConfigs, commonTlsContext.getAlpnProtocolsList()); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/UpstreamTlsConfig.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/UpstreamTlsConfig.java new file mode 100644 index 0000000000..e50bd4c454 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authn/UpstreamTlsConfig.java @@ -0,0 +1,61 @@ +/* + * 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.xds.security.authn; + +/** + * Tls config for outbound connection + */ +public class UpstreamTlsConfig { + + public GeneralTlsConfig generalTlsConfig; + + private String sni; + + private boolean allowRenegotiation; + + public UpstreamTlsConfig(GeneralTlsConfig generalTlsConfig, String sni, boolean allowRenegotiation) { + this.generalTlsConfig = generalTlsConfig; + this.sni = sni; + this.allowRenegotiation = allowRenegotiation; + } + + public UpstreamTlsConfig() {} + + public GeneralTlsConfig getGeneralTlsConfig() { + return generalTlsConfig; + } + + public void setGeneralTlsConfig(GeneralTlsConfig generalTlsConfig) { + this.generalTlsConfig = generalTlsConfig; + } + + public String getSni() { + return sni; + } + + public void setSni(String sni) { + this.sni = sni; + } + + public boolean isAllowRenegotiation() { + return allowRenegotiation; + } + + public void setAllowRenegotiation(boolean allowRenegotiation) { + this.allowRenegotiation = allowRenegotiation; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/AuthorizationRequestContext.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/AuthorizationRequestContext.java new file mode 100644 index 0000000000..6d6f034d19 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/AuthorizationRequestContext.java @@ -0,0 +1,114 @@ +/* + * 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.xds.security.authz; + +import org.apache.dubbo.rpc.Invocation; + +import java.util.LinkedList; +import java.util.List; + +public class AuthorizationRequestContext { + + private final Invocation invocation; + + private final RequestCredential requestCredential; + + private boolean failed = false; + + private Exception validationException; + + private boolean enableTrace = false; + + private List validateStackTrace; + + private int depth; + + private static final int MAX_DEPTH = 50; + + public AuthorizationRequestContext(Invocation invocation, RequestCredential requestCredential) { + this.invocation = invocation; + this.requestCredential = requestCredential; + } + + public void depthIncrease() { + this.depth++; + if (depth > MAX_DEPTH) { + throw new IllegalStateException("Rule tree depth exceed limit:" + MAX_DEPTH); + } + } + + public void depthDecrease() { + this.depth--; + } + + public void startTrace() { + this.enableTrace = true; + } + + public void endTrace() { + this.enableTrace = false; + } + + public boolean enableTrace() { + return this.enableTrace; + } + + public boolean isFailed() { + return failed; + } + + public void setFailed(boolean failed) { + this.failed = failed; + } + + public Exception getValidationException() { + return validationException; + } + + public void setValidationException(Exception validationException) { + this.validationException = validationException; + } + + public RequestCredential getRequestCredential() { + return requestCredential; + } + + public void addTraceInfo(String info) { + if (!enableTrace) { + return; + } + if (validateStackTrace == null) { + validateStackTrace = new LinkedList<>(); + } + validateStackTrace.add(getNtab() + info); + } + ; + + public String getTraceInfo() { + StringBuilder builder = new StringBuilder(); + validateStackTrace.forEach(info -> builder.append(info).append("\n")); + return builder.toString(); + } + + private String getNtab() { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < depth; i++) { + builder.append(" "); + } + return builder.toString(); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/ConsumerServiceAccountAuthFilter.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/ConsumerServiceAccountAuthFilter.java new file mode 100644 index 0000000000..6a9b9e0677 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/ConsumerServiceAccountAuthFilter.java @@ -0,0 +1,51 @@ +/* + * 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.xds.security.authz; + +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.security.api.ServiceIdentitySource; + +import static org.apache.dubbo.rpc.Constants.ID_TOKEN_KEY; + +@Activate(group = CommonConstants.CONSUMER, order = -10000) +public class ConsumerServiceAccountAuthFilter implements Filter { + + private final ServiceIdentitySource serviceIdentitySource; + + public ConsumerServiceAccountAuthFilter(ApplicationModel applicationModel) { + this.serviceIdentitySource = applicationModel.getAdaptiveExtension(ServiceIdentitySource.class); + } + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + String token = serviceIdentitySource.getToken(invoker.getUrl()); + if (StringUtils.isNotEmpty(token)) { + // TODO Attach it based on protocol can work better with other systems, + // like standard HTTP cookie/authorization header + invocation.setObjectAttachment(ID_TOKEN_KEY, token); + } + return invoker.invoke(invocation); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/RequestCredential.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/RequestCredential.java new file mode 100644 index 0000000000..42238ffc34 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/RequestCredential.java @@ -0,0 +1,26 @@ +/* + * 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.xds.security.authz; + +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +public interface RequestCredential { + + Object get(RequestAuthProperty propertyType); + + void add(RequestAuthProperty propertyType, Object value); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/RoleBasedAuthorizer.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/RoleBasedAuthorizer.java new file mode 100644 index 0000000000..421087e563 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/RoleBasedAuthorizer.java @@ -0,0 +1,194 @@ +/* + * 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.xds.security.authz; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.security.api.AuthorizationException; +import org.apache.dubbo.xds.security.api.RequestAuthorizer; +import org.apache.dubbo.xds.security.authz.resolver.CredentialResolver; +import org.apache.dubbo.xds.security.authz.rule.CommonRequestCredential; +import org.apache.dubbo.xds.security.authz.rule.source.RuleFactory; +import org.apache.dubbo.xds.security.authz.rule.source.RuleProvider; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleNode.Relation; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot.Action; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import io.micrometer.core.instrument.config.validate.ValidationException; + +@Activate +public class RoleBasedAuthorizer implements RequestAuthorizer { + + private final RuleProvider ruleProvider; + + private final List credentialResolver; + + private final RuleFactory ruleFactory; + + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RoleBasedAuthorizer.class); + + /** + * TODO + * Cached rules + * Connection Identity -> Authorization Rules + * Here are two problems: + * 1.How to identify remote connection (may we can use [protocol:port]) + * 2.How to remove cache when remote connection is disconnected + */ + private final Map> rules = new ConcurrentHashMap<>(); + + public RoleBasedAuthorizer(ApplicationModel applicationModel) { + this.ruleProvider = applicationModel.getAdaptiveExtension(RuleProvider.class); + this.credentialResolver = applicationModel.getActivateExtensions(CredentialResolver.class); + this.ruleFactory = applicationModel.getAdaptiveExtension(RuleFactory.class); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public void validate(Invocation invocation) throws AuthorizationException { + + List rulesSources = ruleProvider.getSource(invocation.getInvoker().getUrl(), invocation); + List roots = ruleFactory.getRules(invocation.getInvoker().getUrl(), rulesSources); + + List logRules = roots.stream() + .filter(root -> root.getAction().equals(Action.LOG)) + .collect(Collectors.toList()); + + roots.removeAll(logRules); + + List andRules = roots.stream() + .filter(root -> Relation.AND.equals(root.getRelation())) + .collect(Collectors.toList()); + List orRules = roots.stream() + .filter(root -> Relation.OR.equals(root.getRelation())) + .collect(Collectors.toList()); + List notRules = roots.stream() + .filter(root -> Relation.NOT.equals(root.getRelation())) + .collect(Collectors.toList()); + + RequestCredential requestCredential = new CommonRequestCredential(); + credentialResolver.forEach(resolver -> + resolver.appendRequestCredential(invocation.getInvoker().getUrl(), invocation, requestCredential)); + + AuthorizationRequestContext context = new AuthorizationRequestContext(invocation, requestCredential); + + if (!logRules.isEmpty()) { + context.startTrace(); + context.addTraceInfo(":::Start validation trace for request [" + + invocation.getInvoker().getUrl() + "], credentials=[" + invocation.getAttachments() + "] :::"); + + for (RuleRoot logRule : logRules) { + boolean result; + try { + result = logRule.evaluate(context); + context.addTraceInfo("::: Request " + (result ? "meet" : "does not meet") + " rule [" + + logRule.getNodeName() + "] ::: "); + } catch (ValidationException e) { + context.addTraceInfo( + "::: Got Exception evaluating rule [" + logRule.getNodeName() + "] , exception=" + e); + } + } + context.addTraceInfo("::: End validation trace :::"); + context.endTrace(); + logger.info(context.getTraceInfo()); + } + + for (RuleRoot rule : notRules) { + try { + if (rule.evaluate(context) && rule.getAction().boolVal()) { + throw new AuthorizationException( + "Request authorization failed: request credential meet one of NOT rules."); + } + } catch (Exception e) { + logger.error( + "", + "", + "", + "Request authorization failed, source:" + invocation.getServiceName() + // TODO get source + ", target URL:" + + invocation.getInvoker().getUrl(), + e.getCause()); + if (e instanceof AuthorizationException) { + throw (AuthorizationException) e; + } + throw new AuthorizationException(e); + } + } + + for (RuleRoot rule : andRules) { + try { + if (!rule.evaluate(context) && rule.getAction().boolVal()) { + throw new AuthorizationException( + "Request authorization failed: request credential doesn't meet all AND rules."); + } + } catch (Exception e) { + logger.error( + "", + "", + "", + "Request authorization failed, source:" + invocation.getServiceName() + // TODO get source + ", target URL:" + + invocation.getInvoker().getUrl(), + e.getCause()); + if (e instanceof AuthorizationException) { + throw (AuthorizationException) e; + } + throw new AuthorizationException(e); + } + } + + boolean orRes = false; + for (RuleRoot rule : orRules) { + try { + orRes = rule.evaluate(context) && rule.getAction().boolVal(); + if (orRes) { + break; + } + } catch (Exception e) { + logger.error( + "", + "", + "", + "Request authorization failed, source:" + invocation.getServiceName() + // TODO source + ", target URL:" + + invocation.getInvoker().getUrl(), + e.getCause()); + if (e instanceof AuthorizationException) { + throw (AuthorizationException) e; + } + throw new AuthorizationException(e); + } + } + if (CollectionUtils.isEmpty(orRules)) { + orRes = true; + } + if (orRes) { + return; + } + throw new AuthorizationException( + "Request authorization failed: request credential doesn't meet any required " + "OR rules."); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/ConnectionCredentialResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/ConnectionCredentialResolver.java new file mode 100644 index 0000000000..b2bb35406f --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/ConnectionCredentialResolver.java @@ -0,0 +1,284 @@ +/* + * 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.xds.security.authz.resolver; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.remoting.api.ChannelContextListener; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcContextAttachment; +import org.apache.dubbo.xds.security.authz.RequestCredential; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import javax.net.ssl.SSLSession; + +import java.net.InetSocketAddress; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext; +import io.grpc.netty.shaded.io.netty.handler.ssl.SslHandler; + +@Activate(order = -20) +public class ConnectionCredentialResolver implements CredentialResolver, ChannelContextListener { + + private final Map connectionInfos = new ConcurrentHashMap<>(); + + private static final ErrorTypeAwareLogger logger = + LoggerFactory.getErrorTypeAwareLogger(ConnectionCredentialResolver.class); + + @Override + public void appendRequestCredential(URL url, Invocation invocation, RequestCredential requestCredential) { + requestCredential.add( + RequestAuthProperty.TARGET_VERSION, + invocation.getInvoker().getUrl().getVersion()); + RpcContextAttachment serverContext = RpcContext.getServerContext(); + requestCredential.add( + RequestAuthProperty.DIRECT_REMOTE_IP, + serverContext.getRemoteAddress().getHostName()); + requestCredential.add(RequestAuthProperty.REMOTE_PORT, serverContext.getRemotePort()); + requestCredential.add(RequestAuthProperty.REMOTE_APPLICATION, serverContext.getRemoteApplicationName()); + requestCredential.add(RequestAuthProperty.REMOTE_GROUP, serverContext.getGroup()); + requestCredential.add(RequestAuthProperty.DESTINATION_IP, serverContext.getLocalHost()); + requestCredential.add(RequestAuthProperty.DESTINATION_PORT, serverContext.getLocalPort()); + + ConnectionCredential credential = connectionInfos.get(url.getIp() + ":" + url.getPort()); + if (credential != null) { + requestCredential.add(RequestAuthProperty.CONNECTION_CREDENTIAL, credential); + requestCredential.add(RequestAuthProperty.REQUESTED_SERVER_NAME, credential.getSni()); + } + } + + @Override + public void onConnect(Object channelContext) { + if (channelContext instanceof ChannelHandlerContext) { + ChannelHandlerContext context = (ChannelHandlerContext) channelContext; + SslHandler sslHandler = context.pipeline().get(SslHandler.class); + if (sslHandler != null) { + SSLSession sslSession = sslHandler.engine().getSession(); + String applicationProtocol = sslSession.getProtocol(); + try { + Certificate[] peerCertificates = sslSession.getPeerCertificates(); + List certCredentialList = new ArrayList<>(1); + for (Certificate certificate : peerCertificates) { + if (!(certificate instanceof X509Certificate)) { + logger.warn( + "99-1", + "", + "", + "One SSL certificate was ignored because it's not in X.509 format: " + certificate); + continue; + } + certCredentialList.add(new CertificateCredential((X509Certificate) certificate)); + } + String remoteAddress = NetUtils.toAddressString( + (InetSocketAddress) context.channel().remoteAddress()); + String sniHostName = sslSession.getPeerHost(); + connectionInfos.put( + remoteAddress, + new ConnectionCredential(certCredentialList, applicationProtocol, sniHostName)); + } catch (Exception e) { + logger.warn("99-1", "", "", "Got exception when resolving certificate from SSL session", e); + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("No SSL/TLS handler found in pipeline:" + + NetUtils.toAddressString( + (InetSocketAddress) context.channel().remoteAddress()) + "-> " + + NetUtils.toAddressString( + (InetSocketAddress) context.channel().localAddress())); + } + } + } + } + + @Override + public void onDisconnect(Object channelContext) { + if (channelContext instanceof ChannelHandlerContext) { + ChannelHandlerContext context = (ChannelHandlerContext) channelContext; + String remoteAddress = NetUtils.toAddressString( + (InetSocketAddress) context.channel().remoteAddress()); + connectionInfos.remove(remoteAddress); + } + } + + public static class ConnectionCredential { + private final List certificateCredentials; + private final String applicationProtocol; + private final String sni; + + public ConnectionCredential( + List certificateCredentials, String applicationProtocol, String sni) { + this.certificateCredentials = certificateCredentials; + this.applicationProtocol = applicationProtocol; + this.sni = sni; + } + + public List getCertificateCredentials() { + return certificateCredentials; + } + + public String getApplicationProtocol() { + return applicationProtocol; + } + + public String getSni() { + return sni; + } + } + + public static class CertificateCredential { + private final X509Certificate certificate; + private final String subject; + private final String issuer; + private final Map> subjectAltNames; + private final Date certNotBefore; + private final Date certNotAfter; + private final String signatureAlgorithmName; + private final String publicKeyAlgorithmName; + private final Set criticalExtensionOIDs; + private final List extendedKeyUsage; + + public CertificateCredential(X509Certificate cert) throws Exception { + this.subject = cert.getSubjectX500Principal().getName(); + this.issuer = cert.getIssuerX500Principal().toString(); + this.certNotBefore = cert.getNotBefore(); + this.certNotAfter = cert.getNotAfter(); + this.subjectAltNames = extractDetailedFields(cert); + this.signatureAlgorithmName = cert.getSigAlgName(); + this.publicKeyAlgorithmName = cert.getPublicKey().getAlgorithm(); + this.criticalExtensionOIDs = cert.getCriticalExtensionOIDs(); + // e.g., TLS Web Server Authentication, TLS Web Client Authentication + this.extendedKeyUsage = cert.getExtendedKeyUsage(); + this.certificate = cert; + } + + private Map> extractDetailedFields(X509Certificate cert) throws Exception { + Collection> subjectAltNames = cert.getSubjectAlternativeNames(); + if (subjectAltNames != null) { + Map> sanMap = new HashMap<>(); + for (List sanItem : subjectAltNames) { + SANType type = SANType.map((Integer) sanItem.get(0)); + Object value = sanItem.get(1); + sanMap.computeIfAbsent(type, k -> new ArrayList<>()).add(value); + } + } + + return Collections.emptyMap(); + } + + public Map> getSubjectAltNames() { + return subjectAltNames; + } + + public List getExtendedKeyUsage() { + return extendedKeyUsage; + } + + public Set getCriticalExtensionOIDs() { + return criticalExtensionOIDs; + } + + public String getPublicKeyAlgorithmName() { + return publicKeyAlgorithmName; + } + + public String getSignatureAlgorithmName() { + return signatureAlgorithmName; + } + + public Date getCertNotAfter() { + return certNotAfter; + } + + public Date getCertNotBefore() { + return certNotBefore; + } + + public String getSubject() { + return subject; + } + + public String getIssuer() { + return issuer; + } + + public X509Certificate getCertificate() { + return certificate; + } + } + + public enum SANType { + OTHER_NAME(0), + RFC_822_NAME(1), + DNS_NAME(2), + X400_ADDRESS(3), + DIRECTORY_NAME(4), + EDI_PARTY_NAME(5), + URI(6), + IP_ADDRESS(7), + REGISTERED_ID(8); + + private final int value; + + SANType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static SANType map(int value) { + switch (value) { + case 0: + return OTHER_NAME; + case 1: + return RFC_822_NAME; + case 2: + return DNS_NAME; + case 3: + return X400_ADDRESS; + case 4: + return DIRECTORY_NAME; + case 5: + return EDI_PARTY_NAME; + case 6: + return URI; + case 7: + return IP_ADDRESS; + case 8: + return REGISTERED_ID; + default: + throw new IllegalArgumentException("Unknown SAN value: " + value); + } + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/CredentialResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/CredentialResolver.java new file mode 100644 index 0000000000..245c459eba --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/CredentialResolver.java @@ -0,0 +1,32 @@ +/* + * 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.xds.security.authz.resolver; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.xds.security.authz.RequestCredential; + +/** + * Resolve connection/request credential to validation context. + */ +@SPI(scope = ExtensionScope.APPLICATION) +public interface CredentialResolver { + + void appendRequestCredential(URL url, Invocation invocation, RequestCredential requestCredential); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/HttpCredentialResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/HttpCredentialResolver.java new file mode 100644 index 0000000000..1806b4bcbb --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/HttpCredentialResolver.java @@ -0,0 +1,54 @@ +/* + * 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.xds.security.authz.resolver; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.xds.security.authz.RequestCredential; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import java.util.List; +import java.util.Map; + +@Activate +public class HttpCredentialResolver implements CredentialResolver { + + private static final String TRIPLE_NAME = "tri"; + + private static final String REST_NAME = "rest"; + + @Override + public void appendRequestCredential(URL url, Invocation invocation, RequestCredential requestCredential) { + if (!(TRIPLE_NAME.equals(url.getProtocol()) || REST_NAME.equals(url.getProtocol()))) { + return; + } + String targetPath = invocation.getServiceName() + "/" + invocation.getMethodName(); + String httpMethod = "POST"; + requestCredential.add(RequestAuthProperty.URL_PATH, targetPath); + requestCredential.add(RequestAuthProperty.HTTP_METHOD, httpMethod); + + // TODO get more detailed http message from context + Map requestHttpHeaders = null; + requestCredential.add(RequestAuthProperty.JWT_FROM_HEADERS, requestHttpHeaders); + + Map> httpRequestParams = null; + requestCredential.add(RequestAuthProperty.JWT_FROM_PARAMS, httpRequestParams); + + // TODO: REMOTE_IP from X-forward header + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/JwtCredentialResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/JwtCredentialResolver.java new file mode 100644 index 0000000000..4b718da760 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/JwtCredentialResolver.java @@ -0,0 +1,68 @@ +/* + * 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.xds.security.authz.resolver; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.xds.security.authz.RequestCredential; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; + +import static org.apache.dubbo.rpc.Constants.ID_TOKEN_KEY; + +@Activate(order = -10) +public class JwtCredentialResolver implements CredentialResolver { + + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(JwtCredentialResolver.class); + + @Override + public void appendRequestCredential(URL url, Invocation invocation, RequestCredential requestCredential) { + String token = (String) RpcContext.getServerAttachment().getObjectAttachment(ID_TOKEN_KEY); + if (StringUtils.isEmpty(token)) { + return; + } + + if (token.startsWith("Bearer ")) { + token = token.substring("Bearer ".length()); + } + + DecodedJWT jwt = JWT.decode(token); + long now = System.currentTimeMillis(); + String expAt = String.valueOf(jwt.getClaims().get("exp")); + + // convert millisecond -> second + if (Long.parseLong(expAt) * 1000 < now) { + logger.warn("99-0", "", "", "Request JWT token already expire, now:" + now + " exp:" + expAt); + } + + String issuer = jwt.getIssuer(); + String sub = jwt.getSubject(); + requestCredential.add(RequestAuthProperty.JWT_PRINCIPALS, issuer + "/" + sub); + requestCredential.add(RequestAuthProperty.JWT_AUDIENCES, jwt.getAudience()); + requestCredential.add(RequestAuthProperty.JWT_ISSUER, issuer); + requestCredential.add(RequestAuthProperty.DECODED_JWT, jwt); + // use jwks to validate this jwt + requestCredential.add(RequestAuthProperty.JWKS, jwt); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/KubernetesCredentialResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/KubernetesCredentialResolver.java new file mode 100644 index 0000000000..f22fb7124c --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/KubernetesCredentialResolver.java @@ -0,0 +1,81 @@ +/* + * 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.xds.security.authz.resolver; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.kubernetes.KubeEnv; +import org.apache.dubbo.xds.security.authz.RequestCredential; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import java.util.Map; + +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; + +@Activate +public class KubernetesCredentialResolver implements CredentialResolver { + + private final KubeEnv kubeEnv; + + public KubernetesCredentialResolver(ApplicationModel applicationModel) { + this.kubeEnv = applicationModel.getBeanFactory().getBean(KubeEnv.class); + } + + @Override + public void appendRequestCredential(URL url, Invocation invocation, RequestCredential requestCredential) { + DecodedJWT jwt = ((DecodedJWT) requestCredential.get(RequestAuthProperty.DECODED_JWT)); + if (jwt == null) { + return; + } + Claim prop = jwt.getClaims().get("kubernetes.io"); + if (prop == null) { + return; + } + Map kubeProps = prop.asMap(); + + String namespace = (String) kubeProps.get("namespace"); + String podName = null; + String podId = null; + String sourceService = null; + String uid = null; + @SuppressWarnings("unchecked") + Map serviceAccount = (Map) kubeProps.get("serviceaccount"); + + if (serviceAccount != null) { + sourceService = serviceAccount.get("name"); + uid = serviceAccount.get("uid"); + } + @SuppressWarnings("unchecked") + Map pod = (Map) kubeProps.get("pod"); + if (pod != null) { + podName = pod.get("name"); + podId = pod.get("uid"); + } + + requestCredential.add( + RequestAuthProperty.KUBE_SERVICE_PRINCIPAL, + kubeEnv.getCluster() + "/ns/" + namespace + "/sa/" + sourceService); + requestCredential.add(RequestAuthProperty.KUBE_POD_NAME, podName); + requestCredential.add(RequestAuthProperty.KUBE_POD_ID, podId); + requestCredential.add(RequestAuthProperty.KUBE_SERVICE_UID, uid); + requestCredential.add(RequestAuthProperty.KUBE_SOURCE_NAMESPACE, namespace); + requestCredential.add(RequestAuthProperty.KUBE_SERVICE_NAME, sourceService); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/SpiffeCredentialResolver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/SpiffeCredentialResolver.java new file mode 100644 index 0000000000..37f543a6f7 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/resolver/SpiffeCredentialResolver.java @@ -0,0 +1,110 @@ +/* + * 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.xds.security.authz.resolver; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.xds.security.authz.RequestCredential; +import org.apache.dubbo.xds.security.authz.resolver.ConnectionCredentialResolver.CertificateCredential; +import org.apache.dubbo.xds.security.authz.resolver.ConnectionCredentialResolver.ConnectionCredential; +import org.apache.dubbo.xds.security.authz.resolver.ConnectionCredentialResolver.SANType; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; + +@Activate +public class SpiffeCredentialResolver implements CredentialResolver { + + private static final String SPIFFE_KEY = "spiffe"; + + private static final ErrorTypeAwareLogger logger = + LoggerFactory.getErrorTypeAwareLogger(SpiffeCredentialResolver.class); + + private static final String NAMESPACE = "ns"; + + private static final String SERVICE_ACCOUNT = "sa"; + + @Override + public void appendRequestCredential(URL url, Invocation invocation, RequestCredential requestCredential) { + Object credential = requestCredential.get(RequestAuthProperty.CONNECTION_CREDENTIAL); + if (credential != null) { + if (credential instanceof ConnectionCredential) { + java.net.URI spiffe = readSpiffeId(((ConnectionCredential) credential).getCertificateCredentials()); + if (spiffe != null) { + requestCredential.add(RequestAuthProperty.TRUST_DOMAIN, spiffe.getHost()); + requestCredential.add(RequestAuthProperty.KUBE_SOURCE_CLUSTER, spiffe.getHost()); + requestCredential.add(RequestAuthProperty.WORKLOAD_ID, spiffe.getPath()); + + String hostWithPath = spiffe.getHost() + spiffe.getPath(); + String[] segments = hostWithPath.split("/"); + // cluster.local[0]/ns[1]/default[2]/sa[3]/my-service-account[4] , len=5 + if (segments.length == 5 && NAMESPACE.equals(segments[1]) && SERVICE_ACCOUNT.equals(segments[3])) { + String namespace = segments[2]; + String serviceAccount = segments[4]; + requestCredential.add(RequestAuthProperty.KUBE_SOURCE_NAMESPACE, namespace); + requestCredential.add(RequestAuthProperty.KUBE_SERVICE_PRINCIPAL, serviceAccount); + requestCredential.add(RequestAuthProperty.PRINCIPAL, hostWithPath); + } else { + logger.error("99-1", "", "", "Invalid SPIFFE ID format:" + spiffe); + } + + requestCredential.add(RequestAuthProperty.SPIFFE_ID, spiffe.toString()); + } + } else { + logger.error( + "99-1", + "", + "", + "Got value with key=CONNECTION_CREDENTIAL but not a valid RequestCredential instance:" + + credential); + } + } + } + + public java.net.URI readSpiffeId(List credentials) { + for (CertificateCredential credential : credentials) { + Map> subjectAltNames = credential.getSubjectAltNames(); + if (subjectAltNames != null) { + List list = subjectAltNames.get(SANType.URI); + if (list != null && !list.isEmpty()) { + for (Object o : list) { + if (o instanceof String) { + try { + java.net.URI uri = new java.net.URI((String) o); + if (SPIFFE_KEY.equals(uri.getScheme())) { + return uri; + } + } catch (URISyntaxException e) { + logger.warn( + "99-1", + "", + "", + "One SAN URI was ignored because it's not in valid URI format:" + o); + } + } + } + } + } + } + return null; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/AuthorizationPolicyPathConvertor.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/AuthorizationPolicyPathConvertor.java new file mode 100644 index 0000000000..3d49c72b1f --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/AuthorizationPolicyPathConvertor.java @@ -0,0 +1,46 @@ +/* + * 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.xds.security.authz.rule; + +public class AuthorizationPolicyPathConvertor { + + public static RequestAuthProperty convert(String path) { + + switch (path) { + case "rules.to.operation.paths": + return RequestAuthProperty.URL_PATH; + case "rules.to.operation.methods": + return RequestAuthProperty.HTTP_METHOD; + case "rules.from.source.namespaces": + return RequestAuthProperty.KUBE_SOURCE_NAMESPACE; + case "rules.source.service.name": + return RequestAuthProperty.KUBE_SERVICE_NAME; + case "rules.source.service.uid": + return RequestAuthProperty.KUBE_SERVICE_UID; + case "rules.source.pod.name": + return RequestAuthProperty.KUBE_POD_NAME; + case "rules.source.pod.id": + return RequestAuthProperty.KUBE_POD_ID; + case "rules.from.source.principals": + return RequestAuthProperty.KUBE_SERVICE_PRINCIPAL; + case "rules.to.operation.version": + return RequestAuthProperty.TARGET_VERSION; + default: + throw new RuntimeException("not supported path:" + path); + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/CommonRequestCredential.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/CommonRequestCredential.java new file mode 100644 index 0000000000..cfcb386108 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/CommonRequestCredential.java @@ -0,0 +1,44 @@ +/* + * 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.xds.security.authz.rule; + +import org.apache.dubbo.xds.security.authz.RequestCredential; + +import java.util.HashMap; +import java.util.Map; + +public class CommonRequestCredential implements RequestCredential { + + /** + * PropertyName -> credential properties + */ + private final Map authProperties; + + public CommonRequestCredential() { + this.authProperties = new HashMap<>(); + } + + @Override + public Object get(RequestAuthProperty propertyType) { + return authProperties.get(propertyType); + } + + @Override + public void add(RequestAuthProperty propertyType, Object value) { + this.authProperties.put(propertyType, value); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/RequestAuthProperty.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/RequestAuthProperty.java new file mode 100644 index 0000000000..ad182bda6b --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/RequestAuthProperty.java @@ -0,0 +1,280 @@ +/* + * 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.xds.security.authz.rule; + +public enum RequestAuthProperty { + + // Envoy LDS RbacFilter & JwtFilter props + + /** + * Request header + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * when: + * 1)rules:when (request.headers[xxx]) + */ + HEADER, + + /** + * Direct request ip address + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * from: + * 1)rules:from:source:ipBlocks + * 2)rules:from:source:notIpBlocks + *

+ * when: + * 1)rules:when (source.ip) + */ + DIRECT_REMOTE_IP, + + /** + * The original client IP address determined by the X-Forwarded-For request header or proxy protocol + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * from: + * 1)rules:from:source:remoteIpBlocks + * 2)rules:from:source:notRemoteIpBlocks + *

+ * when: + * 1)rules:when (remote.ip) + */ + REMOTE_IP, + + REMOTE_PORT, + + /** + * Identity in jwt = issuer + "/" + subject + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * from: + * 1)rules:from:source:requestPrincipals + *

+ * when: + * 1)rules:when (request.auth.principal) + */ + JWT_PRINCIPALS, + + /** + * Audience in jwt + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * when: + * 1)rules:when (request.auth.claims[xxx]) + */ + JWT_CLAIMS, + + /** + * Azp in jwt: Authorized party - the party to which the ID Token was issued + * rule attribution:principal + *

+ * Rule modification section: + *

+ * when: + * 1)rules:when (request.auth.presenter) + */ + JWT_PRESENTERS, + + /** + * What should the requester's identity be + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * from: + * 1)rules:from:source:principals + * 2)rules:from:source:notPrincipals + * 3)rules:from:namespaces + * Concatenate regular expressions as formal principals,for example:namespaces: ["namespace1"]-> .* + * /ns/namespace1/.* + * 4)rules:from:notNamespaces + *

+ * when: + * 1)rules:when (source.principal) + * 2)rules:when (source.namespace) + */ + PRINCIPAL, + + /** + * Server ip + * Rule attribution:permission + *

+ * Rule modification section: + *

+ * when: + * 1)rules:when (destination.ip) + */ + DESTINATION_IP, + + /** + * Server hosts + *

+ * Rule modification section: + *

+ * to: + * 1)rules:to:operation:hosts + * 2)rules:to:operation:notHosts + */ + HOSTS, + + /** + * Server url path + * Rule attribution:permission + *

+ * Rule modification section: + *

+ * to: + * 1)rules:to:operation:paths + * 2)rules:to:operation:notPaths + */ + URL_PATH, + + /** + * Server port + * Rule attribution:permission + *

+ * Rule modification section: + *

+ * to: + * 1)rules:to:operation:ports + * 2)rules:to:operation:notPorts + *

+ * when: + * 1)rules:when (destination.port) + */ + DESTINATION_PORT, + + /** + * Server methods + * Rule attribution:permission + *

+ * Rule modification section: + *

+ * to: + * 1)rules:to:operation:methods + * 2)rules:to:operation:notMethods + */ + HTTP_METHOD, + + /** + * Server sni : request.getServerName() + * Rule attribution:permission + *

+ * Rule modification section: + *

+ * when: + * 1)rules:when (connection.sni) + */ + REQUESTED_SERVER_NAME, + + // Downstream kubernetes environment props + /** + * consumer service account name + */ + KUBE_SERVICE_PRINCIPAL, + + /** + * consumer namespace + */ + KUBE_SOURCE_NAMESPACE, + + /** + * consumer service name + */ + KUBE_SERVICE_NAME, + + /** + * consumer pod name + */ + KUBE_POD_NAME, + + /** + * consumer pod id + */ + KUBE_POD_ID, + + /** + * consumer service uid + */ + KUBE_SERVICE_UID, + + /** + * consumer required provider service version + */ + TARGET_VERSION, + + /** + * consumer cluster name + */ + KUBE_SOURCE_CLUSTER, + + SOURCE_METADATA, + + // Dubbo properties + /** + * consumer dubbo application name + */ + REMOTE_APPLICATION, + + /** + * consumer service group + */ + REMOTE_GROUP, + + // JWT rules + /** + * Audience in jwt + * Rule attribution:principal + *

+ * Rule modification section: + *

+ * when: + * 1)rules:when (request.auth.audiences) + */ + JWT_AUDIENCES, + + JWT_NAME, + + JWT_ISSUER, + + JWKS, + + JWT_FROM_PARAMS, + + JWT_FROM_HEADERS, + + /** + * spiffe://{trust_domain}/{workload_identity} + */ + SPIFFE_ID, + TRUST_DOMAIN, + WORKLOAD_ID, + + // properties for internal use + DECODED_JWT, + CONNECTION_CREDENTIAL; +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/RuleMismatchException.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/RuleMismatchException.java new file mode 100644 index 0000000000..d061444fc3 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/RuleMismatchException.java @@ -0,0 +1,31 @@ +/* + * 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.xds.security.authz.rule; + +import org.apache.dubbo.xds.security.api.AuthorizationException; + +public class RuleMismatchException extends AuthorizationException { + private String ruleType; + + private String expectValue; + + private String actualValue; + + public RuleMismatchException(String ruleType, String expectValue, String actualValue) { + super("Authorization rule mismatch. Type:" + ruleType + ",expect:" + expectValue + ",actual:" + actualValue); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/CustomMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/CustomMatcher.java new file mode 100644 index 0000000000..24c4f97798 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/CustomMatcher.java @@ -0,0 +1,48 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import java.util.function.Function; + +public class CustomMatcher implements Matcher { + + private RequestAuthProperty property; + + private Function matchFunction; + + public CustomMatcher(RequestAuthProperty property, Function matchFunction) { + this.matchFunction = matchFunction; + this.property = property; + } + + @Override + public boolean match(T actual) { + return matchFunction.apply(actual); + } + + @Override + public RequestAuthProperty propType() { + return property; + } + + @Override + public String toString() { + return "CustomMatcher{" + "property=" + property + ", matchFunction=" + matchFunction + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/IpMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/IpMatcher.java new file mode 100644 index 0000000000..ba450410c0 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/IpMatcher.java @@ -0,0 +1,110 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +public class IpMatcher implements Matcher { + + /** + * Prefix length in CIDR case + */ + private final int prefixLen; + + /** + * Ip address to be matched + */ + private final String ipBinaryString; + + private final RequestAuthProperty authProperty; + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(IpMatcher.class); + + public IpMatcher(int prefixLen, String ipString, RequestAuthProperty property) { + this.prefixLen = prefixLen; + this.ipBinaryString = ip2BinaryString(ipString); + this.authProperty = property; + } + + /** + * @param ip dotted ip string, + * @return + */ + public static String ip2BinaryString(String ip) { + try { + String[] ips = ip.split("\\."); + if (4 != ips.length) { + logger.error("99-0", "", "", "Error ip=" + ip); + return ""; + } + long[] ipLong = new long[4]; + for (int i = 0; i < 4; ++i) { + ipLong[i] = Long.parseLong(ips[i]); + if (ipLong[i] < 0 || ipLong[i] > 255) { + logger.error("99-0", "", "", "Error ip=" + ip); + return ""; + } + } + return String.format( + "%32s", + Long.toBinaryString((ipLong[0] << 24) + (ipLong[1] << 16) + (ipLong[2] << 8) + ipLong[3])) + .replace(" ", "0"); + } catch (Exception e) { + logger.error("", "", "", "Error ip=" + ip); + } + return ""; + } + + public boolean match(String object) { + if (StringUtils.isEmpty(ipBinaryString)) { + return false; + } + String ipBinary = ip2BinaryString(object); + if (StringUtils.isEmpty(ipBinary)) { + return false; + } + if (prefixLen <= 0) { + return ipBinaryString.equals(ipBinary); + } + if (ipBinaryString.length() >= prefixLen && ipBinary.length() >= prefixLen) { + return ipBinaryString.substring(0, prefixLen).equals(ipBinary.substring(0, prefixLen)); + } + return false; + } + + @Override + public RequestAuthProperty propType() { + return authProperty; + } + + public int getPrefixLen() { + return prefixLen; + } + + public String getIpBinaryString() { + return ipBinaryString; + } + + @Override + public String toString() { + return "IpMatcher{" + "prefixLen=" + prefixLen + ", ipBinaryString='" + ipBinaryString + '\'' + + ", authProperty=" + authProperty + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/KeyMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/KeyMatcher.java new file mode 100644 index 0000000000..cf270a70c2 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/KeyMatcher.java @@ -0,0 +1,61 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; +import org.apache.dubbo.xds.security.authz.rule.matcher.StringMatcher.MatchType; + +import java.util.Map; + +public class KeyMatcher implements Matcher> { + + private String key; + + private StringMatcher stringMatcher; + + public KeyMatcher(MatchType matchType, String condition, RequestAuthProperty authProperty, String key) { + this.stringMatcher = new StringMatcher(matchType, condition, authProperty); + this.key = key; + } + + public KeyMatcher(String key, StringMatcher stringMatcher) { + this.key = key; + this.stringMatcher = stringMatcher; + } + + @Override + public boolean match(Map actual) { + if (actual == null) { + return this.stringMatcher.match(null); + } + String toMatch = actual.get(key); + if (toMatch == null) { + return false; + } + return this.stringMatcher.match(toMatch); + } + + @Override + public RequestAuthProperty propType() { + return this.stringMatcher.propType(); + } + + @Override + public String toString() { + return "KeyMatcher{" + "key='" + key + '\'' + ", stringMatcher=" + stringMatcher + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/MapMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/MapMatcher.java new file mode 100644 index 0000000000..ac9c4eb502 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/MapMatcher.java @@ -0,0 +1,58 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import java.util.Map; + +/** + * supports multiple keys and values + */ +public class MapMatcher implements Matcher> { + + private Map> keyToMatchers; + + private RequestAuthProperty property; + + public MapMatcher(Map> matcherMap, RequestAuthProperty property) { + this.keyToMatchers = matcherMap; + this.property = property; + } + + @Override + public boolean match(Map actualValues) { + for (String key : keyToMatchers.keySet()) { + Matcher matcher = keyToMatchers.get(key); + String actual = actualValues.get(key); + if (!matcher.match(actual)) { + return false; + } + } + return true; + } + + @Override + public RequestAuthProperty propType() { + return property; + } + + @Override + public String toString() { + return "MapMatcher{" + "keyToMatchers=" + keyToMatchers + ", property=" + property + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/Matcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/Matcher.java new file mode 100644 index 0000000000..16b596b70b --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/Matcher.java @@ -0,0 +1,28 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +/** + * @param Type of the actual value to match. + */ +public interface Matcher { + boolean match(T actual); + + RequestAuthProperty propType(); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/Matchers.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/Matchers.java new file mode 100644 index 0000000000..42d1b80d41 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/Matchers.java @@ -0,0 +1,148 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; +import org.apache.dubbo.xds.security.authz.rule.matcher.StringMatcher.MatchType; + +import java.util.HashMap; +import java.util.Map; + +import io.envoyproxy.envoy.config.core.v3.CidrRange; +import io.envoyproxy.envoy.config.route.v3.HeaderMatcher; +import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; + +public class Matchers { + + public static MapMatcher mapMatcher( + Map valueMap, RequestAuthProperty propertyType, StringMatcher.MatchType matchType) { + Map> matcherMap = new HashMap<>(valueMap.size()); + valueMap.forEach((k, v) -> matcherMap.put(k, stringMatcher(v, propertyType))); + return new MapMatcher(matcherMap, propertyType); + } + + public static IpMatcher ipMatcher(CidrRange range, RequestAuthProperty authProperty) { + return new IpMatcher(range.getPrefixLen().getValue(), range.getAddressPrefix(), authProperty); + } + + public static KeyMatcher keyMatcher(String key, StringMatcher stringMatcher) { + return new KeyMatcher(key, stringMatcher); + } + + public static StringMatcher stringMatcher(String value, RequestAuthProperty property) { + return new StringMatcher(MatchType.EXACT, value, property); + } + + public static StringMatcher stringMatcher( + io.envoyproxy.envoy.type.matcher.v3.StringMatcher stringMatcher, RequestAuthProperty authProperty) { + String exact = stringMatcher.getExact(); + String prefix = stringMatcher.getPrefix(); + String suffix = stringMatcher.getSuffix(); + String contains = stringMatcher.getContains(); + String regex = stringMatcher.getSafeRegex().getRegex(); + if (StringUtils.isNotBlank(exact)) { + return new StringMatcher(MatchType.EXACT, exact, authProperty); + } + if (StringUtils.isNotBlank(prefix)) { + return new StringMatcher(MatchType.PREFIX, prefix, authProperty); + } + if (StringUtils.isNotBlank(suffix)) { + return new StringMatcher(MatchType.SUFFIX, suffix, authProperty); + } + if (StringUtils.isNotBlank(contains)) { + return new StringMatcher(MatchType.CONTAIN, contains, authProperty); + } + if (StringUtils.isNotBlank(regex)) { + return new StringMatcher(MatchType.REGEX, regex, authProperty); + } + return null; + } + + public static StringMatcher stringMatcher(HeaderMatcher headerMatcher, RequestAuthProperty authProperty) { + return stringMatcher(headerMatch2StringMatch(headerMatcher), authProperty); + } + + public static io.envoyproxy.envoy.type.matcher.v3.StringMatcher headerMatch2StringMatch( + HeaderMatcher headerMatcher) { + if (headerMatcher == null) { + return null; + } + if (headerMatcher.getPresentMatch()) { + io.envoyproxy.envoy.type.matcher.v3.StringMatcher.Builder builder = + io.envoyproxy.envoy.type.matcher.v3.StringMatcher.newBuilder(); + return builder.setSafeRegex(RegexMatcher.newBuilder().build()) + .setIgnoreCase(true) + .build(); + } + if (!headerMatcher.hasStringMatch()) { + io.envoyproxy.envoy.type.matcher.v3.StringMatcher.Builder builder = + io.envoyproxy.envoy.type.matcher.v3.StringMatcher.newBuilder(); + String exactMatch = headerMatcher.getExactMatch(); + String containsMatch = headerMatcher.getContainsMatch(); + String prefixMatch = headerMatcher.getPrefixMatch(); + String suffixMatch = headerMatcher.getSuffixMatch(); + RegexMatcher safeRegex = headerMatcher.getSafeRegexMatch(); + if (!StringUtils.isEmpty(exactMatch)) { + builder.setExact(exactMatch); + } else if (!StringUtils.isEmpty(containsMatch)) { + builder.setContains(containsMatch); + } else if (!StringUtils.isEmpty(prefixMatch)) { + builder.setPrefix(prefixMatch); + } else if (!StringUtils.isEmpty(suffixMatch)) { + builder.setSuffix(suffixMatch); + } else if (safeRegex.isInitialized()) { + builder.setSafeRegex(safeRegex); + } + return builder.setIgnoreCase(true).build(); + } + return headerMatcher.getStringMatch(); + } + + public static StringMatcher toStringMatcher(HeaderMatcher headerMatcher, RequestAuthProperty property) { + return toStringMatcher(headerMatch2StringMatch(headerMatcher), property); + } + + public static StringMatcher toStringMatcher( + io.envoyproxy.envoy.type.matcher.v3.StringMatcher stringMatcher, RequestAuthProperty authProperty) { + if (stringMatcher == null) { + return null; + } + boolean ignoreCase = stringMatcher.getIgnoreCase(); + String exact = stringMatcher.getExact(); + String prefix = stringMatcher.getPrefix(); + String suffix = stringMatcher.getSuffix(); + String contains = stringMatcher.getContains(); + String regex = stringMatcher.getSafeRegex().getRegex(); + if (StringUtils.isNotBlank(exact)) { + return new StringMatcher(MatchType.EXACT, prefix, authProperty); + } + if (StringUtils.isNotBlank(prefix)) { + return new StringMatcher(MatchType.PREFIX, prefix, authProperty); + } + if (StringUtils.isNotBlank(suffix)) { + return new StringMatcher(MatchType.SUFFIX, prefix, authProperty); + } + if (StringUtils.isNotBlank(contains)) { + return new StringMatcher(MatchType.CONTAIN, prefix, authProperty); + } + if (StringUtils.isNotBlank(regex)) { + return new StringMatcher(MatchType.REGEX, prefix, authProperty); + } + return null; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/StringMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/StringMatcher.java new file mode 100644 index 0000000000..58a77d1297 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/StringMatcher.java @@ -0,0 +1,133 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +import java.util.Objects; +import java.util.regex.Pattern; + +public class StringMatcher implements Matcher { + + private String condition; + + private MatchType matchType; + + private RequestAuthProperty authProperty; + + private ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(StringMatcher.class); + + private boolean not = false; + + public StringMatcher(MatchType matchType, String condition, RequestAuthProperty authProperty) { + this.matchType = matchType; + this.condition = condition; + this.authProperty = authProperty; + } + + public StringMatcher(MatchType matchType, String condition, RequestAuthProperty authProperty, boolean not) { + this.matchType = matchType; + this.condition = condition; + this.authProperty = authProperty; + this.not = not; + } + + public boolean match(String actual) { + boolean res; + if (StringUtils.isEmpty(actual)) { + return Objects.equals(condition, actual); + } else { + switch (matchType) { + case EXACT: + res = actual.equals(condition); + break; + case PREFIX: + res = actual.startsWith(condition); + break; + case SUFFIX: + res = actual.endsWith(condition); + break; + case CONTAIN: + res = actual.contains(condition); + break; + case REGEX: + try { + res = Pattern.matches(condition, actual); + break; + } catch (Exception e) { + logger.warn("", "", "", "Irregular matching,key={},str={}", e); + return false; + } + default: + throw new UnsupportedOperationException("unsupported string compare operation"); + } + } + return not ^ res; + } + + @Override + public RequestAuthProperty propType() { + return authProperty; + } + + @Override + public String toString() { + return "StringMatcher{" + "condition='" + condition + '\'' + ", matchType=" + matchType + ", authProperty=" + + authProperty + ", not=" + not + '}'; + } + + public enum MatchType { + + /** + * exact match. + */ + EXACT("exact"), + /** + * prefix match. + */ + PREFIX("prefix"), + /** + * suffix match. + */ + SUFFIX("suffix"), + /** + * regex match. + */ + REGEX("regex"), + /** + * contain match. + */ + CONTAIN("contain"); + + /** + * type of matcher. + */ + public final String key; + + MatchType(String type) { + this.key = type; + } + + @Override + public String toString() { + return this.key; + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/WildcardStringMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/WildcardStringMatcher.java new file mode 100644 index 0000000000..dff375282c --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/matcher/WildcardStringMatcher.java @@ -0,0 +1,81 @@ +/* + * 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.xds.security.authz.rule.matcher; + +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; + +/** + * Supports simple '*' match + */ +public class WildcardStringMatcher implements Matcher { + + private String value; + + private RequestAuthProperty authProperty; + + public WildcardStringMatcher(String value, RequestAuthProperty authProperty) { + this.value = parseToPattern(value); + this.authProperty = authProperty; + } + + @Override + public boolean match(String actual) { + String pattern = parseToPattern(value); + return actual.matches(pattern); + } + + private String parseToPattern(String val) { + StringBuilder patternBuilder = new StringBuilder(); + for (int i = 0; i < val.length(); i++) { + char c = val.charAt(i); + switch (c) { + case '*': + patternBuilder.append(".*"); + break; + case '\\': + case '.': + case '^': + case '$': + case '+': + case '?': + case '{': + case '}': + case '[': + case ']': + case '|': + case '(': + case ')': + patternBuilder.append("\\").append(c); + break; + default: + patternBuilder.append(c); + break; + } + } + return patternBuilder.toString(); + } + + @Override + public RequestAuthProperty propType() { + return authProperty; + } + + @Override + public String toString() { + return "WildcardStringMatcher{" + "value='" + value + '\'' + ", authProperty=" + authProperty + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/JwtValidationUtil.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/JwtValidationUtil.java new file mode 100644 index 0000000000..e2043a9560 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/JwtValidationUtil.java @@ -0,0 +1,59 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; + +import org.jose4j.jwk.JsonWebKeySet; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.jose4j.jwt.consumer.JwtContext; +import org.jose4j.keys.resolvers.JwksVerificationKeyResolver; +import org.jose4j.lang.JoseException; + +public class JwtValidationUtil { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(JwtValidationUtil.class); + + public static JwtClaims extractJwtClaims(String jwks, String token) { + if (StringUtils.isBlank(jwks) || StringUtils.isBlank(token)) { + return null; + } + try { + // don't validate jwt's attribute, just validate the sign + JwtConsumerBuilder jwtConsumerBuilder = new JwtConsumerBuilder().setSkipAllValidators(); + JsonWebSignature jws = new JsonWebSignature(); + jws.setCompactSerialization(token); + JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwks); + JwksVerificationKeyResolver jwksResolver = new JwksVerificationKeyResolver(jsonWebKeySet.getJsonWebKeys()); + jwtConsumerBuilder.setVerificationKeyResolver(jwksResolver); + JwtConsumer jwtConsumer = jwtConsumerBuilder.build(); + JwtContext jwtContext = jwtConsumer.process(token); + return jwtContext.getJwtClaims(); + } catch (JoseException e) { + logger.warn("", "", "", "Invalid jwks = " + jwks); + } catch (InvalidJwtException e) { + logger.warn("", "", "", "Invalid jwt token" + token + "for jwks " + jwks); + } + return null; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/KubeRuleProvider.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/KubeRuleProvider.java new file mode 100644 index 0000000000..92f9047cf5 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/KubeRuleProvider.java @@ -0,0 +1,136 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.Experimental; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.kubernetes.KubeApiClient; +import org.apache.dubbo.xds.kubernetes.KubeEnv; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.Watch; + +@Activate +@Experimental("Unstable kubernetes rule source") +public class KubeRuleProvider implements RuleProvider> { + + protected final KubeApiClient kubeApiClient; + + private volatile List> ruleSourceInst; + + protected KubeEnv kubeEnv; + + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(KubeRuleProvider.class); + + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool( + 1, task -> new Thread(task, "KubeRuleSourceProvider-Scheduled-AutoRefresh")); + + public KubeRuleProvider(ApplicationModel applicationModel) throws Exception { + this.kubeApiClient = applicationModel.getBeanFactory().getBean(KubeApiClient.class); + this.kubeEnv = applicationModel.getBeanFactory().getBean(KubeEnv.class); + Map resource = getResource(); + updateSource(resource); + startListenRequestAuthentication(); + } + + @Override + public List> getSource(URL url, Invocation invocation) { + return new ArrayList<>(ruleSourceInst); + } + + private void startListenRequestAuthentication() throws ApiException { + + Watch watch = getResourceListen(); + + executor.scheduleAtFixedRate( + () -> { + try { + Map resource = getResource(); + updateSource(resource); + // TODO FIX ME + // if (watch.hasNext()) { + // Response resp = watch.next(); + // if ("ADDED".equals(resp.type) || "MODIFIED".equals(resp.type)) { + // updateSource((Map) resp.object); + // } else if ("DELETED".equals(resp.type)) { + // ruleSourceInst = Collections.emptyList(); + // } + // System.out.println("resource updated"+ resp.object); + // } + } catch (Exception e) { + logger.error( + "", "", "", "Got exception when watch and updating RequestAuthorization resource", e); + } + }, + 2000, + 30000, + TimeUnit.MILLISECONDS); + } + + protected Map getResource() { + return kubeApiClient.getResourceAsMap( + "security.istio.io", "v1", kubeEnv.getNamespace(), "authorizationpolicies"); + } + + protected Watch getResourceListen() { + return kubeApiClient.listenResource("security.istio.io", "v1", kubeEnv.getNamespace(), "authorizationpolicies"); + } + + protected void updateSource(Map resultMap) { + List> items = (List>) resultMap.get("items"); + List> rules = new ArrayList<>(); + for (Map item : items) { + Map spec = (Map) item.get("spec"); + boolean match = false; + if (spec != null) { + Map selector = (Map) spec.get("selector"); + if (selector != null) { + Map matchLabels = (Map) selector.get("matchLabels"); + + String targetLabelKey = "app"; + String targetLabelValue = kubeEnv.getServiceName(); + + if (matchLabels != null + && (StringUtils.isEmpty(targetLabelValue) + || targetLabelValue.equals(matchLabels.get(targetLabelKey)))) { + match = true; + } + } else { + // no selector set + match = true; + } + if (match) { + rules.add(spec); + } + } + } + this.ruleSourceInst = rules; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/LdsRuleFactory.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/LdsRuleFactory.java new file mode 100644 index 0000000000..e15b74190a --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/LdsRuleFactory.java @@ -0,0 +1,507 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.security.api.DataSources; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; +import org.apache.dubbo.xds.security.authz.rule.matcher.CustomMatcher; +import org.apache.dubbo.xds.security.authz.rule.matcher.IpMatcher; +import org.apache.dubbo.xds.security.authz.rule.matcher.KeyMatcher; +import org.apache.dubbo.xds.security.authz.rule.matcher.Matcher; +import org.apache.dubbo.xds.security.authz.rule.matcher.Matchers; +import org.apache.dubbo.xds.security.authz.rule.matcher.StringMatcher; +import org.apache.dubbo.xds.security.authz.rule.tree.CompositeRuleNode; +import org.apache.dubbo.xds.security.authz.rule.tree.LeafRuleNode; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleNode; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleNode.Relation; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot.Action; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import com.google.protobuf.InvalidProtocolBufferException; +import io.envoyproxy.envoy.config.rbac.v3.Permission; +import io.envoyproxy.envoy.config.rbac.v3.Policy; +import io.envoyproxy.envoy.config.rbac.v3.Principal; +import io.envoyproxy.envoy.config.rbac.v3.Principal.IdentifierCase; +import io.envoyproxy.envoy.config.rbac.v3.RBAC; +import io.envoyproxy.envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication; +import io.envoyproxy.envoy.extensions.filters.http.jwt_authn.v3.JwtProvider; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter; +import io.envoyproxy.envoy.type.matcher.v3.MetadataMatcher.PathSegment; + +import static org.apache.dubbo.xds.listener.ListenerConstants.LDS_JWT_FILTER; +import static org.apache.dubbo.xds.listener.ListenerConstants.LDS_RBAC_FILTER; +import static org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty.DIRECT_REMOTE_IP; +import static org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty.HEADER; +import static org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty.PRINCIPAL; +import static org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty.REMOTE_IP; +import static org.apache.dubbo.xds.security.authz.rule.tree.RuleNode.Relation.AND; +import static org.apache.dubbo.xds.security.authz.rule.tree.RuleNode.Relation.OR; + +public class LdsRuleFactory implements RuleFactory { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(LdsRuleFactory.class); + + public static final String LDS_REQUEST_AUTH_PRINCIPAL = "request.auth.principal"; + + public static final String LDS_REQUEST_AUTH_AUDIENCE = "request.auth.audiences"; + + public static final String LDS_REQUEST_AUTH_PRESENTER = "request.auth.presenter"; + + public static final String LDS_REQUEST_AUTH_CLAIMS = "request.auth.claims"; + + public LdsRuleFactory(ApplicationModel applicationModel) {} + + @Override + public List getRules(URL url, List ruleSource) { + // JWT rules + ArrayList roots = new ArrayList<>(resolveJWT(ruleSource).values()); + // Rbac rules + roots.addAll(resolveRbac(ruleSource)); + return roots; + } + + /** + * Rbac rule focus on validating generic properties, like: + * 1. Principals: spiffeId, etc. + * 2. Dubbo's properties: version, group, application, etc. + * 3. General connection properties: source port,source ip,destination port, destination ip, etc. + * 4. Protocol related metadata: http header, form data, etc. + */ + public List resolveRbac(List httpFilters) { + List roots = new ArrayList<>(); + Map> actions = new HashMap<>(); + for (HttpFilter httpFilter : httpFilters) { + if (!httpFilter.getName().equals(LDS_RBAC_FILTER)) { + continue; + } + try { + io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC rbac = httpFilter + .getTypedConfig() + .unpack(io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.class); + if (rbac != null) { + // TODO Is it possible there are multiple duplicates that have same action? + actions.computeIfAbsent(rbac.getRules().getAction(), (k) -> new ArrayList<>()) + .add(rbac.getRules()); + } + } catch (InvalidProtocolBufferException e) { + logger.warn("", "", "", "Parsing RbacRule error", e); + } + } + + for (Entry> rbacEntry : actions.entrySet()) { + for (RBAC rbac : rbacEntry.getValue()) { + RBAC.Action action = rbacEntry.getKey(); + RuleRoot ruleNode = + new RuleRoot(AND, action.equals(RBAC.Action.ALLOW) ? Action.ALLOW : Action.DENY, "rules"); + + // policies: "service-admin"、"product-viewer" + for (Entry entry : rbac.getPoliciesMap().entrySet()) { + + CompositeRuleNode policyNode = new CompositeRuleNode(entry.getKey(), AND); + CompositeRuleNode principalNode = new CompositeRuleNode("principals", Relation.OR); + + List principals = entry.getValue().getPrincipalsList(); + + for (Principal principal : principals) { + RuleNode principalAnd = resolvePrincipal(principal); + if (principalAnd != null) { + principalNode.addChild(principalAnd); + } + } + + if (!principals.isEmpty()) { + policyNode.addChild(principalNode); + } + + CompositeRuleNode permissionNode = new CompositeRuleNode("permissions", Relation.OR); + List permissions = entry.getValue().getPermissionsList(); + for (Permission permission : permissions) { + RuleNode permissionRule = resolvePermission(permission); + if (permissionRule != null) { + permissionNode.addChild(permissionRule); + } + } + + if (!permissions.isEmpty()) { + policyNode.addChild(permissionNode); + } + + ruleNode.addChild(policyNode); + roots.add(ruleNode); + } + } + } + return roots; + } + + private RuleNode resolvePrincipal(Principal principal) { + + switch (principal.getIdentifierCase()) { + case AND_IDS: + CompositeRuleNode andNode = new CompositeRuleNode("and_ids", Relation.AND); + for (Principal subPrincipal : principal.getAndIds().getIdsList()) { + andNode.addChild(resolvePrincipal(subPrincipal)); + } + return andNode; + + case OR_IDS: + CompositeRuleNode orNode = new CompositeRuleNode("or_ids", Relation.OR); + for (Principal subPrincipal : principal.getOrIds().getIdsList()) { + orNode.addChild(resolvePrincipal(subPrincipal)); + } + return orNode; + + case NOT_ID: + CompositeRuleNode notNode = new CompositeRuleNode("not_id", Relation.NOT); + notNode.addChild(resolvePrincipal(principal.getNotId())); + return notNode; + + default: + return handleLeafPrincipal(principal); + } + } + + private LeafRuleNode handleLeafPrincipal(Principal orIdentity) { + IdentifierCase principalCase = orIdentity.getIdentifierCase(); + + LeafRuleNode valueNode = null; + + switch (principalCase) { + case AUTHENTICATED: + StringMatcher matcher = + Matchers.stringMatcher(orIdentity.getAuthenticated().getPrincipalName(), PRINCIPAL); + if (matcher != null) { + valueNode = new LeafRuleNode(Collections.singletonList(matcher), PRINCIPAL.name()); + } + break; + + case HEADER: + String headerName = orIdentity.getHeader().getName(); + KeyMatcher keyMatcher = + Matchers.keyMatcher(headerName, Matchers.stringMatcher(orIdentity.getHeader(), HEADER)); + valueNode = new LeafRuleNode(Collections.singletonList(keyMatcher), HEADER.name()); + break; + + case REMOTE_IP: + IpMatcher ipMatcher = Matchers.ipMatcher(orIdentity.getRemoteIp(), REMOTE_IP); + valueNode = new LeafRuleNode(Collections.singletonList(ipMatcher), REMOTE_IP.name()); + break; + + case DIRECT_REMOTE_IP: + IpMatcher directIpMatcher = Matchers.ipMatcher(orIdentity.getDirectRemoteIp(), DIRECT_REMOTE_IP); + valueNode = new LeafRuleNode(Collections.singletonList(directIpMatcher), DIRECT_REMOTE_IP.name()); + break; + + case METADATA: + List segments = orIdentity.getMetadata().getPathList(); + String key = segments.get(0).getKey(); + + switch (key) { + case LDS_REQUEST_AUTH_PRINCIPAL: + StringMatcher jwtPrincipalMatcher = Matchers.stringMatcher( + orIdentity.getMetadata().getValue().getStringMatch(), + RequestAuthProperty.JWT_PRINCIPALS); + if (jwtPrincipalMatcher != null) { + valueNode = new LeafRuleNode( + Collections.singletonList(jwtPrincipalMatcher), LDS_REQUEST_AUTH_PRINCIPAL); + } + break; + case LDS_REQUEST_AUTH_AUDIENCE: + StringMatcher jwtAudienceMatcher = Matchers.stringMatcher( + orIdentity.getMetadata().getValue().getStringMatch(), + RequestAuthProperty.JWT_AUDIENCES); + if (jwtAudienceMatcher != null) { + valueNode = new LeafRuleNode( + Collections.singletonList(jwtAudienceMatcher), LDS_REQUEST_AUTH_AUDIENCE); + } + break; + case LDS_REQUEST_AUTH_PRESENTER: + StringMatcher jwtPresenterMatcher = Matchers.stringMatcher( + orIdentity.getMetadata().getValue().getStringMatch(), + RequestAuthProperty.JWT_PRESENTERS); + if (jwtPresenterMatcher != null) { + valueNode = new LeafRuleNode( + Collections.singletonList(jwtPresenterMatcher), LDS_REQUEST_AUTH_PRESENTER); + } + break; + case LDS_REQUEST_AUTH_CLAIMS: + if (segments.size() >= 2) { + String claimKey = segments.get(1).getKey(); + KeyMatcher jwtClaimsMatcher = Matchers.keyMatcher( + claimKey, + Matchers.stringMatcher( + orIdentity + .getMetadata() + .getValue() + .getListMatch() + .getOneOf() + .getStringMatch(), + RequestAuthProperty.JWT_CLAIMS)); + valueNode = new LeafRuleNode( + Collections.singletonList(jwtClaimsMatcher), LDS_REQUEST_AUTH_CLAIMS); + } + break; + default: + logger.warn("99-0", "", "", "Unsupported metadata type=" + key); + break; + } + break; + + default: + logger.warn("99-0", "", "", "Unsupported principalCase =" + principalCase); + break; + } + return valueNode; + } + + private RuleNode resolvePermission(Permission permission) { + + switch (permission.getRuleCase()) { + case AND_RULES: + CompositeRuleNode andNode = new CompositeRuleNode("and_rules", Relation.AND); + for (Permission subPermission : permission.getAndRules().getRulesList()) { + andNode.addChild(resolvePermission(subPermission)); + } + return andNode; + + case OR_RULES: + CompositeRuleNode orNode = new CompositeRuleNode("or_rules", Relation.OR); + for (Permission subPermission : permission.getOrRules().getRulesList()) { + orNode.addChild(resolvePermission(subPermission)); + } + return orNode; + + case NOT_RULE: + CompositeRuleNode notNode = new CompositeRuleNode("not_rules", Relation.NOT); + notNode.addChild(resolvePermission(permission.getNotRule())); + return notNode; + + default: + return handleLeafPermission(permission); + } + } + + private RuleNode handleLeafPermission(Permission permission) { + Permission.RuleCase ruleCase = permission.getRuleCase(); + + LeafRuleNode leafRuleNode = null; + + switch (ruleCase) { + case DESTINATION_PORT: { + int port = permission.getDestinationPort(); + if (port != 0) { + StringMatcher matcher = Matchers.stringMatcher( + String.valueOf(permission.getDestinationPort()), RequestAuthProperty.DESTINATION_PORT); + leafRuleNode = new LeafRuleNode( + Collections.singletonList(matcher), RequestAuthProperty.DESTINATION_PORT.name()); + } + break; + } + case REQUESTED_SERVER_NAME: { + StringMatcher matcher = Matchers.stringMatcher( + permission.getRequestedServerName(), RequestAuthProperty.REQUESTED_SERVER_NAME); + leafRuleNode = new LeafRuleNode( + Collections.singletonList(matcher), RequestAuthProperty.DESTINATION_PORT.name()); + break; + } + case DESTINATION_IP: { + IpMatcher matcher = + Matchers.ipMatcher(permission.getDestinationIp(), RequestAuthProperty.DESTINATION_IP); + leafRuleNode = + new LeafRuleNode(Collections.singletonList(matcher), RequestAuthProperty.DESTINATION_IP.name()); + break; + } + case URL_PATH: { + StringMatcher matcher = + Matchers.stringMatcher(permission.getUrlPath().getPath(), RequestAuthProperty.URL_PATH); + leafRuleNode = + new LeafRuleNode(Collections.singletonList(matcher), RequestAuthProperty.URL_PATH.name()); + break; + } + case HEADER: { + String headerName = permission.getHeader().getName(); + + KeyMatcher matcher = Matchers.keyMatcher( + headerName, Matchers.stringMatcher(permission.getHeader(), RequestAuthProperty.HEADER)); + leafRuleNode = new LeafRuleNode( + Collections.singletonList(matcher), matcher.propType().name()); + break; + } + default: + logger.warn("", "", "", "Unsupported ruleCase=" + ruleCase); + break; + } + return leafRuleNode; + } + + /** + * This rules basically focus on validating jwt properties. + */ + public Map resolveJWT(List httpFilters) { + Map jwtRules = new HashMap<>(); + + JwtAuthentication jwtAuthentication = null; + + for (HttpFilter httpFilter : httpFilters) { + if (!httpFilter.getName().equals(LDS_JWT_FILTER)) { + continue; + } + try { + jwtAuthentication = httpFilter.getTypedConfig().unpack(JwtAuthentication.class); + if (null != jwtAuthentication) { + break; + } + } catch (InvalidProtocolBufferException e) { + logger.warn("", "", "", "Parsing JwtRule error", e); + } + } + if (null == jwtAuthentication) { + return jwtRules; + } + + RuleRoot ruleRoot = new RuleRoot(OR, Action.ALLOW, "providers"); + + Map jwtProviders = jwtAuthentication.getProvidersMap(); + for (Entry entry : jwtProviders.entrySet()) { + + CompositeRuleNode compositeRuleNode = new CompositeRuleNode(entry.getKey(), AND); + JwtProvider provider = entry.getValue(); + + String issuer = provider.getIssuer(); + compositeRuleNode.addChild(new LeafRuleNode( + Matchers.stringMatcher(issuer, RequestAuthProperty.JWT_ISSUER), + RequestAuthProperty.JWT_ISSUER.name())); + HashSet audiencesList = new HashSet<>(provider.getAudiencesList()); + + if (!audiencesList.isEmpty()) { + Matcher> matcher = + new CustomMatcher<>(RequestAuthProperty.JWT_AUDIENCES, actualAudiences -> { + ArrayList copy = new ArrayList<>(audiencesList); + copy.removeAll(actualAudiences); + // At least one request audiences can match given audiences + return copy.size() != audiencesList.size(); + }); + compositeRuleNode.addChild(new LeafRuleNode(matcher, RequestAuthProperty.JWT_AUDIENCES.name())); + } + + String localJwks = DataSources.readActualValue(provider.getLocalJwks()); + Matcher jwkMatcher = buildJwksMatcher(localJwks); + compositeRuleNode.addChild(new LeafRuleNode(jwkMatcher, RequestAuthProperty.JWKS.name())); + + ruleRoot.addChild(compositeRuleNode); + } + + return jwtRules; + } + + public Matcher buildJwksMatcher(String localJwks) { + JSONObject jwks = JSON.parseObject(localJwks); + JSONArray keys = jwks.getJSONArray("keys"); + + return new CustomMatcher<>(RequestAuthProperty.JWKS, requestJwt -> { + Date expiresAt = requestJwt.getExpiresAt(); + if (expiresAt == null || expiresAt.getTime() <= System.currentTimeMillis()) { + logger.warn( + "", + "", + "", + "Failed to verify JWT: JWT.expiresAt=[" + expiresAt + "] and current time is " + + System.currentTimeMillis()); + return false; + } + + String kid = requestJwt.getKeyId(); + String alg = requestJwt.getAlgorithm(); + RSAPublicKey publicKey = null; + + for (int i = 0; i < keys.size(); i++) { + JSONObject keyNode = keys.getJSONObject(i); + if (keyNode.getString("kid").equals(kid)) { + try { + publicKey = buildPublicKey(keyNode.getString("n"), keyNode.getString("e")); + } catch (Exception e) { + logger.warn("", "", "", "Failed to verify JWT by JWKS: build JWT public key failed."); + return false; + } + break; + } + } + + if (publicKey == null) { + throw new IllegalStateException("Public key not found in JWKS"); + } + Algorithm algorithm = determineAlgorithm(alg, publicKey); + JWTVerifier verifier = JWT.require(algorithm).build(); + + // Verify the token + verifier.verify(requestJwt); + return true; + }); + } + + private static RSAPublicKey buildPublicKey(String modulusBase64, String exponentBase64) + throws NoSuchAlgorithmException, InvalidKeySpecException { + byte[] modulusBytes = Base64.getUrlDecoder().decode(modulusBase64); + byte[] exponentBytes = Base64.getUrlDecoder().decode(exponentBase64); + BigInteger modulus = new BigInteger(1, modulusBytes); + BigInteger exponent = new BigInteger(1, exponentBytes); + + RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) factory.generatePublic(spec); + } + + private static Algorithm determineAlgorithm(String alg, RSAPublicKey publicKey) throws IllegalArgumentException { + switch (alg) { + case "RS256": + return Algorithm.RSA256(publicKey, null); + case "RS384": + return Algorithm.RSA384(publicKey, null); + case "RS512": + return Algorithm.RSA512(publicKey, null); + default: + throw new IllegalArgumentException("Unsupported algorithm: " + alg); + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/LdsRuleProvider.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/LdsRuleProvider.java new file mode 100644 index 0000000000..03e0104359 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/LdsRuleProvider.java @@ -0,0 +1,101 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.listener.LdsListener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import io.envoyproxy.envoy.config.listener.v3.Filter; +import io.envoyproxy.envoy.config.listener.v3.FilterChain; +import io.envoyproxy.envoy.config.listener.v3.Listener; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter; + +import static org.apache.dubbo.xds.listener.ListenerConstants.LDS_CONNECTION_MANAGER; +import static org.apache.dubbo.xds.listener.ListenerConstants.LDS_VIRTUAL_INBOUND; + +@Activate +public class LdsRuleProvider implements LdsListener, RuleProvider { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(LdsRuleProvider.class); + + public LdsRuleProvider(ApplicationModel applicationModel) {} + + private volatile List rbacFilters = Collections.emptyList(); + + @Override + public void onResourceUpdate(List listeners) { + if (CollectionUtils.isEmpty(listeners)) { + return; + } + this.rbacFilters = resolveHttpFilter(listeners); + } + + public static List resolveHttpFilter(List listeners) { + List httpFilters = new ArrayList<>(); + for (Listener listener : listeners) { + if (!listener.getName().equals(LDS_VIRTUAL_INBOUND)) { + continue; + } + for (FilterChain filterChain : listener.getFilterChainsList()) { + for (Filter filter : filterChain.getFiltersList()) { + if (!filter.getName().equals(LDS_CONNECTION_MANAGER)) { + continue; + } + HttpConnectionManager httpConnectionManager = unpackHttpConnectionManager(filter.getTypedConfig()); + if (httpConnectionManager == null) { + continue; + } + for (HttpFilter httpFilter : httpConnectionManager.getHttpFiltersList()) { + if (httpFilter != null) { + httpFilters.add(httpFilter); + } + } + } + } + } + return httpFilters; + } + + public static HttpConnectionManager unpackHttpConnectionManager(Any any) { + try { + if (!any.is(HttpConnectionManager.class)) { + return null; + } + return any.unpack(HttpConnectionManager.class); + } catch (InvalidProtocolBufferException e) { + return null; + } + } + + @Override + public List getSource(URL url, Invocation invocation) { + return rbacFilters; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/MapRuleFactory.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/MapRuleFactory.java new file mode 100644 index 0000000000..9003af56b8 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/MapRuleFactory.java @@ -0,0 +1,69 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleNode.Relation; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot.Action; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleTreeBuilder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Default rule factory that supports common AuthorizationPolicy properties + */ +public class MapRuleFactory implements RuleFactory> { + + @Override + public List getRules(URL url, List> ruleSources) { + + List roots = new ArrayList<>(); + + for (Map sourceMap : ruleSources) { + + Action action = Action.map((String) sourceMap.get("action")); + if (action == null) { + throw new RuntimeException("Parse rule map failed: unknown action"); + } + + RuleTreeBuilder builder = new RuleTreeBuilder(); + RuleRoot ruleRoot = new RuleRoot(Relation.AND, action); + builder.addRoot(ruleRoot); + + ArrayList levelRelations = new ArrayList<>(); + // from|to|... + levelRelations.add(Relation.AND); + // from.source[0]|from.source[1]|... + levelRelations.add(Relation.OR); + // from.source.principle|from.source.namespaces|... + levelRelations.add(Relation.AND); + + Map ruleMap = new HashMap<>(1); + ruleMap.put("rules", sourceMap.get("rules")); + + builder.setPathLevelRelations(levelRelations); + builder.createFromRuleMap(ruleMap, ruleRoot); + + roots.addAll(builder.getRoots()); + } + return roots; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/RuleFactory.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/RuleFactory.java new file mode 100644 index 0000000000..90c7747479 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/RuleFactory.java @@ -0,0 +1,34 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot; + +import java.util.List; + +/** + * + */ +@SPI +public interface RuleFactory { + + @Adaptive({"authz_rule", "mesh"}) + List getRules(URL url, List ruleSource); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/RuleProvider.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/RuleProvider.java new file mode 100644 index 0000000000..73f2277e8f --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/source/RuleProvider.java @@ -0,0 +1,35 @@ +/* + * 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.xds.security.authz.rule.source; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.Invocation; + +import java.util.List; + +/** + * Provides rules for role-based authorization + */ +@SPI(value = "default", scope = ExtensionScope.APPLICATION) +public interface RuleProvider { + + @Adaptive(value = {"authz_rule", "mesh"}) + List getSource(URL url, Invocation invocation); +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/CompositeRuleNode.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/CompositeRuleNode.java new file mode 100644 index 0000000000..3f0780c78e --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/CompositeRuleNode.java @@ -0,0 +1,100 @@ +/* + * 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.xds.security.authz.rule.tree; + +import org.apache.dubbo.xds.security.authz.AuthorizationRequestContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CompositeRuleNode implements RuleNode { + + protected String name; + + protected Map> children; + + protected Relation relation; + + public CompositeRuleNode(String name, Map> children, Relation relation) { + this.name = name; + this.children = children; + this.relation = relation; + } + + public CompositeRuleNode(String name, Relation relation) { + this.name = name; + this.relation = relation; + this.children = new HashMap<>(); + } + + public void setRelation(Relation relation) { + this.relation = relation; + } + + public void addChild(RuleNode ruleNode) { + this.children + .computeIfAbsent(ruleNode.getNodeName(), (k) -> new ArrayList<>()) + .add(ruleNode); + } + + public Relation getRelation() { + return relation; + } + + @Override + public boolean evaluate(AuthorizationRequestContext context) { + boolean result; + context.depthIncrease(); + if (context.enableTrace()) { + context.addTraceInfo(""); + } + + if (relation == Relation.AND) { + result = children.values().stream() + .allMatch(childList -> childList.stream().allMatch(ch -> ch.evaluate(context))); + } else if (relation == Relation.OR) { + result = children.values().stream() + .anyMatch(childList -> childList.stream().anyMatch(ch -> ch.evaluate(context))); + } else { + // relation == NOT + result = children.values().stream() + .noneMatch(childList -> childList.stream().anyMatch(ch -> ch.evaluate(context))); + } + if (context.enableTrace()) { + String msg = " " + (result ? "match " : "not match "); + context.addTraceInfo(msg); + } + context.depthDecrease(); + return result; + } + + public Map> getChildren() { + return children; + } + + public String getNodeName() { + return name; + } + + @Override + public String toString() { + return "CompositeRuleNode{" + "name='" + name + '\'' + ", children=" + children + ", relation=" + relation + + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/LeafRuleNode.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/LeafRuleNode.java new file mode 100644 index 0000000000..41dad969d2 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/LeafRuleNode.java @@ -0,0 +1,87 @@ +/* + * 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.xds.security.authz.rule.tree; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.xds.security.authz.AuthorizationRequestContext; +import org.apache.dubbo.xds.security.authz.rule.matcher.Matcher; + +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("unchecked,rawtypes") +public class LeafRuleNode implements RuleNode { + + /** + * e.g principle in rules.from.source.principles + */ + private String rulePropName; + + /** + * patterns that matches required values + */ + private List matchers; + + private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(LeafRuleNode.class); + + public LeafRuleNode(List expectedConditions, String name) { + this.matchers = (List) expectedConditions; + this.rulePropName = name; + } + + public LeafRuleNode(Matcher matcher, String name) { + this.matchers = Collections.singletonList(matcher); + this.rulePropName = name; + } + + @Override + public boolean evaluate(AuthorizationRequestContext context) { + context.depthIncrease(); + // If we have multiple values to validate, then every value must match at list one rule pattern + for (Matcher matcher : matchers) { + + Object toValidate = context.getRequestCredential().get(matcher.propType()); + boolean match = matcher.match(toValidate); + + if (context.enableTrace()) { + String msg = "" + (match ? "match" : "not match") + + " for request property " + toValidate + ", " + matcher; + context.addTraceInfo(msg); + } + + if (!match) { + LOGGER.debug("principal=" + toValidate + " does not match rule " + matcher); + context.depthDecrease(); + return false; + } + LOGGER.debug("principal=" + toValidate + " successful match rule " + matcher); + } + context.depthDecrease(); + return true; + } + + @Override + public String getNodeName() { + return rulePropName; + } + + @Override + public String toString() { + return "LeafRuleNode{" + "rulePropName='" + rulePropName + '\'' + ", matchers=" + matchers + '}'; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleNode.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleNode.java new file mode 100644 index 0000000000..78aaa58a2e --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleNode.java @@ -0,0 +1,35 @@ +/* + * 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.xds.security.authz.rule.tree; + +import org.apache.dubbo.xds.security.authz.AuthorizationRequestContext; + +public interface RuleNode { + + /** + * evaluate if the request can match rules in this node and its children + */ + boolean evaluate(AuthorizationRequestContext context); + + String getNodeName(); + + enum Relation { + AND, + OR, + NOT + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleRoot.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleRoot.java new file mode 100644 index 0000000000..91597bafc5 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleRoot.java @@ -0,0 +1,117 @@ +/* + * 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.xds.security.authz.rule.tree; + +import org.apache.dubbo.xds.security.authz.AuthorizationRequestContext; + +public class RuleRoot extends CompositeRuleNode { + + /** + * Relations between rule tree roots. + * All roots that has Relation=AND will do AND, and all roots has Relation=OR will do OR. + */ + private Action action; + + public RuleRoot(Relation relation, Action action, String name) { + super(name, relation); + this.action = action; + } + + public RuleRoot(Relation relation, Action action) { + super("", relation); + this.action = action; + } + + public Action getAction() { + return action; + } + + @Override + public boolean evaluate(AuthorizationRequestContext context) { + boolean result; + if (context.enableTrace()) { + String msg = " "; + context.addTraceInfo(msg); + } + if (relation == Relation.AND) { + result = children.values().stream() + .allMatch(childList -> childList.stream().allMatch(ch -> ch.evaluate(context))); + } else { + // Relation == OR + result = children.values().stream() + .anyMatch(childList -> childList.stream().anyMatch(ch -> ch.evaluate(context))); + } + if (context.enableTrace()) { + String msg = " " + (result ? "match" : "not match, action:" + action); + context.addTraceInfo(msg); + } + return result; + } + + /** + * The action of authorization policy + */ + public enum Action { + + /** + * The request must map this policy + */ + ALLOW("ALLOW", true), + + /** + * The request must not map this policy + */ + DENY("DENY", false), + + /** + * Only log this policy, will not affect the result + */ + LOG("LOG", false); + + private final String name; + + private boolean boolVal; + + Action(String name, boolean boolValue) { + this.name = name; + this.boolVal = boolValue; + } + + public static Action map(String name) { + name = name.toUpperCase(); + switch (name) { + case "ALLOW": + return ALLOW; + case "DENY": + return DENY; + case "LOG": + return LOG; + default: + return null; + } + } + + public boolean boolVal() { + return boolVal; + } + } + + @Override + public String toString() { + return "RuleRoot{" + "action=" + action + "} " + super.toString(); + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleTreeBuilder.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleTreeBuilder.java new file mode 100644 index 0000000000..b7943e8edd --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/authz/rule/tree/RuleTreeBuilder.java @@ -0,0 +1,122 @@ +/* + * 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.xds.security.authz.rule.tree; + +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.xds.security.authz.rule.AuthorizationPolicyPathConvertor; +import org.apache.dubbo.xds.security.authz.rule.matcher.WildcardStringMatcher; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleNode.Relation; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot.Action; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * non thread-safe + */ +public class RuleTreeBuilder { + + /** + * Root of the rule tree. + */ + private List roots = new ArrayList<>(); + + /** + * The relations between nodes that have same parent.

+ * eg:

+ * 1. rules[0].from AND rules[0].to + * Only when the request meet all FROM AND TO rule,their parent (rules[0]) will returns true. + *

+ * 2. rules[0].from[0].source[0].principles[0] OR rules[0].from[0].source[0].namespaces[0] + * Only when the request meet PRINCIPLE OR NAMESPACE rule, their parent (source[0]) will returns true. + *

+ * The node in same level shares same relation, like rules.from and rules.to because they are in same level (2). + */ + private List nodeLevelRelations = new ArrayList<>(); + + public RuleTreeBuilder() {} + + public void addRoot(RuleRoot root) { + this.roots.add(root); + } + + public void addRoot(Relation relationToOtherRoots, Action action) { + this.roots.add(new RuleRoot(relationToOtherRoots, action)); + } + + public void createFromRuleMap(Map map, RuleRoot rootToCreate) { + if (CollectionUtils.isEmpty(nodeLevelRelations)) { + throw new RuntimeException("Node level relations can't be null or empty"); + } + if (this.roots.isEmpty()) { + throw new RuntimeException("No rule root exist."); + } + for (String key : map.keySet()) { + Object value = map.get(key); + processNode(rootToCreate, key, value, 0); + } + } + + public void setPathLevelRelations(List pathLevelRelations) { + this.nodeLevelRelations = pathLevelRelations; + } + + private void processNode(CompositeRuleNode parent, String currentKey, Object value, int level) { + // key:name of current node + // value:values for children of current node + if (value instanceof List) { + List list = (List) value; + if (!list.isEmpty()) { + + if (list.get(0) instanceof String) { + + List matchers = ((List) list) + .stream() + .map(s -> new WildcardStringMatcher( + s, AuthorizationPolicyPathConvertor.convert(currentKey))) + .collect(Collectors.toList()); + + LeafRuleNode current = new LeafRuleNode(matchers, currentKey); + parent.addChild(current); + } else if (list.get(0) instanceof Map) { + + CompositeRuleNode current = new CompositeRuleNode(currentKey, nodeLevelRelations.get(level)); + parent.addChild(current); + for (Object item : list) { + ((Map) item) + .forEach((childKey, childValue) -> + processNode(current, currentKey + "." + childKey, childValue, level + 1)); + } + } + } + } else if (value instanceof Map) { + CompositeRuleNode current = new CompositeRuleNode(currentKey, nodeLevelRelations.get(level)); + parent.addChild(current); + ((Map) value) + .forEach((childKey, childValue) -> + processNode(current, currentKey + "." + childKey, childValue, level + 1)); + } else { + throw new RuntimeException(); + } + } + + public List getRoots() { + return roots; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/KubeServiceJwtIdentitySource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/KubeServiceJwtIdentitySource.java new file mode 100644 index 0000000000..e7b11fc4d8 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/KubeServiceJwtIdentitySource.java @@ -0,0 +1,48 @@ +/* + * 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.xds.security.identity; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.kubernetes.KubeEnv; +import org.apache.dubbo.xds.security.api.ServiceIdentitySource; + +import java.nio.charset.StandardCharsets; + +public class KubeServiceJwtIdentitySource implements ServiceIdentitySource { + + private final KubeEnv kubeEnv; + + private final ErrorTypeAwareLogger logger = + LoggerFactory.getErrorTypeAwareLogger(KubeServiceJwtIdentitySource.class); + + public KubeServiceJwtIdentitySource(ApplicationModel applicationModel) { + this.kubeEnv = applicationModel.getBeanFactory().getBean(KubeEnv.class); + } + + @Override + public String getToken(URL url) { + try { + return new String(kubeEnv.getServiceAccountToken(), StandardCharsets.UTF_8); + } catch (Exception e) { + logger.error("99-1", "", "Failed to read ServiceAccount from KubeEnv.", "", e); + return ""; + } + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/NoOpServiceIdentitySource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/NoOpServiceIdentitySource.java new file mode 100644 index 0000000000..3a10bfa488 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/NoOpServiceIdentitySource.java @@ -0,0 +1,28 @@ +/* + * 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.xds.security.identity; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.xds.security.api.ServiceIdentitySource; + +public class NoOpServiceIdentitySource implements ServiceIdentitySource { + + @Override + public String getToken(URL url) { + return null; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/RemoteIdentitySource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/RemoteIdentitySource.java new file mode 100644 index 0000000000..4db696f76e --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/identity/RemoteIdentitySource.java @@ -0,0 +1,55 @@ +/* + * 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.xds.security.identity; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.xds.security.api.ServiceIdentitySource; + +import okhttp3.OkHttpClient; +import okhttp3.Request.Builder; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class RemoteIdentitySource implements ServiceIdentitySource { + + private static final ErrorTypeAwareLogger logger = + LoggerFactory.getErrorTypeAwareLogger(RemoteIdentitySource.class); + + private static final String REMOTE_IDENTITY_KEY = "remoteIdentity"; + + private final OkHttpClient httpClient; + + public RemoteIdentitySource() { + this.httpClient = new OkHttpClient.Builder().build(); + } + + @Override + public String getToken(URL url) { + String tokenServiceAddr = url.getParameter(REMOTE_IDENTITY_KEY); + try (Response response = httpClient + .newCall(new Builder().get().url(tokenServiceAddr).build()) + .execute()) { + ResponseBody body = response.body(); + return body == null ? null : body.string(); + } catch (Exception e) { + logger.error("99-1", "", "", "Failed to get token from remote service", e); + } + return null; + } +} diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/istio/IstioCitadelCertificateSigner.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/istio/IstioCitadelCertificateSigner.java new file mode 100644 index 0000000000..5daf036082 --- /dev/null +++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/security/istio/IstioCitadelCertificateSigner.java @@ -0,0 +1,378 @@ +/* + * 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.xds.security.istio; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.xds.istio.IstioConstant; +import org.apache.dubbo.xds.istio.IstioEnv; +import org.apache.dubbo.xds.security.api.CertPair; +import org.apache.dubbo.xds.security.api.CertSource; +import org.apache.dubbo.xds.security.api.TrustSource; +import org.apache.dubbo.xds.security.api.X509CertChains; +import org.apache.dubbo.xds.security.authn.SecretConfig; +import org.apache.dubbo.xds.security.authn.SecretConfig.ConfigType; +import org.apache.dubbo.xds.security.authn.SecretConfig.Source; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.ECGenParameterSpec; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.grpc.ClientInterceptor; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.grpc.stub.MetadataUtils; +import io.grpc.stub.StreamObserver; +import istio.v1.auth.IstioCertificateRequest; +import istio.v1.auth.IstioCertificateResponse; +import istio.v1.auth.IstioCertificateServiceGrpc; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.ExtensionsGenerator; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.bouncycastle.util.io.pem.PemObject; + +import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_GENERATE_CERT_ISTIO; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_GENERATE_KEY_ISTIO; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_RECEIVE_ERROR_MSG_ISTIO; + +@Activate +public class IstioCitadelCertificateSigner implements CertSource, TrustSource { + + private static final ErrorTypeAwareLogger logger = + LoggerFactory.getErrorTypeAwareLogger(IstioCitadelCertificateSigner.class); + + private final IstioEnv istioEnv; + + private volatile CertPair certPair; + + private volatile X509CertChains trustChain; + + public IstioCitadelCertificateSigner() { + this.istioEnv = IstioEnv.getInstance(); + if (!istioEnv.haveServiceAccount()) { + return; + } + ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1); + long refreshRate = + IstioEnv.getInstance().getSecretTTL() - IstioEnv.getInstance().getTryRefreshBeforeCertExpireAt(); + if (refreshRate <= 0) { + refreshRate = IstioEnv.getInstance().getSecretTTL(); + } + scheduledThreadPool.scheduleAtFixedRate(new GenerateCertTask(), 0, refreshRate, TimeUnit.SECONDS); + } + + @Override + public CertPair getCert(URL url, SecretConfig secretConfig) { + if (certPair != null && !certPair.isExpire()) { + return certPair; + } + return doGenerateCert(); + } + + @Override + public SecretConfig selectSupportedCertConfig(URL url, List secretConfigs) { + if (!IstioConstant.ISTIO_NAME.equals(url.getParameter("mesh"))) { + return null; + } + for (SecretConfig secretConfig : secretConfigs) { + if (secretConfig.configType().equals(ConfigType.CERT) + && secretConfig.source().equals(Source.SDS)) { + // TODO currently simply choose first SDS cert config. + // consider there may have more different SDS cert source + return secretConfig; + } + } + return null; + } + + @Override + public SecretConfig selectSupportedTrustConfig(URL url, List secretConfigs) { + if (!IstioConstant.ISTIO_NAME.equals(url.getParameter("mesh"))) { + return null; + } + for (SecretConfig secretConfig : secretConfigs) { + if (secretConfig.configType().equals(ConfigType.TRUST) + && secretConfig.source().equals(Source.SDS)) { + return secretConfig; + } + } + return null; + } + + @Override + public X509CertChains getTrustCerts(URL url, SecretConfig secretConfig) { + getCert(url, secretConfig); + return trustChain; + } + + private class GenerateCertTask implements Runnable { + @Override + public void run() { + doGenerateCert(); + } + } + + private CertPair doGenerateCert() { + synchronized (this) { + if (certPair == null || certPair.isExpire() || canTryUpdate(certPair.getExpireTime())) { + try { + certPair = createCert(); + } catch (IOException e) { + logger.error(REGISTRY_FAILED_GENERATE_CERT_ISTIO, "", "", "Generate Cert from Istio failed.", e); + throw new RpcException("Generate Cert from Istio failed.", e); + } + } + } + return certPair; + } + + public boolean canTryUpdate(Long expireAt) { + Long refreshBeforeCertExpireAt = IstioEnv.getInstance().getTryRefreshBeforeCertExpireAt(); + + long min = 0; + long max = expireAt; + long rand = min + (new SecureRandom().nextLong() * (max - min + 1)); + + return System.currentTimeMillis() - expireAt < (refreshBeforeCertExpireAt - rand); + } + + public CertPair createCert() throws IOException { + PublicKey publicKey = null; + PrivateKey privateKey = null; + ContentSigner signer = null; + + if (istioEnv.isECCFirst()) { + try { + ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); + KeyPairGenerator g = KeyPairGenerator.getInstance("EC"); + g.initialize(ecSpec, new SecureRandom()); + KeyPair keypair = g.generateKeyPair(); + publicKey = keypair.getPublic(); + privateKey = keypair.getPrivate(); + signer = new JcaContentSignerBuilder("SHA256withECDSA").build(keypair.getPrivate()); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | OperatorCreationException e) { + logger.error( + REGISTRY_FAILED_GENERATE_KEY_ISTIO, + "", + "", + "Generate Key with secp256r1 algorithm failed. Please check if your system support. " + + "Will attempt to generate with RSA2048.", + e); + } + } + + if (publicKey == null) { + try { + KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA"); + kpGenerator.initialize(istioEnv.getRasKeySize()); + KeyPair keypair = kpGenerator.generateKeyPair(); + publicKey = keypair.getPublic(); + privateKey = keypair.getPrivate(); + signer = new JcaContentSignerBuilder("SHA256WithRSA").build(keypair.getPrivate()); + } catch (NoSuchAlgorithmException | OperatorCreationException e) { + logger.error( + REGISTRY_FAILED_GENERATE_KEY_ISTIO, + "", + "", + "Generate Key with SHA256WithRSA algorithm " + "failed. Please check if your system support.", + e); + throw new RpcException(e); + } + } + + String csr = generateCsr(publicKey, signer); + String caCert = istioEnv.getCaCert(); + ManagedChannel channel; + if (StringUtils.isNotEmpty(caCert)) { + channel = NettyChannelBuilder.forTarget(istioEnv.getCaAddr()) + .sslContext(GrpcSslContexts.forClient() + .trustManager(new ByteArrayInputStream(caCert.getBytes(StandardCharsets.UTF_8))) + .build()) + .build(); + } else { + channel = NettyChannelBuilder.forTarget(istioEnv.getCaAddr()) + .sslContext(GrpcSslContexts.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build()) + .build(); + } + + // Istio always use SA token(JWT) to verify xDS client. + IstioCertificateServiceGrpc.IstioCertificateServiceStub stub = + IstioCertificateServiceGrpc.newStub(channel).withInterceptors(getJwtHeaderInterceptor()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + StringBuffer publicKeyBuilder = new StringBuffer(); + AtomicBoolean failed = new AtomicBoolean(false); + + StreamObserver observer = generateResponseObserver(countDownLatch, publicKeyBuilder, failed); + stub.createCertificate(generateRequest(csr), observer); + + long expireTime = + System.currentTimeMillis() + (long) (istioEnv.getSecretTTL() * istioEnv.getSecretGracePeriodRatio()); + + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RpcException("Generate Cert Failed. Wait for cert failed.", e); + } + + if (failed.get()) { + throw new RpcException("Generate Cert Failed. Send csr request failed. Please check log above."); + } + + String privateKeyPem = generatePrivatePemKey(privateKey); + CertPair certPair = + new CertPair(privateKeyPem, publicKeyBuilder.toString(), System.currentTimeMillis(), expireTime); + + channel.shutdown(); + return certPair; + } + + private void updateTrust(List trustChains) { + try { + this.trustChain = new X509CertChains(trustChains); + } catch (Exception e) { + logger.error( + REGISTRY_FAILED_GENERATE_KEY_ISTIO, + "", + "", + "Got exception when resolving trust chains from " + "istio", + e); + } + } + + private ClientInterceptor getJwtHeaderInterceptor() { + Metadata headerWithJwt = new Metadata(); + Metadata.Key key = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER); + headerWithJwt.put(key, "Bearer " + istioEnv.getServiceAccount()); + + key = Metadata.Key.of("ClusterID", Metadata.ASCII_STRING_MARSHALLER); + headerWithJwt.put(key, istioEnv.getIstioMetaClusterId()); + return MetadataUtils.newAttachHeadersInterceptor(headerWithJwt); + } + + private IstioCertificateRequest generateRequest(String csr) { + return IstioCertificateRequest.newBuilder() + .setCsr(csr) + .setValidityDuration(istioEnv.getSecretTTL()) + .build(); + } + + private StreamObserver generateResponseObserver( + CountDownLatch countDownLatch, StringBuffer publicKeyBuilder, AtomicBoolean failed) { + return new StreamObserver() { + @Override + public void onNext(IstioCertificateResponse istioCertificateResponse) { + for (int i = 0; i < istioCertificateResponse.getCertChainCount(); i++) { + publicKeyBuilder.append( + istioCertificateResponse.getCertChainBytes(i).toStringUtf8()); + } + if (logger.isDebugEnabled()) { + logger.debug("Receive Cert chain from Istio Citadel. \n" + publicKeyBuilder); + } + updateTrust(istioCertificateResponse.getCertChainList()); + countDownLatch.countDown(); + } + + @Override + public void onError(Throwable throwable) { + failed.set(true); + logger.error( + REGISTRY_RECEIVE_ERROR_MSG_ISTIO, + "", + "", + "Receive error message from Istio Citadel grpc" + " stub.", + throwable); + countDownLatch.countDown(); + } + + @Override + public void onCompleted() { + countDownLatch.countDown(); + } + }; + } + + private String generatePrivatePemKey(PrivateKey privateKey) throws IOException { + String key = generatePemKey("RSA PRIVATE KEY", privateKey.getEncoded()); + if (logger.isDebugEnabled()) { + logger.debug("Generated Private Key. \n" + key); + } + return key; + } + + private String generatePemKey(String type, byte[] content) throws IOException { + PemObject pemObject = new PemObject(type, content); + StringWriter str = new StringWriter(); + JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(str); + jcaPEMWriter.writeObject(pemObject); + jcaPEMWriter.close(); + str.close(); + return str.toString(); + } + + public String generateCsr(PublicKey publicKey, ContentSigner signer) throws IOException { + GeneralNames subjectAltNames = new GeneralNames(new GeneralName[] {new GeneralName(6, istioEnv.getCsrHost())}); + + ExtensionsGenerator extGen = new ExtensionsGenerator(); + extGen.addExtension(Extension.subjectAlternativeName, true, subjectAltNames); + + PKCS10CertificationRequest request = new JcaPKCS10CertificationRequestBuilder( + new X500Name("O=" + istioEnv.getTrustDomain()), publicKey) + .addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()) + .build(signer); + + String csr = generatePemKey("CERTIFICATE REQUEST", request.getEncoded()); + + if (logger.isDebugEnabled()) { + logger.debug("CSR Request to Istio Citadel. \n" + csr); + } + return csr; + } +} diff --git a/dubbo-xds/src/main/proto/ca.proto b/dubbo-xds/src/main/proto/ca.proto new file mode 100644 index 0000000000..41e6addb79 --- /dev/null +++ b/dubbo-xds/src/main/proto/ca.proto @@ -0,0 +1,62 @@ +// Copyright Istio Authors +// +// Licensed 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. + +// The canonical version of this proto can be found at +// https://github.com/istio/api/blob/9abf4c87205f6ad04311fa021ce60803d8b95f78/security/v1alpha1/ca.proto + +syntax = "proto3"; + +import "google/protobuf/struct.proto"; + +// Keep this package for backward compatibility. +package istio.v1.auth; + +option go_package = "istio.io/api/security/v1alpha1"; +option java_generic_services = true; +option java_multiple_files = true; + +// Certificate request message. The authentication should be based on: +// 1. Bearer tokens carried in the side channel; +// 2. Client-side certificate via Mutual TLS handshake. +// Note: the service implementation is REQUIRED to verify the authenticated caller is authorize to +// all SANs in the CSR. The server side may overwrite any requested certificate field based on its +// policies. +message IstioCertificateRequest { + // PEM-encoded certificate request. + // The public key in the CSR is used to generate the certificate, + // and other fields in the generated certificate may be overwritten by the CA. + string csr = 1; + // Optional: requested certificate validity period, in seconds. + int64 validity_duration = 3; + + // $hide_from_docs + // Optional: Opaque metadata provided by the XDS node to Istio. + // Supported metadata: WorkloadName, WorkloadIP, ClusterID + google.protobuf.Struct metadata = 4; +} + +// Certificate response message. +message IstioCertificateResponse { + // PEM-encoded certificate chain. + // The leaf cert is the first element, and the root cert is the last element. + repeated string cert_chain = 1; +} + +// Service for managing certificates issued by the CA. +service IstioCertificateService { + // Using provided CSR, returns a signed certificate. + rpc CreateCertificate(IstioCertificateRequest) + returns (IstioCertificateResponse) { + } +} diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener new file mode 100644 index 0000000000..f14abf4644 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.deploy.ApplicationDeployListener @@ -0,0 +1 @@ +mesh=org.apache.dubbo.xds.config.XdsApplicationDeployListener diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.ssl.CertProvider b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.ssl.CertProvider new file mode 100644 index 0000000000..7c22ac7245 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.common.ssl.CertProvider @@ -0,0 +1 @@ +istio=org.apache.dubbo.xds.security.api.XdsCertProvider diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory index 0df432b5c2..aca79a9fdb 100644 --- a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory @@ -1 +1 @@ -xds=org.apache.dubbo.xds.registry.XdsRegistryFactory +istio=org.apache.dubbo.xds.registry.XdsRegistryFactory diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.ChannelContextListener b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.ChannelContextListener new file mode 100644 index 0000000000..fe06d50e19 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.api.ChannelContextListener @@ -0,0 +1 @@ +credential=org.apache.dubbo.xds.security.authz.resolver.ConnectionCredentialResolver diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter new file mode 100644 index 0000000000..9b5bf6997c --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter @@ -0,0 +1,2 @@ +saJwtClient=org.apache.dubbo.xds.security.authz.ConsumerServiceAccountAuthFilter +providerAuth=org.apache.dubbo.xds.security.ProviderAuthFilter diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer new file mode 100644 index 0000000000..3390cb7b7d --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.model.ScopeModelInitializer @@ -0,0 +1 @@ +security=org.apache.dubbo.xds.security.SecurityBeanConfig diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.listener.CdsListener b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.listener.CdsListener new file mode 100644 index 0000000000..2facffff51 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.listener.CdsListener @@ -0,0 +1 @@ +tlsUpstream=org.apache.dubbo.xds.listener.UpstreamTlsConfigListener diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.listener.LdsListener b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.listener.LdsListener new file mode 100644 index 0000000000..d7d22635c1 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.listener.LdsListener @@ -0,0 +1 @@ +tlsDownstream=org.apache.dubbo.xds.listener.DownstreamTlsConfigListener diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.CertSource b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.CertSource new file mode 100644 index 0000000000..721b2eb030 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.CertSource @@ -0,0 +1,2 @@ +istio=org.apache.dubbo.xds.security.istio.IstioCitadelCertificateSigner +local=org.apache.dubbo.xds.security.api.LocalSecretProvider diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.RequestAuthorizer b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.RequestAuthorizer new file mode 100644 index 0000000000..88117c84b2 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.RequestAuthorizer @@ -0,0 +1 @@ +istio=org.apache.dubbo.xds.security.authz.RoleBasedAuthorizer diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.ServiceIdentitySource b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.ServiceIdentitySource new file mode 100644 index 0000000000..06c6a91b3e --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.ServiceIdentitySource @@ -0,0 +1,3 @@ +istio=org.apache.dubbo.xds.security.identity.KubeServiceJwtIdentitySource +noOp=org.apache.dubbo.xds.security.identity.NoOpServiceIdentitySource +remote=org.apache.dubbo.xds.security.identity.RemoteIdentitySource diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.TrustSource b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.TrustSource new file mode 100644 index 0000000000..721b2eb030 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.TrustSource @@ -0,0 +1,2 @@ +istio=org.apache.dubbo.xds.security.istio.IstioCitadelCertificateSigner +local=org.apache.dubbo.xds.security.api.LocalSecretProvider diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.resolver.CredentialResolver b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.resolver.CredentialResolver new file mode 100644 index 0000000000..4b999094ad --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.resolver.CredentialResolver @@ -0,0 +1,5 @@ +jwt=org.apache.dubbo.xds.security.authz.resolver.JwtRequestCredentialResolver +http=org.apache.dubbo.xds.security.authz.resolver.HttpRequestCredentialResolver +kubernetes=org.apache.dubbo.xds.security.authz.resolver.KubernetesRequestCredentialResolver +spiffe=org.apache.dubbo.xds.security.authz.resolver.SpiffeRequestCredentialResolver +connection=org.apache.dubbo.xds.security.authz.resolver.ConnectionRequestCredentialResolver diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleFactory b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleFactory new file mode 100644 index 0000000000..d1a0ce20c6 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleFactory @@ -0,0 +1 @@ +default=org.apache.dubbo.xds.security.authz.rule.source.MapRuleFactory diff --git a/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleProvider b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleProvider new file mode 100644 index 0000000000..186619b315 --- /dev/null +++ b/dubbo-xds/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleProvider @@ -0,0 +1 @@ +istio=org.apache.dubbo.xds.security.authz.rule.source.KubeRuleProvider diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoTest.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoTest.java index fb27c37112..6e157bf274 100644 --- a/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoTest.java +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoTest.java @@ -17,16 +17,14 @@ package org.apache.dubbo.xds; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Protocol; -import org.apache.dubbo.rpc.ProxyFactory; import org.apache.dubbo.rpc.cluster.Directory; import org.apache.dubbo.rpc.cluster.RouterChain; import org.apache.dubbo.rpc.cluster.SingleRouterChain; import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.auth.DemoService; import org.apache.dubbo.xds.directory.XdsDirectory; import org.apache.dubbo.xds.resource.XdsCluster; import org.apache.dubbo.xds.resource.XdsVirtualHost; @@ -35,6 +33,7 @@ import org.apache.dubbo.xds.router.XdsRouter; import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -42,19 +41,41 @@ import org.mockito.Mockito; public class DemoTest { - private Protocol protocol = - ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); + // private Protocol protocol = + // ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); - private ProxyFactory proxy = - ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + // + // private ProxyFactory proxy = + // ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + // - // @Test + // @Test public void testXdsRouterInitial() throws InterruptedException { + System.setProperty("API_SERVER_PATH", "https://127.0.0.1:6443"); + System.setProperty("SA_CA_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + System.setProperty( + "SA_TOKEN_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"); + System.setProperty("NAMESPACE", "foo"); + + System.setProperty("CA_ADDR_KEY", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + + // ApplicationModel app = FrameworkModel.defaultModel().defaultApplication(); + // KubeEnv kubeEnv = new KubeEnv(app); + // kubeEnv.setNamespace("foo"); + // kubeEnv.setEnableSsl(true); + // kubeEnv.setApiServerPath( "https://127.0.0.1:6443"); + // + // kubeEnv.setServiceAccountTokenPath("/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"); + // + // kubeEnv.setServiceAccountCaPath("/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + // app.getBeanFactory().registerBean(kubeEnv); URL url = URL.valueOf("xds://localhost:15010/?secure=plaintext"); PilotExchanger.initialize(url); + new CountDownLatch(1).await(); + Thread.sleep(7000); Directory directory = Mockito.mock(Directory.class); @@ -62,7 +83,7 @@ public class DemoTest { .thenReturn(URL.valueOf("dubbo://0.0.0.0:15010/DemoService?provided-by=dubbo-samples-xds-provider")); Mockito.when(directory.getInterface()).thenReturn(DemoService.class); // doReturn(DemoService.class).when(directory.getInterface()); - Mockito.when(directory.getProtocol()).thenReturn(protocol); + // Mockito.when(directory.getProtocol()).thenReturn(protocol); SingleRouterChain singleRouterChain = new SingleRouterChain<>(Collections.emptyList(), Arrays.asList(new XdsRouter<>(url)), false, null); diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/AuthTest.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/AuthTest.java new file mode 100644 index 0000000000..477a1ebf63 --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/AuthTest.java @@ -0,0 +1,120 @@ +/* + * 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.xds.auth; + +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.xds.istio.IstioConstant; +import org.apache.dubbo.xds.kubernetes.KubeApiClient; +import org.apache.dubbo.xds.kubernetes.KubeEnv; +import org.apache.dubbo.xds.security.authz.rule.source.KubeRuleProvider; +import org.apache.dubbo.xds.security.authz.rule.source.MapRuleFactory; + +public class AuthTest { + + // @Test + public void authZTest() throws Exception { + + ApplicationModel applicationModel = ApplicationModel.defaultModel(); + System.setProperty("NAMESPACE", "foo"); + System.setProperty("SERVICE_NAME", "httpbin"); + System.setProperty("API_SERVER_PATH", "https://127.0.0.1:6443"); + System.setProperty("SA_CA_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + System.setProperty( + "SA_TOKEN_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"); + + KubeEnv kubeEnv = new KubeEnv(applicationModel); + kubeEnv.setNamespace("foo"); + kubeEnv.setEnableSsl(true); + kubeEnv.setApiServerPath("https://127.0.0.1:6443"); + kubeEnv.setServiceAccountTokenPath( + "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"); + kubeEnv.setServiceAccountCaPath("/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + + applicationModel.getBeanFactory().registerBean(kubeEnv); + applicationModel.getBeanFactory().registerBean(new KubeApiClient(applicationModel)); + + MapRuleFactory defaultRuleFactory = new MapRuleFactory(); + applicationModel.getBeanFactory().registerBean(defaultRuleFactory); + + KubeRuleProvider provider = new KubeRuleProvider(applicationModel); + applicationModel.getBeanFactory().registerBean(provider); + applicationModel.getBeanFactory().registerBean(MapRuleFactory.class); + // List source = provider.getSource(null, null); + // + // List rules = defaultRuleFactory.getRules(source.get(0)); + + // HttpBasedMeshRequestCredential credential = new HttpBasedMeshRequestCredential( + // "cluster.local/ns/default/sa/sleep", + // "test_subject", + // "/info", + // "GET", + // "test", + // new HashMap<>() + // ); + // + // credential.setIssuer(""); + // credential.setTargetPath(); + // credential.setServiceName(); + // credential.setPodId(); + // credential.setNamespace(); + // credential.setServiceUid(); + + // AuthorizationRequestContext context = new AuthorizationRequestContext(null,credential); + // boolean res = rules.get(0).evaluate(context); + // + // System.out.println(res); + } + + static { + System.setProperty(IstioConstant.SERVICE_NAME_KEY, "httpbin"); + } + + static T newRef(ApplicationModel applicationModel, Class serviceClass) { + ReferenceConfig referenceConfig = new ReferenceConfig<>(); + referenceConfig.setInterface(serviceClass); + RegistryConfig config = new RegistryConfig("istio://localhost:15012?signer=istio"); + referenceConfig.setRegistry(config); + referenceConfig.setCluster("xds"); + referenceConfig.setScopeModel(applicationModel.newModule()); + referenceConfig.setTimeout(1000000); + referenceConfig.getParameters().put("mesh", "istio"); + referenceConfig.getParameters().put("security", "mTLS,serviceIdentity"); + referenceConfig.setProvidedBy("httpbin"); + return referenceConfig.get(false); + } + + static void newService(ApplicationModel applicationModel, T serviceInst, Class serviceClass, int port) { + ServiceConfig serviceConfig = new ServiceConfig<>(); + serviceConfig.setRef(serviceInst); + ProtocolConfig triConf = new ProtocolConfig("tri"); + triConf.setPort(port); + triConf.setHost("192.168.0.103"); + serviceConfig.setRegistry(new RegistryConfig("istio://localhost:15012?signer=istio")); + serviceConfig.setProtocol(triConf); + serviceConfig.setCluster("xds"); + serviceConfig.setScopeModel(applicationModel.newModule()); + serviceConfig.setInterface(serviceClass); + serviceConfig.setTimeout(1000000); + serviceConfig.getParameters().put("mesh", "istio"); + serviceConfig.getParameters().put("security", "mTLS,serviceIdentity"); + serviceConfig.export(); + } +} diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/CredentialResolvingTest.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/CredentialResolvingTest.java new file mode 100644 index 0000000000..385620b419 --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/CredentialResolvingTest.java @@ -0,0 +1,97 @@ +/* + * 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.xds.auth; + +import org.apache.dubbo.xds.security.api.X509CertChains; +import org.apache.dubbo.xds.security.authz.resolver.ConnectionCredentialResolver.CertificateCredential; +import org.apache.dubbo.xds.security.authz.resolver.ConnectionCredentialResolver.ConnectionCredential; +import org.apache.dubbo.xds.security.authz.resolver.SpiffeCredentialResolver; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CredentialResolvingTest { + + // @Test + public void testResolveSpiffe() throws Exception { + SpiffeCredentialResolver spiffeCredentialResolver = new SpiffeCredentialResolver(); + URI spiffeId = + spiffeCredentialResolver.readSpiffeId(getConnectionCredential().getCertificateCredentials()); + } + + private ConnectionCredential getConnectionCredential() throws Exception { + List certs = new ArrayList<>(); + CertificateCredential certificateCredential = + new CertificateCredential(new X509CertChains(Collections.singletonList(testCert)) + .readAsCerts() + .get(0)); + certs.add(certificateCredential); + ConnectionCredential connectionCredential = new ConnectionCredential(certs, "http/2", "my.application.app1"); + return connectionCredential; + } + + /** + *Certificate: + * Data: + * Version: 3 (0x2) + * Serial Number: + * 04:10:df:a2:3b:45:3b:75:ec:fd:fa:41:1b:84:cb:70:d9 + * Signature Algorithm: sha256WithRSAEncryption + * Issuer: CN=SPIFFE CA + * Validity + * Not Before: Mar 10 12:00:00 2021 GMT + * Not After : Mar 10 12:00:00 2022 GMT + * Subject: CN=spiffe://cluster.local/ns/default/sa/service1 + * Subject Public Key Info: + * Public Key Algorithm: rsaEncryption + * Public-Key: (2048 bit) + * X509v3 extensions: + * X509v3 Key Usage: critical + * Digital Signature, Key Encipherment + * X509v3 Extended Key Usage: + * TLS Web Server Authentication, TLS Web Client Authentication + * X509v3 Subject Alternative Name: + * URI: spiffe://cluster.local/ns/default/sa/service1 + * Signature Algorithm: sha256WithRSAEncryption + * 5c:ca:ba:8e:92:...:00:00:00:00:00:00:00:00:00:00:00:00 + */ + final String testCert = "-----BEGIN CERTIFICATE-----\n" + + "Q2VydGlmaWNhdGU6DQogICAgRGF0YToNCiAgICAgICAgVmVyc2lvbjogMyAoMHgy\n" + + "KQ0KICAgICAgICBTZXJpYWwgTnVtYmVyOg0KICAgICAgICAgICAgMDQ6MTA6ZGY6\n" + + "YTI6M2I6NDU6M2I6NzU6ZWM6ZmQ6ZmE6NDE6MWI6ODQ6Y2I6NzA6ZDkNCiAgICBT\n" + + "aWduYXR1cmUgQWxnb3JpdGhtOiBzaGEyNTZXaXRoUlNBRW5jcnlwdGlvbg0KICAg\n" + + "ICAgICBJc3N1ZXI6IENOPVNQSUZGRSBDQQ0KICAgICAgICBWYWxpZGl0eQ0KICAg\n" + + "ICAgICAgICAgTm90IEJlZm9yZTogTWFyIDEwIDEyOjAwOjAwIDIwMjEgR01UDQog\n" + + "ICAgICAgICAgICBOb3QgQWZ0ZXIgOiBNYXIgMTAgMTI6MDA6MDAgMjAyMiBHTVQN\n" + + "CiAgICAgICAgU3ViamVjdDogQ049c3BpZmZlOi8vY2x1c3Rlci5sb2NhbC9ucy9k\n" + + "ZWZhdWx0L3NhL3NlcnZpY2UxIA0KICAgICAgICBTdWJqZWN0IFB1YmxpYyBLZXkg\n" + + "SW5mbzoNCiAgICAgICAgICAgIFB1YmxpYyBLZXkgQWxnb3JpdGhtOiByc2FFbmNy\n" + + "eXB0aW9uDQogICAgICAgICAgICAgICAgUHVibGljLUtleTogKDIwNDggYml0KQ0K\n" + + "ICAgICAgICBYNTA5djMgZXh0ZW5zaW9uczoNCiAgICAgICAgICAgIFg1MDl2MyBL\n" + + "ZXkgVXNhZ2U6IGNyaXRpY2FsDQogICAgICAgICAgICAgICAgRGlnaXRhbCBTaWdu\n" + + "YXR1cmUsIEtleSBFbmNpcGhlcm1lbnQNCiAgICAgICAgICAgIFg1MDl2MyBFeHRl\n" + + "bmRlZCBLZXkgVXNhZ2U6IA0KICAgICAgICAgICAgICAgIFRMUyBXZWIgU2VydmVy\n" + + "IEF1dGhlbnRpY2F0aW9uLCBUTFMgV2ViIENsaWVudCBBdXRoZW50aWNhdGlvbg0K\n" + + "ICAgICAgICAgICAgWDUwOXYzIFN1YmplY3QgQWx0ZXJuYXRpdmUgTmFtZTogDQog\n" + + "ICAgICAgICAgICAgICAgVVJJOiBzcGlmZmU6Ly9jbHVzdGVyLmxvY2FsL25zL2Rl\n" + + "ZmF1bHQvc2Evc2VydmljZTEgDQogICAgU2lnbmF0dXJlIEFsZ29yaXRobTogc2hh\n" + + "MjU2V2l0aFJTQUVuY3J5cHRpb24NCiAgICAgICAgIDVjOmNhOmJhOjhlOjkyOi4u\n" + + "LjowMDowMDowMDowMDowMDowMDowMDowMDowMDowMDowMDowMA==" + + "-----END CERTIFICATE-----"; +} diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoService.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoService.java similarity index 91% rename from dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoService.java rename to dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoService.java index 7ae9ade8fc..b9017fe534 100644 --- a/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoService.java +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoService.java @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.xds; +package org.apache.dubbo.xds.auth; public interface DemoService { - default String sayHello() { + default String sayHello(String name) { return null; } } diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoServiceImpl.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoService2.java similarity index 84% rename from dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoServiceImpl.java rename to dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoService2.java index 7e5a735696..a5aea75241 100644 --- a/dubbo-xds/src/test/java/org/apache/dubbo/xds/DemoServiceImpl.java +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoService2.java @@ -14,11 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.xds; +package org.apache.dubbo.xds.auth; -public class DemoServiceImpl implements DemoService { - @Override - public String sayHello() { - return "hello"; +public interface DemoService2 { + default String sayHello(String name) { + return null; } } diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoServiceImpl.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoServiceImpl.java new file mode 100644 index 0000000000..880bfe3932 --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoServiceImpl.java @@ -0,0 +1,28 @@ +/* + * 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.xds.auth; + +import org.apache.dubbo.rpc.RpcContext; + +public class DemoServiceImpl implements DemoService { + @Override + public String sayHello(String name) { + System.out.println("service1 impl get attachment:" + + RpcContext.getServerAttachment().getAttachment("s2")); + return "hello:" + name; + } +} diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoServiceImpl2.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoServiceImpl2.java new file mode 100644 index 0000000000..b1a30ef4c0 --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/DemoServiceImpl2.java @@ -0,0 +1,28 @@ +/* + * 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.xds.auth; + +import org.apache.dubbo.rpc.RpcContext; + +public class DemoServiceImpl2 implements DemoService2 { + @Override + public String sayHello(String name) { + System.out.println("service2 impl get attachment:" + + RpcContext.getServerAttachment().getAttachment("s1")); + return "hello:" + name; + } +} diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/LdsRuleTest.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/LdsRuleTest.java new file mode 100644 index 0000000000..696dd4ffef --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/LdsRuleTest.java @@ -0,0 +1,243 @@ +/* + * 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.xds.auth; + +// import envoy.config.rbac.v3.Permission; +// import envoy.config.rbac.v3.Policy; +// import envoy.config.rbac.v3.Principal; +// import envoy.config.rbac.v3.RBAC; +// import envoy.type.matcher.v3.HttpMatcher; +// import envoy.type.matcher.v3.StringMatcher; +// import envoy.type.v3.CidrRange; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.xds.listener.XdsTlsConfigRepository; +import org.apache.dubbo.xds.security.authz.AuthorizationRequestContext; +import org.apache.dubbo.xds.security.authz.rule.CommonRequestCredential; +import org.apache.dubbo.xds.security.authz.rule.RequestAuthProperty; +import org.apache.dubbo.xds.security.authz.rule.source.LdsRuleFactory; +import org.apache.dubbo.xds.security.authz.rule.tree.RuleRoot; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Random; + +import com.google.protobuf.Any; +import com.google.protobuf.UInt32Value; +import io.envoyproxy.envoy.config.core.v3.CidrRange; +import io.envoyproxy.envoy.config.rbac.v3.Permission; +import io.envoyproxy.envoy.config.rbac.v3.Permission.Set; +import io.envoyproxy.envoy.config.rbac.v3.Policy; +import io.envoyproxy.envoy.config.rbac.v3.Principal; +import io.envoyproxy.envoy.config.rbac.v3.Principal.Authenticated; +import io.envoyproxy.envoy.config.rbac.v3.RBAC; +import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action; +import io.envoyproxy.envoy.config.route.v3.HeaderMatcher; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter; +import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import org.junit.Test; + +public class LdsRuleTest { + + @Test + public void testMatcher() { + + RBAC sampleConfig1 = io.envoyproxy + .envoy + .extensions + .filters + .http + .rbac + .v3 + .RBAC + .newBuilder() + .getRulesBuilder() + .setAction(Action.ALLOW) + .putPolicies( + "policy-1", + Policy.newBuilder() + .addPermissions(Policy.newBuilder() + .addPermissionsBuilder() + .setOrRules(Set.newBuilder() + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("method") + .setExactMatch("GET")))) + .build()) + .addPrincipals(Principal.newBuilder() + .setAuthenticated( + Authenticated.newBuilder() + .setPrincipalName( + StringMatcher.newBuilder() + .setSuffix( + "CN=example.com,OU=IT,O=Example Corp,L=San Francisco,ST=California,C=US"))) + .build()) + .addPrincipals(Principal.newBuilder() + .setRemoteIp(CidrRange.newBuilder() + .setAddressPrefix("11.22.33.0") + .setPrefixLen(UInt32Value.newBuilder() + .setValue(24) + .build())) + .build()) + .build()) + .buildPartial(); + RBAC sampleConfig2 = RBAC.newBuilder() + .setAction(Action.ALLOW) + .putPolicies( + "complex-policy-2", + Policy.newBuilder() + .addPermissions(Permission.newBuilder() + .setAndRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("path") + .setExactMatch("/api"))) + .addRules(Permission.newBuilder() + .setHeader(HeaderMatcher.newBuilder() + .setName("user-agent") + .setSafeRegexMatch( + RegexMatcher.newBuilder() + .setRegex(".*Android.*") + .build())) + .build()))) + .addRules(Permission.newBuilder() + .setOrRules(Permission.Set.newBuilder() + .addRules(Permission.newBuilder() + .setDestinationPort(443)) + .addRules(Permission.newBuilder() + .setDestinationIp(CidrRange.newBuilder() + .setAddressPrefix("10.1.0.0") + .setPrefixLen(UInt32Value.of(16)))) + .build())) + .build()) + .build()) + .addPrincipals(Principal.newBuilder() + .setAndIds(Principal.Set.newBuilder() + .addIds(Principal.newBuilder() + .setOrIds(Principal.Set.newBuilder() + .addIds( + Principal.newBuilder() + .setAuthenticated( + Principal.Authenticated + .newBuilder() + .setPrincipalName( + StringMatcher + .newBuilder() + .setExact( + "user@example.com")))) + .addIds( + Principal.newBuilder() + .setAuthenticated( + Principal.Authenticated + .newBuilder() + .setPrincipalName( + StringMatcher + .newBuilder() + .setPrefix( + "admin")))) + .build())) + .build()) + .build()) + .build()) + .build(); + + io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC rbacConfig = io.envoyproxy + .envoy + .extensions + .filters + .http + .rbac + .v3 + .RBAC + .newBuilder() + .setRules(sampleConfig1) + .build(); + + io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC rbacConfig2 = io.envoyproxy + .envoy + .extensions + .filters + .http + .rbac + .v3 + .RBAC + .newBuilder() + .setRules(sampleConfig2) + .build(); + + HttpFilter rbacFilter = HttpFilter.newBuilder() + .setName("envoy.filters.http.rbac") + .setTypedConfig(Any.pack(rbacConfig)) + .build(); + HttpFilter rbacFilter2 = HttpFilter.newBuilder() + .setName("envoy.filters.http.rbac") + .setTypedConfig(Any.pack(rbacConfig2)) + .build(); + + long start = System.currentTimeMillis(); + boolean r = true; + for (int i = 0; i < 10000; i++) { + LdsRuleFactory ldsRuleFactory = new LdsRuleFactory(null); + List rules = + ldsRuleFactory.getRules(URL.valueOf("test://test"), Arrays.asList(rbacFilter, rbacFilter2)); + + // rule1: ALLOW [ method=GET AND FROM *CN=example.com,OU=IT,O=Example Corp,L=San + // Francisco,ST=California,C=US + // AND sourceIP = 11.22.33*] + // rule2: ALLOW [(path=/api OR user-agent=Android) AND (destinationPort=443 OR destinationIP=10.1.2*) AND + // (Principal = user@example.com OR admin*) ] + CommonRequestCredential credential = new CommonRequestCredential(); + credential.add(RequestAuthProperty.HTTP_METHOD, "GET"); + credential.add( + RequestAuthProperty.PRINCIPAL, + "admin,CN=example.com,OU=IT,O=Example Corp,L=San Francisco,ST=California,C=US"); + credential.add(RequestAuthProperty.URL_PATH, "/api"); + HashMap header = new HashMap<>(); + header.put("path", "/api"); + header.put("method", "GET"); + credential.add(RequestAuthProperty.HEADER, header); + credential.add(RequestAuthProperty.REMOTE_IP, "33.44.55.66"); + credential.add(RequestAuthProperty.DESTINATION_IP, "11.22.33.44"); + credential.add(RequestAuthProperty.DESTINATION_PORT, "443"); + credential.add(RequestAuthProperty.WORKLOAD_ID, new Random().nextInt()); + AuthorizationRequestContext context = new AuthorizationRequestContext(null, credential); + + // context.startTrace(); + boolean res = rules.get(0).evaluate(context); + res &= rules.get(1).evaluate(context); + r &= res; + // Assertions.assertTrue(res); + // System.out.println(context.getTraceInfo()); + } + System.out.println(System.currentTimeMillis() - start); + System.out.println(r); + } + + @Test + public void factoryTest() { + FrameworkModel frameworkModel = new FrameworkModel(); + ApplicationModel applicationModel = frameworkModel.newApplication(); + ApplicationModel applicationModel1 = frameworkModel.newApplication(); + frameworkModel.getBeanFactory().getOrRegisterBean(XdsTlsConfigRepository.class); + } +} diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/MtlsService1.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/MtlsService1.java new file mode 100644 index 0000000000..27c04ec49a --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/MtlsService1.java @@ -0,0 +1,61 @@ +/* + * 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.xds.auth; + +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.xds.istio.IstioConstant; + +public class MtlsService1 extends AuthTest { + + public static void main(String[] args) { + System.setProperty(IstioConstant.WORKLOAD_NAMESPACE_KEY, "bar"); + System.setProperty("API_SERVER_PATH", "https://127.0.0.1:6443"); + System.setProperty("SA_CA_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + System.setProperty( + "SA_TOKEN_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_bar"); + System.setProperty("NAMESPACE", "bar"); + IstioConstant.KUBERNETES_SA_PATH = "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_bar"; + + FrameworkModel f1 = new FrameworkModel(); + ApplicationModel applicationModel = f1.newApplication(); + // KubeEnv kubeEnv = new KubeEnv(applicationModel); + // + // kubeEnv.setNamespace("foo"); + // kubeEnv.setEnableSsl(true); + // kubeEnv.setApiServerPath( "https://127.0.0.1:6443"); + // + // kubeEnv.setServiceAccountTokenPath("/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_bar"); + // + // kubeEnv.setServiceAccountCaPath("/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + // applicationModel.getBeanFactory().registerBean(kubeEnv); + + newService(applicationModel, new DemoServiceImpl(), DemoService.class, 10086); + DemoService2 demoService2 = newRef(applicationModel, DemoService2.class); + + while (true) { + try { + RpcContext.getClientAttachment().setAttachment("s1", "attachment from service1"); + System.out.println(demoService2.sayHello("service1 to service2")); + Thread.sleep(1000L); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/MtlsService2.java b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/MtlsService2.java new file mode 100644 index 0000000000..c0bfdf680c --- /dev/null +++ b/dubbo-xds/src/test/java/org/apache/dubbo/xds/auth/MtlsService2.java @@ -0,0 +1,52 @@ +/* + * 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.xds.auth; + +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.xds.istio.IstioConstant; + +public class MtlsService2 extends AuthTest { + + public static void main(String[] args) throws InterruptedException { + System.setProperty(IstioConstant.WORKLOAD_NAMESPACE_KEY, "foo"); + System.setProperty("API_SERVER_PATH", "https://127.0.0.1:6443"); + System.setProperty("SA_CA_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/ca.crt"); + System.setProperty( + "SA_TOKEN_PATH", "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"); + System.setProperty("NAMESPACE", "foo"); + IstioConstant.KUBERNETES_SA_PATH = "/Users/nameles/Desktop/test_secrets/kubernetes.io/serviceaccount/token_foo"; + + FrameworkModel f2 = new FrameworkModel(); + ApplicationModel applicationModel = f2.newApplication(); + + newService(applicationModel, new DemoServiceImpl2(), DemoService2.class, 10087); + + DemoService demoService = newRef(applicationModel, DemoService.class); + + while (true) { + try { + RpcContext.getClientAttachment().setAttachment("s2", "attachment from service2"); + System.out.println(demoService.sayHello("service2 to service1")); + Thread.sleep(1000L); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +}