This commit is contained in:
namelessssssssssss 2024-06-20 08:06:01 +00:00 committed by GitHub
commit 05b9890426
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
159 changed files with 8667 additions and 340 deletions

View File

@ -147,6 +147,10 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
this(url, null, isUrlFromRegistry);
}
public AbstractDirectory(URL url, RouterChain<T> routerChain, boolean isUrlFromRegistry, URL consumerUrl) {
this(addConsumerUrl(url, consumerUrl), null, isUrlFromRegistry);
}
public AbstractDirectory(URL url, RouterChain<T> routerChain, boolean isUrlFromRegistry) {
if (url == null) {
throw new IllegalArgumentException("url == null");
@ -252,6 +256,13 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
}
}
private static URL addConsumerUrl(URL url, URL consumerUrl) {
Map<String, String> referMap = new HashMap<>();
referMap.put(CONSUMER_URL_KEY, consumerUrl.toString());
url = url.putAttribute(REFER_KEY, referMap);
return url;
}
@Override
public URL getUrl() {
return url;

View File

@ -19,5 +19,6 @@ package org.apache.dubbo.common.ssl;
public enum AuthPolicy {
NONE,
SERVER_AUTH,
CLIENT_AUTH
CLIENT_AUTH_STRICT,
CLIENT_AUTH_PERMISSIVE
}

View File

@ -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,

View File

@ -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<String> SUPPORT_MESH_TYPE = new HashSet<String>() {
{
addAll(Arrays.asList("istio"));
}
};
}

View File

@ -251,10 +251,5 @@
<artifactId>resteasy-jackson-provider</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -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<T> extends ReferenceConfigBase<T> {
private T createProxy(Map<String, String> 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<T> extends ReferenceConfigBase<T> {
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<T> extends ReferenceConfigBase<T> {
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)) {

View File

@ -607,6 +607,10 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
ProtocolConfig protocolConfig, List<URL> registryURLs, RegisterTypeEnum registerType) {
Map<String, String> 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

View File

@ -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<URL> 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<String, String> 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<URL> genCompatibleRegistries(ScopeModel scopeModel, List<URL> registryList, boolean provider) {
List<URL> result = new ArrayList<>(registryList.size());
registryList.forEach(registryURL -> {

View File

@ -98,6 +98,11 @@
<artifactId>dubbo-serialization-fastjson2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-plugin-cluster-mergeable</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>

View File

@ -65,7 +65,7 @@ public interface RestDemoService {
Boolean testBody2(Boolean b);
@POST
@Path("/testBody4")
@Path("/testBody3")
@Consumes({MediaType.TEXT_PLAIN})
TestPO testBody2(TestPO b);

View File

@ -30,7 +30,7 @@
<properties>
<skip_maven_deploy>true</skip_maven_deploy>
<curator5_version>5.1.0</curator5_version>
<zookeeper_version>3.8.4</zookeeper_version>
<zookeeper_version>3.8.3</zookeeper_version>
</properties>
<dependencies>
@ -157,7 +157,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.5.3</version>
<version>1.4.14</version>
<scope>compile</scope>
</dependency>
</dependencies>
@ -204,7 +204,7 @@
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.1</version>
<version>0.10.0</version>
<configuration>
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<metadataRepository>

View File

@ -30,7 +30,7 @@
<properties>
<skip_maven_deploy>true</skip_maven_deploy>
<curator5_version>5.1.0</curator5_version>
<zookeeper_version>3.8.4</zookeeper_version>
<zookeeper_version>3.8.3</zookeeper_version>
</properties>
<dependencies>
@ -157,7 +157,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.5.3</version>
<version>1.4.14</version>
<scope>compile</scope>
</dependency>
</dependencies>
@ -204,7 +204,7 @@
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.1</version>
<version>0.10.0</version>
<configuration>
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<metadataRepository>

View File

@ -37,12 +37,6 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
@ -107,12 +101,6 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@ -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:-}]'

View File

@ -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);
}

View File

@ -31,6 +31,18 @@
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo-spring-boot-interface</artifactId>

View File

@ -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);

View File

@ -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"));
}
}
}

View File

@ -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

View File

@ -35,7 +35,7 @@
<skip_maven_deploy>true</skip_maven_deploy>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-boot-maven-plugin.version>2.7.18</spring-boot-maven-plugin.version>
<micrometer-core.version>1.12.4</micrometer-core.version>
<micrometer-core.version>1.12.2</micrometer-core.version>
</properties>
<dependencyManagement>

View File

@ -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

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo-xds</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-demo-xds-consumer</artifactId>
<properties>
<skip_maven_deploy>true</skip_maven_deploy>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo-xds-interface</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-configcenter-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-report-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-config-spring</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-fastjson2</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Observability -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-tracing-otel-zipkin-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-qos</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo-xds</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-demo-xds-interface</artifactId>
<properties>
<skip_maven_deploy>true</skip_maven_deploy>
</properties>
</project>

View File

@ -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);
}

View File

@ -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

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo-xds</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-demo-xds-provider</artifactId>
<properties>
<skip_maven_deploy>true</skip_maven_deploy>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo-xds-interface</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-configcenter-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-report-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-config-spring</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-fastjson2</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Observability -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-tracing-otel-zipkin-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-qos</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-demo</artifactId>
<version>${revision}</version>
</parent>
<artifactId>dubbo-demo-xds</artifactId>
<packaging>pom</packaging>
<modules>
<module>dubbo-demo-xds-interface</module>
<module>dubbo-demo-xds-consumer</module>
<module>dubbo-demo-xds-provider</module>
</modules>
<properties>
<skip_maven_deploy>true</skip_maven_deploy>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-boot-maven-plugin.version>2.7.18</spring-boot-maven-plugin.version>
<micrometer-core.version>1.12.4</micrometer-core.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>${micrometer-core.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@ -37,6 +37,7 @@
<module>dubbo-demo-triple</module>
<module>dubbo-demo-native</module>
<module>dubbo-demo-spring-boot</module>
<module>dubbo-demo-xds</module>
</modules>
<properties>

View File

@ -1032,6 +1032,46 @@
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.bootstrap.XdsCertificateSigner</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.listener.LdsListener</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.listener.CdsListener</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.CertSource</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.RequestAuthorizer</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.ServiceIdentitySource</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.api.TrustSource</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleProvider</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.rule.source.RuleFactory</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.xds.security.authz.resolver.CredentialResolver</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.api.ChannelContextListener</resource>
</transformer>
</transformers>
<filters>
<filter>

View File

@ -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<DubboCertManager> reference = new AtomicReference<>();

View File

@ -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);
}

View File

@ -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<String, Channel> dubboChannels;
private final URL url;
private final ChannelHandler handler;
public NettyChannelHandler(Map<String, Channel> dubboChannels, URL url, ChannelHandler handler) {
private final List<ChannelContextListener> contextListeners;
public NettyChannelHandler(
Map<String, Channel> dubboChannels,
URL url,
ChannelHandler handler,
List<ChannelContextListener> 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);
}
});
}
}
}

View File

@ -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<String, Channel> dubboChannels = new ConcurrentHashMap<>();
private final List<ChannelContextListener> 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,

View File

@ -177,7 +177,7 @@ public class NettyServer extends AbstractServer {
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<SocketChannel>() {
@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()));

View File

@ -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.");
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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.
*/

View File

@ -74,6 +74,8 @@ public interface Constants {
String TOKEN_KEY = "token";
String ID_TOKEN_KEY = "identity.token";
String INTERFACE = "interface";
String INTERFACES = "interfaces";

View File

@ -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);

View File

@ -27,8 +27,10 @@
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>The xds module of dubbo project</description>
<properties>
<skip_maven_deploy>false</skip_maven_deploy>
<protobuf-java_version>3.25.1</protobuf-java_version>
</properties>
<dependencies>
<dependency>
@ -86,12 +88,7 @@
<artifactId>log4j-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>1.61.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
@ -141,6 +138,97 @@
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>10.0.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.18.0</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-config-api</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${maven_protobuf_plugin_version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf-java_version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc_version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${maven_os_plugin_version}</version>
</extension>
</extensions>
</build>
</project>

View File

@ -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<DiscoveryRequest> requestObserver;
private CompletableFuture<String> future = new CompletableFuture<>();
private final Map<String, DiscoveryRequest> 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 {
// TODOThis 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<DiscoveryResponse> {
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 {

View File

@ -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<String, Value> 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();

View File

@ -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<List<XdsRouteConfiguration>> 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<List<XdsCluster>> edsCallback = (xdsClusters) -> {
XdsResourceListener<XdsRouteConfiguration> 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<ClusterLoadAssignment> pilotEdsListener = clusterLoadAssignments -> {
List<XdsCluster> 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 callbacklisten to all rds resources in the callback function
Consumer<Set<String>> ldsCallback = rdsProtocol::subscribeResource;
ldsProtocol.setUpdateCallback(ldsCallback);
ldsProtocol.subscribeListeners();
this.ldsProtocol.registerListen(rdsProtocol.getLdsListener());
this.cdsProtocol.registerListen(edsProtocol.getCdsListener());
// cds resources callbacklisten to all cds resources in the callback function
Consumer<Set<String>> cdsCallback = edsProtocol::subscribeResource;
cdsProtocol.setUpdateCallback(cdsCallback);
cdsProtocol.subscribeClusters();
this.cdsProtocol.subscribeClusters();
this.ldsProtocol.subscribeListeners();
}
public static Map<String, XdsVirtualHost> 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<XdsEndpoint> 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;
}
}

View File

@ -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))) {
// TODONeed 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(

View File

@ -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
}
}

View File

@ -31,4 +31,8 @@ public class XdsCluster extends AbstractCluster {
XdsDirectory<T> xdsDirectory = new XdsDirectory<>(directory);
return new XdsClusterInvoker<>(xdsDirectory);
}
public boolean isAvailable() {
return true;
}
}

View File

@ -38,23 +38,30 @@ public class XdsClusterInvoker<T> extends AbstractClusterInvoker<T> {
@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
Invoker<T> 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<T> 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;
}
}

View File

@ -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<RegistryConfig> 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) {}
}

View File

@ -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<T> extends AbstractDirectory<T> {
private final String protocolName;
PilotExchanger pilotExchanger = PilotExchanger.getInstance();
PilotExchanger pilotExchanger;
private Protocol protocol;
@ -58,14 +60,18 @@ public class XdsDirectory<T> extends AbstractDirectory<T> {
private final Map<String, XdsCluster<T>> xdsClusterMap = new ConcurrentHashMap<>();
private static ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsDirectory.class);
public XdsDirectory(Directory<T> 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<T> extends AbstractDirectory<T> {
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<T> 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;
}
}

View File

@ -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";

View File

@ -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);
}
}
// TODOSubsequent implementation in the security section
return "null";
return null;
}
public String getServiceAccountJwt() {
return serviceAccountJwt;
}
public String getCsrHost() {
// spiffe://<trust_domain>/ns/<namespace>/sa/<service_account>
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;
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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<String, Object> getResourceAsMap(String apiGroup, String version, String namespace, String plural) {
CustomObjectsApi apiInstance = new CustomObjectsApi();
try {
return (Map<String, Object>) 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<Object> 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<Response<Object>>() {}.getType());
} catch (ApiException apiException) {
throw new RuntimeException("Failed to listen resource from ApiServer.", apiException);
}
}
}

View File

@ -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;
}
}

View File

@ -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<Cluster> {}

View File

@ -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<Listener> listeners) {
if (CollectionUtils.isEmpty(listeners)) {
return;
}
Map<String, DownstreamTlsConfig> 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<FilterChain> 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<Integer, TlsType> 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;
}
}
}

View File

@ -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<Listener> {}

View File

@ -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";
}

View File

@ -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<Cluster> resource) {
Map<String, UpstreamTlsConfig> 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());
}
}
}
}

View File

@ -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<String, DownstreamTlsConfig> downstreamConfigs = Collections.emptyMap();
/**
* clusterName -> configs
* Indicates the TLS configuration for outbound connection to certain cluster.
*/
private volatile Map<String, UpstreamTlsConfig> upstreamConfigs = Collections.emptyMap();
public void updateInbound(Map<String, DownstreamTlsConfig> downstreamType) {
this.downstreamConfigs = downstreamType;
}
public void updateOutbound(Map<String, UpstreamTlsConfig> upstreamType) {
this.upstreamConfigs = upstreamType;
}
public DownstreamTlsConfig getDownstreamConfig(String port) {
return downstreamConfigs.get(port);
}
public UpstreamTlsConfig getUpstreamConfig(String clusterName) {
return upstreamConfigs.get(clusterName);
}
}

View File

@ -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<T> implements XdsProtocol, XdsListener {
protected Map<String, T> resourcesMap = new ConcurrentHashMap<>();
public AbstractProtocol(AdsObserver adsObserver, Node node, int checkInterval) {
protected List<XdsResourceListener<T>> 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<T> listener) {
this.resourceListeners.add(listener);
}
/**
* Abstract method to obtain Type-URL from sub-class
*
@ -163,13 +174,18 @@ public abstract class AbstractProtocol<T> implements XdsProtocol, XdsListener {
.build();
}
// protected abstract Map<String, T> decodeDiscoveryResponse(DiscoveryResponse response);
protected abstract Map<String, T> decodeDiscoveryResponse(DiscoveryResponse response);
@Override
public final void process(DiscoveryResponse discoveryResponse) {
Map<String, T> newResult = decodeDiscoveryResponse(discoveryResponse);
// Map<String, T> newResult = decodeDiscoveryResponse(discoveryResponse);
Map<String, T> oldResource = resourcesMap;
// discoveryResponseListener(oldResource, newResult);
Map<String, T> newResult = decodeDiscoveryResponse(discoveryResponse);
resourceListeners.forEach(l -> l.onResourceUpdate(new ArrayList<>(newResult.values())));
resourcesMap = newResult;
}

View File

@ -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<T> {
void onResourceUpdate(List<T> resource);
}

View File

@ -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<String> {
public class CdsProtocol extends AbstractProtocol<Cluster> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(CdsProtocol.class);
public void setUpdateCallback(Consumer<Set<String>> updateCallback) {
@ -46,8 +54,11 @@ public class CdsProtocol extends AbstractProtocol<String> {
private Consumer<Set<String>> 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<CdsListener> ldsListeners =
applicationModel.getExtensionLoader(CdsListener.class).getActivateExtensions();
ldsListeners.forEach(this::registerListen);
}
@Override
@ -59,20 +70,63 @@ public class CdsProtocol extends AbstractProtocol<String> {
subscribeResource(null);
}
// @Override
// protected Map<String, String> decodeDiscoveryResponse(DiscoveryResponse response) {
// if (getTypeUrl().equals(response.getTypeUrl())) {
// Set<String> set = response.getResourcesList().stream()
// .map(CdsProtocol::unpackCluster)
// .filter(Objects::nonNull)
// .map(Cluster::getName)
// .collect(Collectors.toSet());
// updateCallback.accept(set);
// // Map<String, ListenerResult> listenerDecodeResult = new ConcurrentHashMap<>();
// // listenerDecodeResult.put(emptyResourceName, new ListenerResult(set));
// // return listenerDecodeResult;
// }
// return new HashMap<>();
// }
@Override
protected Map<String, String> decodeDiscoveryResponse(DiscoveryResponse response) {
protected Map<String, Cluster> decodeDiscoveryResponse(DiscoveryResponse response) {
if (getTypeUrl().equals(response.getTypeUrl())) {
Set<String> set = response.getResourcesList().stream()
return response.getResourcesList().stream()
.map(CdsProtocol::unpackCluster)
.filter(Objects::nonNull)
.map(Cluster::getName)
.collect(Collectors.toSet());
updateCallback.accept(set);
// Map<String, ListenerResult> 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<XdsEndpoint> 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) {

View File

@ -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<String> {
public class EdsProtocol extends AbstractProtocol<ClusterLoadAssignment> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(EdsProtocol.class);
public void setUpdateCallback(Consumer<List<XdsCluster>> updateCallback) {
this.updateCallback = updateCallback;
}
private XdsResourceListener<Cluster> clusterListener = clusters -> {
Set<String> clusterNames = clusters.stream().map(Cluster::getName).collect(Collectors.toSet());
this.subscribeResource(clusterNames);
};
private Consumer<List<XdsCluster>> updateCallback;
public EdsProtocol(
AdsObserver adsObserver, Node node, int checkInterval, Consumer<List<XdsCluster>> 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<String> {
return "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment";
}
@Override
protected Map<String, String> decodeDiscoveryResponse(DiscoveryResponse response) {
List<XdsCluster> 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<Cluster> getCdsListener() {
return clusterListener;
}
public List<XdsCluster> parse(DiscoveryResponse response) {
@Override
protected Map<String, ClusterLoadAssignment> 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<XdsEndpoint> 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) {

View File

@ -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<XdsVirtualHost> {
public class LdsProtocol extends AbstractProtocol<Listener> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(LdsProtocol.class);
public void setUpdateCallback(Consumer<Set<String>> updateCallback) {
this.updateCallback = updateCallback;
}
private Consumer<Set<String>> 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<LdsListener> ldsListeners =
applicationModel.getExtensionLoader(LdsListener.class).getActivateExtensions();
ldsListeners.forEach(this::registerListen);
}
@Override
@ -63,19 +63,14 @@ public class LdsProtocol extends AbstractProtocol<XdsVirtualHost> {
}
@Override
protected Map<String, XdsVirtualHost> decodeDiscoveryResponse(DiscoveryResponse response) {
protected Map<String, Listener> decodeDiscoveryResponse(DiscoveryResponse response) {
if (getTypeUrl().equals(response.getTypeUrl())) {
Set<String> 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<String, ListenerResult> 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<String> decodeResourceToListener(Listener resource) {

View File

@ -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<String> {
public class RdsProtocol extends AbstractProtocol<XdsRouteConfiguration> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RdsProtocol.class);
protected Consumer<List<XdsRouteConfiguration>> updateCallback;
public RdsProtocol(
AdsObserver adsObserver,
Node node,
int checkInterval,
Consumer<List<XdsRouteConfiguration>> 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<String> {
return "type.googleapis.com/envoy.config.route.v3.RouteConfiguration";
}
// @Override
// protected Map<String, String> decodeDiscoveryResponse(DiscoveryResponse response) {
// List<XdsRouteConfiguration> 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<String, String> decodeDiscoveryResponse(DiscoveryResponse response) {
List<XdsRouteConfiguration> 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<String, XdsRouteConfiguration> 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<XdsRouteConfiguration> parse(DiscoveryResponse response) {
@ -164,6 +176,40 @@ public class RdsProtocol extends AbstractProtocol<String> {
return xdsRouteAction;
}
public XdsResourceListener<Listener> getLdsListener() {
return ldsListener;
}
private final XdsResourceListener<Listener> ldsListener = resource -> {
Set<String> set = resource.stream()
.flatMap(e -> listenerToConnectionManagerNames(e).stream())
.collect(Collectors.toSet());
this.subscribeResource(set);
};
private Set<String> 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);

View File

@ -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);
// }
}
}

View File

@ -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<T> extends AbstractStateRouter<T> {
private final PilotExchanger pilotExchanger = PilotExchanger.getInstance();
private final PilotExchanger pilotExchanger;
private Map<String, XdsVirtualHost> xdsVirtualHostMap = new ConcurrentHashMap<>();
@ -47,6 +50,7 @@ public class XdsRouter<T> extends AbstractStateRouter<T> {
public XdsRouter(URL url) {
super(url);
pilotExchanger = url.getOrDefaultApplicationModel().getBeanFactory().getBean(PilotExchanger.class);
}
@Override
@ -60,8 +64,8 @@ public class XdsRouter<T> extends AbstractStateRouter<T> {
throws RpcException {
// return all invokers directly if xds is not used
// TODOneed 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<T> extends AbstractStateRouter<T> {
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<T> extends AbstractStateRouter<T> {
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;

View File

@ -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<X509Certificate> readPemX509CertificateChains(List<String> x590CertChains)
throws IOException, CertificateException {
List<String> 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<X509Certificate> readPemX509Certificates(List<String> x509Certs)
throws IOException, CertificateException {
List<X509Certificate> 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();
}
}

View File

@ -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<RequestAuthorizer> 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<String> 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);
}
}

View File

@ -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) {}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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> secretConfig);
}

View File

@ -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<String, DataSources> 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<String, DataSources> dataSource) {
return readActualValue(dataSource, null);
}
public static String readActualValue(Pair<String, DataSources> 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));
}
}

View File

@ -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<String, Pair<byte[], FileAlterationMonitor>> 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<byte[], FileAlterationMonitor> 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<byte[], FileAlterationMonitor> 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();
}
}

View File

@ -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<SecretConfig> secretConfigs) {
return selectSupportedConfig(
secretConfigs,
secretConfig -> ConfigType.CERT.equals(secretConfig.configType())
&& Source.LOCAL.equals(secretConfig.source()));
}
@Override
public SecretConfig selectSupportedTrustConfig(URL url, List<SecretConfig> 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> secretConfig, Predicate<SecretConfig> 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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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> secretConfig);
}

View File

@ -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<String> 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<X509Certificate> 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;
}
}

View File

@ -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> trustSource;
private final List<CertSource> 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<SecretConfig> trustConfigs =
upstreamConfig.getGeneralTlsConfig().trustConfigs();
List<SecretConfig> 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<SecretConfig> secretConfigs =
downstreamConfig.getGeneralTlsConfig().certConfigs();
List<SecretConfig> 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<SecretConfig> 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<SecretConfig> certConfigs) {
for (TrustSource trustSource : this.trustSource) {
SecretConfig secretConfig = trustSource.selectSupportedTrustConfig(address, certConfigs);
if (secretConfig != null) {
return trustSource.getTrustCerts(address, secretConfig);
}
}
return null;
}
}

View File

@ -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;
}
}

View File

@ -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<String, DataSources> certChain;
private final Pair<String, DataSources> privateKey;
private final Pair<String, DataSources> password;
private final Pair<String, DataSources> 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<String, DataSources> getCertChain() {
return certChain;
}
public Pair<String, DataSources> getPrivateKey() {
return privateKey;
}
public Pair<String, DataSources> getPassword() {
return password;
}
public Pair<String, DataSources> 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
}
}

View File

@ -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<SecretConfig> certConfigs;
private List<SecretConfig> trustConfigs;
/**
* L7 protocols
*/
private List<String> 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<SecretConfig> certConfigs, List<SecretConfig> trustConfigs, List<String> alpnProtocols) {
this.name = name;
this.certConfigs = certConfigs;
this.trustConfigs = trustConfigs;
this.alpnProtocols = alpnProtocols;
}
public String getName() {
return name;
}
public List<SecretConfig> certConfigs() {
return certConfigs;
}
public List<SecretConfig> trustConfigs() {
return trustConfigs;
}
public List<String> alpnProtocols() {
return alpnProtocols;
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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<SecretConfig> trustConfigs = new ArrayList<>();
List<SecretConfig> certConfigs = new ArrayList<>();
// sds cert sources
List<io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig> 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());
}
}

View File

@ -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;
}
}

View File

@ -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<String> 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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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> 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<String, List<RuleRoot>> 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<RuleRoot> roots = ruleFactory.getRules(invocation.getInvoker().getUrl(), rulesSources);
List<RuleRoot> logRules = roots.stream()
.filter(root -> root.getAction().equals(Action.LOG))
.collect(Collectors.toList());
roots.removeAll(logRules);
List<RuleRoot> andRules = roots.stream()
.filter(root -> Relation.AND.equals(root.getRelation()))
.collect(Collectors.toList());
List<RuleRoot> orRules = roots.stream()
.filter(root -> Relation.OR.equals(root.getRelation()))
.collect(Collectors.toList());
List<RuleRoot> 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.");
}
}

View File

@ -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<String, ConnectionCredential> 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<CertificateCredential> 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<CertificateCredential> certificateCredentials;
private final String applicationProtocol;
private final String sni;
public ConnectionCredential(
List<CertificateCredential> certificateCredentials, String applicationProtocol, String sni) {
this.certificateCredentials = certificateCredentials;
this.applicationProtocol = applicationProtocol;
this.sni = sni;
}
public List<CertificateCredential> 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<SANType, List<Object>> subjectAltNames;
private final Date certNotBefore;
private final Date certNotAfter;
private final String signatureAlgorithmName;
private final String publicKeyAlgorithmName;
private final Set<String> criticalExtensionOIDs;
private final List<String> 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<SANType, List<Object>> extractDetailedFields(X509Certificate cert) throws Exception {
Collection<List<?>> subjectAltNames = cert.getSubjectAlternativeNames();
if (subjectAltNames != null) {
Map<SANType, List<Object>> 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<SANType, List<Object>> getSubjectAltNames() {
return subjectAltNames;
}
public List<String> getExtendedKeyUsage() {
return extendedKeyUsage;
}
public Set<String> 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);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More