[WIP] [OSPP] [2.2.x] mtls support for 2.2.x branch (#3466)
* feat: Add mtls related supportpull/3449/head^2
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: '1.0'
|
||||
description: Spring Cloud Alibaba Mtls Example
|
||||
name: mtls-example
|
||||
version: 1.0.0
|
@ -0,0 +1,11 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: mtls-mvc
|
||||
name: mtls-mvc-env
|
||||
data:
|
||||
NAMESPACE_NAME: {{ .Release.Namespace | quote }}
|
||||
NACOS_SERVER_ADDR: {{ .Values.nacosServerAddr | quote }}
|
||||
USE_AGENT: {{ .Values.useAgent | quote }}
|
||||
|
@ -0,0 +1,49 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
account: sa-mtls-mvc
|
||||
name: sa-mtls-mvc
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mtls-mvc
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mtls-mvc
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
inject.istio.io/templates: grpc-agent
|
||||
labels:
|
||||
appName: mtls-mvc
|
||||
app: mtls-mvc
|
||||
spec:
|
||||
serviceAccountName: sa-mtls-mvc
|
||||
containers:
|
||||
- name: mtls-mvc
|
||||
image: '{{ .Values.image.mtlsMvc.repository }}:{{ .Values.image.mtlsMvc.tag }}'
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http-port
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mtls-mvc-env
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mtls-mvc
|
||||
labels:
|
||||
app: mtls-mvc
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http-server
|
||||
selector:
|
||||
app: mtls-mvc
|
@ -0,0 +1,11 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: mtls-openfeign
|
||||
name: mtls-openfeign-env
|
||||
data:
|
||||
NAMESPACE_NAME: {{ .Release.Namespace | quote }}
|
||||
NACOS_SERVER_ADDR: {{ .Values.nacosServerAddr | quote }}
|
||||
USE_AGENT: {{ .Values.useAgent | quote }}
|
||||
|
@ -0,0 +1,49 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
account: sa-mtls-openfeign
|
||||
name: sa-mtls-openfeign
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mtls-openfeign
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mtls-openfeign
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
inject.istio.io/templates: grpc-agent
|
||||
labels:
|
||||
appName: mtls-openfeign
|
||||
app: mtls-openfeign
|
||||
spec:
|
||||
serviceAccountName: sa-mtls-openfeign
|
||||
containers:
|
||||
- name: mtls-openfeign
|
||||
image: '{{ .Values.image.mtlsOpenfeign.repository }}:{{ .Values.image.mtlsOpenfeign.tag }}'
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http-port
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mtls-openfeign-env
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mtls-openfeign
|
||||
labels:
|
||||
app: mtls-openfeign
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http-server
|
||||
selector:
|
||||
app: mtls-openfeign
|
@ -0,0 +1,11 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: mtls-rest
|
||||
name: mtls-rest-env
|
||||
data:
|
||||
NAMESPACE_NAME: {{ .Release.Namespace | quote }}
|
||||
NACOS_SERVER_ADDR: {{ .Values.nacosServerAddr | quote }}
|
||||
USE_AGENT: {{ .Values.useAgent | quote }}
|
||||
|
@ -0,0 +1,49 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
account: sa-mtls-test
|
||||
name: sa-mtls-test
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mtls-rest
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mtls-rest
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
inject.istio.io/templates: grpc-agent
|
||||
labels:
|
||||
appName: mtls-rest
|
||||
app: mtls-rest
|
||||
spec:
|
||||
serviceAccountName: sa-mtls-test
|
||||
containers:
|
||||
- name: mtls-rest
|
||||
image: '{{ .Values.image.mtlsRest.repository }}:{{ .Values.image.mtlsRest.tag }}'
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http-port
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mtls-rest-env
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mtls-rest
|
||||
labels:
|
||||
app: mtls-rest
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http-server
|
||||
selector:
|
||||
app: mtls-rest
|
@ -0,0 +1,10 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: mtls-webclient
|
||||
name: mtls-webclient-env
|
||||
data:
|
||||
NAMESPACE_NAME: {{ .Release.Namespace | quote }}
|
||||
NACOS_SERVER_ADDR: {{ .Values.nacosServerAddr | quote }}
|
||||
USE_AGENT: {{ .Values.useAgent | quote }}
|
@ -0,0 +1,49 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
account: sa-mtls-webclient
|
||||
name: sa-mtls-webclient
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mtls-webclient
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mtls-webclient
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
inject.istio.io/templates: grpc-agent
|
||||
labels:
|
||||
appName: mtls-webclient
|
||||
app: mtls-webclient
|
||||
spec:
|
||||
serviceAccountName: sa-mtls-webclient
|
||||
containers:
|
||||
- name: mtls-webclient
|
||||
image: '{{ .Values.image.mtlsWebClient.repository }}:{{ .Values.image.mtlsWebClient.tag }}'
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http-port
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mtls-webclient-env
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mtls-webclient
|
||||
labels:
|
||||
app: mtls-webclient
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http-server
|
||||
selector:
|
||||
app: mtls-webclient
|
@ -0,0 +1,10 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: mtls-webflux
|
||||
name: mtls-webflux-env
|
||||
data:
|
||||
NAMESPACE_NAME: {{ .Release.Namespace | quote }}
|
||||
NACOS_SERVER_ADDR: {{ .Values.nacosServerAddr | quote }}
|
||||
USE_AGENT: {{ .Values.useAgent | quote }}
|
@ -0,0 +1,49 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
account: sa-mtls-webflux
|
||||
name: sa-mtls-webflux
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mtls-webflux
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mtls-webflux
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
inject.istio.io/templates: grpc-agent
|
||||
labels:
|
||||
appName: mtls-webflux
|
||||
app: mtls-webflux
|
||||
spec:
|
||||
serviceAccountName: sa-mtls-webflux
|
||||
containers:
|
||||
- name: mtls-webflux
|
||||
image: '{{ .Values.image.mtlsWebflux.repository }}:{{ .Values.image.mtlsWebflux.tag }}'
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http-port
|
||||
containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mtls-webflux-env
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mtls-webflux
|
||||
labels:
|
||||
app: mtls-webflux
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
name: http-server
|
||||
selector:
|
||||
app: mtls-webflux
|
@ -0,0 +1,11 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: nacos-server
|
||||
name: nacos-server-env
|
||||
data:
|
||||
NAMESPACE_NAME: {{ .Release.Namespace | quote }}
|
||||
ISTIOD_ADDR: {{ .Values.istiodAddr | quote }}
|
||||
POLLING_TIME: {{ .Values.defaultPollingTime | quote }}
|
||||
MODE: "standalone"
|
@ -0,0 +1,47 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nacos-server
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nacos-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
appName: nacos-server
|
||||
app: nacos-server
|
||||
spec:
|
||||
containers:
|
||||
- name: nacos-server
|
||||
image: '{{ .Values.image.nacosServer.repository }}:{{ .Values.image.nacosServer.tag }}'
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8848
|
||||
name: "nacos-8848"
|
||||
- containerPort: 9848
|
||||
name: "nacos-9848"
|
||||
- containerPort: 9849
|
||||
name: "nacos-9849"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: nacos-server-env
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nacos-server
|
||||
labels:
|
||||
app: nacos-server
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8848
|
||||
name: "nacos-8848"
|
||||
- port: 9848
|
||||
name: "nacos-9848"
|
||||
- port: 9849
|
||||
name: "nacos-9849"
|
||||
selector:
|
||||
app: nacos-server
|
@ -0,0 +1,21 @@
|
||||
image:
|
||||
mtlsMvc:
|
||||
repository: registry.cn-shenzhen.aliyuncs.com/sca-demo/mtls-mvc-example
|
||||
tag: latest
|
||||
mtlsOpenfeign:
|
||||
repository: registry.cn-shenzhen.aliyuncs.com/sca-demo/mtls-openfeign-example
|
||||
tag: latest
|
||||
mtlsRest:
|
||||
repository: registry.cn-shenzhen.aliyuncs.com/sca-demo/mtls-resttemplate-example
|
||||
tag: latest
|
||||
mtlsWebClient:
|
||||
repository: registry.cn-shenzhen.aliyuncs.com/sca-demo/mtls-webclient-example
|
||||
tag: latest
|
||||
mtlsWebflux:
|
||||
repository: registry.cn-shenzhen.aliyuncs.com/sca-demo/mtls-webflux-example
|
||||
tag: latest
|
||||
nacosServer:
|
||||
repository: nacos/nacos-server
|
||||
tag: v2.1.0
|
||||
nacosServerAddr: nacos-server:8848
|
||||
useAgent: true
|
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-examples</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mtls-openfeign-example</artifactId>
|
||||
<name>Spring Cloud Starter Alibaba Mtls Example - OpenFeign</name>
|
||||
<description>Example demonstrating how to use mtls on openfeign</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
public class MtlsOpenfeignApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MtlsOpenfeignApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.alibaba.cloud.examples.feignclient.MvcClient;
|
||||
import com.alibaba.cloud.examples.feignclient.WebfluxClient;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class OpenfeignTestController {
|
||||
|
||||
@Resource
|
||||
private MvcClient mvcClient;
|
||||
|
||||
@Resource
|
||||
private WebfluxClient webfluxClient;
|
||||
|
||||
@GetMapping("/openfeign/getMvc")
|
||||
public String getB(HttpServletRequest httpServletRequest) {
|
||||
return mvcClient.getMvc();
|
||||
}
|
||||
|
||||
@GetMapping("/openfeign/getWebflux")
|
||||
public String getC(HttpServletRequest httpServletRequest) {
|
||||
return webfluxClient.getWebflux();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples.config;
|
||||
|
||||
import com.alibaba.cloud.mtls.client.MtlsClientSSLContext;
|
||||
import feign.Client;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class FeignClientConfiguration {
|
||||
|
||||
@Autowired
|
||||
MtlsClientSSLContext mtlsClientSSLContext;
|
||||
|
||||
@Bean
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory lbClientFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
return new LoadBalancerFeignClient(
|
||||
new feign.okhttp.OkHttpClient(new OkHttpClient.Builder()
|
||||
.sslSocketFactory(mtlsClientSSLContext.getSslSocketFactory(),
|
||||
mtlsClientSSLContext.getTrustManager().orElse(null))
|
||||
.hostnameVerifier(mtlsClientSSLContext.getHostnameVerifier())
|
||||
.build()),
|
||||
lbClientFactory, clientFactory);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples.feignclient;
|
||||
|
||||
import com.alibaba.cloud.examples.config.FeignClientConfiguration;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@FeignClient(value = "https://mtls-mvc-example",
|
||||
configuration = FeignClientConfiguration.class)
|
||||
public interface MvcClient {
|
||||
|
||||
@GetMapping("/mvc/get")
|
||||
String getMvc();
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples.feignclient;
|
||||
|
||||
import com.alibaba.cloud.examples.config.FeignClientConfiguration;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@FeignClient(value = "https://mtls-webflux-example",
|
||||
configuration = FeignClientConfiguration.class)
|
||||
public interface WebfluxClient {
|
||||
|
||||
@GetMapping("/webflux/get")
|
||||
String getWebflux();
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
server:
|
||||
port: ${SERVER_PORT:8444}
|
||||
spring:
|
||||
cloud:
|
||||
mtls:
|
||||
config:
|
||||
enabled: ${MTLS_ENABLE:true}
|
||||
server-tls: ${SERVER_TLS:true}
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
enabled: true
|
||||
governance:
|
||||
auth:
|
||||
enabled: ${ISTIO_AUTH_ENABLE:true}
|
||||
istio:
|
||||
config:
|
||||
enabled: ${ISTIO_CONFIG_ENABLE:true}
|
||||
use-agent: ${USE_AGENT:true}
|
||||
application:
|
||||
name: mtls-openfeign-example
|
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-examples</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mtls-resttemplate-example</artifactId>
|
||||
<name>Spring Cloud Starter Alibaba Mtls Example - RestTemplate</name>
|
||||
<description>Example demonstrating how to use mtls on resttemplate</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MtlsResttemplateApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MtlsResttemplateApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RestController
|
||||
public class ResttemplateTestController {
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@GetMapping("/resttemplate/getMvc")
|
||||
public String getMvc(HttpServletRequest httpServletRequest) {
|
||||
return restTemplate.getForObject("https://mtls-mvc-example/mvc/get",
|
||||
String.class);
|
||||
}
|
||||
|
||||
@GetMapping("/resttemplate/getWebflux")
|
||||
public String getWebflux(HttpServletRequest httpServletRequest) {
|
||||
return restTemplate.getForObject("https://mtls-webflux-example/webflux/get",
|
||||
String.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples.config;
|
||||
|
||||
import com.alibaba.cloud.mtls.client.MtlsClientSSLContext;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Autowired
|
||||
MtlsClientSSLContext mtlsClientSSLContext;
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate
|
||||
.setRequestFactory(new HttpComponentsClientHttpRequestFactory(
|
||||
HttpClientBuilder.create()
|
||||
.setSSLContext(mtlsClientSSLContext.getSslContext())
|
||||
.setSSLHostnameVerifier(
|
||||
mtlsClientSSLContext.getHostnameVerifier())
|
||||
.build()));
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
server:
|
||||
port: ${SERVER_PORT:8111}
|
||||
spring:
|
||||
cloud:
|
||||
mtls:
|
||||
config:
|
||||
enabled: ${MTLS_ENABLE:true}
|
||||
server-tls: ${SERVER_TLS:true}
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
enabled: true
|
||||
governance:
|
||||
auth:
|
||||
enabled: ${ISTIO_AUTH_ENABLE:true}
|
||||
istio:
|
||||
config:
|
||||
enabled: ${ISTIO_CONFIG_ENABLE:true}
|
||||
use-agent: ${USE_AGENT:true}
|
||||
application:
|
||||
name: mtls-resttemplate-example
|
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-examples</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mtls-webclient-example</artifactId>
|
||||
<name>Spring Cloud Starter Alibaba Mtls Example - WebClient</name>
|
||||
<description>Example demonstrating how to use mtls on webclient</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-boot-starter-netflix-ribbon</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import com.alibaba.cloud.mtls.client.MtlsClientSSLContext;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MtlsWebclientApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MtlsWebclientApplication.class, args);
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class Controller {
|
||||
|
||||
@Autowired
|
||||
MtlsClientSSLContext mtlsClientSSLContext;
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder builder;
|
||||
|
||||
@GetMapping("/webclient/getMvc")
|
||||
public Mono<String> getMvc(ServerWebExchange serverWebExchange) {
|
||||
return builder.build().get().uri("https://mtls-mvc-example/mvc/get")
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
|
||||
@GetMapping("/webclient/getWebflux")
|
||||
public Mono<String> getWebflux(ServerWebExchange serverWebExchange) {
|
||||
return builder.build().get().uri("https://mtls-webflux-example/webflux/get")
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples.config;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
|
||||
import com.alibaba.cloud.mtls.client.MtlsClientSSLContext;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@Configuration
|
||||
public class WebClientConfig {
|
||||
|
||||
@Autowired
|
||||
MtlsClientSSLContext mtlsClientSSLContext;
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
public WebClient.Builder webClient() {
|
||||
SslContext nettySslContext = null;
|
||||
try {
|
||||
nettySslContext = SslContextBuilder.forClient()
|
||||
.trustManager(
|
||||
mtlsClientSSLContext.getTrustManagerFactory().orElse(null))
|
||||
.keyManager(mtlsClientSSLContext.getKeyManagerFactory().orElse(null))
|
||||
.build();
|
||||
}
|
||||
catch (SSLException e) {
|
||||
throw new RuntimeException("Error setting SSL context for WebClient", e);
|
||||
}
|
||||
|
||||
SslContext finalNettySslContext = nettySslContext;
|
||||
|
||||
HttpClient httpClient = HttpClient.create().secure(spec -> spec
|
||||
.sslContext(finalNettySslContext).handlerConfigurator(sslHandler -> {
|
||||
SSLEngine engine = sslHandler.engine();
|
||||
SSLParameters sslParameters = engine.getSSLParameters();
|
||||
sslParameters.setEndpointIdentificationAlgorithm("");
|
||||
engine.setSSLParameters(sslParameters);
|
||||
}));
|
||||
|
||||
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
|
||||
return WebClient.builder().clientConnector(connector);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
server:
|
||||
port: ${SERVER_PORT:8555}
|
||||
spring:
|
||||
cloud:
|
||||
mtls:
|
||||
config:
|
||||
enabled: ${MTLS_ENABLE:true}
|
||||
server-tls: ${SERVER_TLS:true}
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
enabled: true
|
||||
governance:
|
||||
auth:
|
||||
enabled: ${ISTIO_AUTH_ENABLE:true}
|
||||
istio:
|
||||
config:
|
||||
enabled: ${ISTIO_CONFIG_ENABLE:true}
|
||||
use-agent: ${USE_AGENT:true}
|
||||
application:
|
||||
name: mtls-webclient-example
|
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-examples</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mtls-mvc-example</artifactId>
|
||||
<name>Spring Cloud Starter Alibaba Mtls Example - MVC</name>
|
||||
<description>Example demonstrating how to use mtls on tomcat server</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MtlsMvcApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MtlsMvcApplication.class, args);
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class Controller {
|
||||
|
||||
@GetMapping("/mvc/get")
|
||||
public String get(HttpServletRequest httpServletRequest) {
|
||||
X509Certificate[] certs = (X509Certificate[]) httpServletRequest
|
||||
.getAttribute("javax.servlet.request.X509Certificate");
|
||||
if (certs != null) {
|
||||
for (int i = 0; i < certs.length; i++) {
|
||||
System.out.println(
|
||||
"client certificate_" + i + ": \n" + certs[i].toString());
|
||||
}
|
||||
}
|
||||
return "mvc-server received request from client";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
server:
|
||||
port: ${SERVER_PORT:8222}
|
||||
spring:
|
||||
cloud:
|
||||
mtls:
|
||||
config:
|
||||
enabled: ${MTLS_ENABLE:true}
|
||||
server-tls: ${SERVER_TLS:true}
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
enabled: true
|
||||
governance:
|
||||
auth:
|
||||
enabled: ${ISTIO_AUTH_ENABLE:true}
|
||||
istio:
|
||||
config:
|
||||
enabled: ${ISTIO_CONFIG_ENABLE:true}
|
||||
use-agent: ${USE_AGENT:true}
|
||||
application:
|
||||
name: mtls-mvc-example
|
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-examples</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mtls-webflux-example</artifactId>
|
||||
<name>Spring Cloud Starter Alibaba Mtls Example - WebFlux</name>
|
||||
<description>Example demonstrating how to use mtls on netty server</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.examples;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MtlsWebfluxApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MtlsWebfluxApplication.class, args);
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class WebFluxController {
|
||||
|
||||
@GetMapping("/webflux/get")
|
||||
public Mono<String> get(ServerWebExchange exchange) {
|
||||
X509Certificate[] certs = exchange.getRequest().getSslInfo()
|
||||
.getPeerCertificates();
|
||||
if (certs != null) {
|
||||
for (int i = 0; i < certs.length; i++) {
|
||||
System.out.println(
|
||||
"client certificate_" + i + ": \n" + certs[i].toString());
|
||||
}
|
||||
}
|
||||
return Mono.just("webflux-server received request from client");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
server:
|
||||
port: ${SERVER_PORT:8333}
|
||||
spring:
|
||||
cloud:
|
||||
mtls:
|
||||
config:
|
||||
enabled: ${MTLS_ENABLE:true}
|
||||
server-tls: ${SERVER_TLS:true}
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
enabled: true
|
||||
governance:
|
||||
auth:
|
||||
enabled: ${ISTIO_AUTH_ENABLE:true}
|
||||
istio:
|
||||
config:
|
||||
enabled: ${ISTIO_CONFIG_ENABLE:true}
|
||||
use-agent: ${USE_AGENT:true}
|
||||
application:
|
||||
name: mtls-webflux-example
|
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 141 KiB |
After Width: | Height: | Size: 124 KiB |
@ -0,0 +1,325 @@
|
||||
# Mtls Example
|
||||
|
||||
## Project Instruction
|
||||
|
||||
This project demonstrates how to utilize Istio issued certificates to achieve mutual TLS capability between Spring Cloud Alibaba (hereinafter referred to as: SCA) applications. Currently for the server-side support for Spring MVC and Spring WebFlux application adaptation , and for feign, resttemplate and other client-side implementation , provides a hot update capability of the ssl context , the configuration can be automatically updated istio certificate.
|
||||
|
||||
## Preparation
|
||||
|
||||
### Install K8s
|
||||
|
||||
Please refer to [tools](https://kubernetes.io/docs/tasks/tools/) chapter of K8s document.
|
||||
|
||||
### Install Helm
|
||||
|
||||
Refer to the Helm [[Installation Guide]](https://helm.sh/docs/intro/install/).
|
||||
|
||||
### Enable Istio on K8s
|
||||
|
||||
Please refer to [install](https://istio.io/latest/docs/setup/install/) chapter of Istio document.
|
||||
|
||||
### Application Deployment
|
||||
|
||||
Deploy the sample project mtls-example in K8s with the following command:
|
||||
|
||||
```shell
|
||||
helm install mtls-demo .
|
||||
```
|
||||
|
||||
After successful deployment, view the pod information as follows:
|
||||
|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
### Connect to Istio
|
||||
|
||||
Before launching the example for demonstration, let's look at how a Spring Cloud application accesses Istio and provides authentication. This section is only for you to understand how to use it. The config has been filled in this example and you may not need to modify it.
|
||||
|
||||
1. Modify the `pom.xml` file to introduce the Istio rules adapter, the mtls module, the governance-auth module, and the actuator dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2. Refer to the [documentation](https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-docs/src/main/asciidoc/mtls.adoc) for the implementation of the interface with the ` Istio` control plane and Service Account configuration.
|
||||
|
||||
### Quick Start
|
||||
|
||||
#### View Certificates
|
||||
|
||||
After deploying the sample application to K8s and configuring the Service Account of the application, in the pod where the application resides, you can view the certificates issued for the current application with the following command:
|
||||
|
||||
```shell
|
||||
openssl s_client -connect 127.0.0.1:${port} </dev/null 2>/dev/null | openssl x509 -inform pem -text
|
||||
```
|
||||
|
||||
Taking the SpringMVC (Tomcat) server as an example, you can see the spiffe encoded ServiceAccount content to be used as the Subject Alternative Name:
|
||||
|
||||
```
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
6a:e4:29:15:41:94:75:67:3c:7f:85:26:ff:37:3d:55
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: O = cluster.local
|
||||
Validity
|
||||
Not Before: Oct 20 06:55:54 2023 GMT
|
||||
Not After : Oct 21 06:57:54 2023 GMT
|
||||
Subject:
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
RSA Public-Key: (2048 bit)
|
||||
Modulus:
|
||||
00:d3:a8:31:78:82:d3:fb:a4:01:2e:02:c3:70:86:
|
||||
fb:f4:e3:f8:42:fc:4e:ce:97:d0:5a:6a:b9:1b:eb:
|
||||
fd:2c:ad:16:4c:8a:12:a0:23:fe:ea:3c:7d:3f:3f:
|
||||
6b:b5:6c:c5:d3:fe:f5:09:68:b9:03:c7:1d:08:ef:
|
||||
96:e3:1a:60:b2:5e:9c:c6:ff:13:42:14:08:0c:fc:
|
||||
66:28:98:65:d2:49:ef:9f:d8:c3:be:70:12:12:24:
|
||||
d6:25:5d:99:eb:63:4e:19:51:13:c9:09:73:79:b8:
|
||||
ac:07:ee:a2:99:75:62:bb:f6:32:48:da:fb:69:32:
|
||||
cc:cf:f1:7e:ef:e8:dd:8f:88:b2:c3:cb:73:b0:ed:
|
||||
a6:60:e2:3c:e1:b2:7e:f4:9d:ad:1c:be:9d:ba:1b:
|
||||
1a:e4:0c:a8:a7:a6:ea:f5:f6:96:47:74:77:bd:fa:
|
||||
4a:5f:bd:05:9b:8b:e7:d5:49:27:22:30:5b:79:e2:
|
||||
62:a8:71:60:36:8a:a6:79:41:76:0a:af:db:8f:fb:
|
||||
e4:f3:fa:19:3c:bd:49:48:39:96:7f:16:24:0e:c6:
|
||||
0d:20:01:9c:70:32:cd:1d:92:32:e0:b3:72:bb:1a:
|
||||
78:ac:49:b2:ff:90:36:4d:d0:d0:e8:5c:02:05:7c:
|
||||
c7:b9:a8:99:72:0e:48:8c:f5:41:7d:ce:8d:92:73:
|
||||
2c:3b
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Key Usage: critical
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Extended Key Usage:
|
||||
TLS Web Server Authentication, TLS Web Client Authentication
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:FALSE
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:9D:4E:93:61:65:5B:47:8B:E5:B4:1D:6C:A7:06:9E:35:AD:9B:8A:0B
|
||||
|
||||
X509v3 Subject Alternative Name: critical
|
||||
URI:spiffe://cluster.local/ns/default/sa/mtls-resttemplate-example
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
6f:ad:f8:d2:e1:1e:45:99:63:9e:0a:6c:06:28:14:fb:7f:f6:
|
||||
6d:c2:4c:e6:2f:43:28:34:5e:a2:16:88:0d:1c:fb:80:d3:bd:
|
||||
72:7e:cc:63:4a:91:54:33:e9:0d:bb:bc:92:46:13:68:0a:9f:
|
||||
83:25:9c:0d:8a:cd:8f:41:d4:f3:c5:b6:48:39:04:da:9e:7a:
|
||||
51:00:3c:34:15:c5:20:c9:00:d1:ac:53:4e:3a:6c:68:a6:6c:
|
||||
8a:1d:c0:e6:13:06:45:56:13:d5:6e:72:e1:83:a8:34:2b:12:
|
||||
27:2e:0f:d4:9f:89:d2:8f:24:76:53:62:ff:3a:32:0a:c6:4a:
|
||||
ca:d1:dc:aa:39:93:dd:c6:66:c3:89:1d:46:69:2f:a8:7c:42:
|
||||
d5:d7:29:81:c3:d7:07:43:bd:aa:4d:04:52:5e:9e:15:fc:6b:
|
||||
97:e3:b0:4a:b6:3f:cf:c7:47:b8:41:8f:a1:81:c1:12:16:48:
|
||||
87:41:3f:fb:88:a2:ce:11:18:54:11:b9:5a:d2:1f:2f:93:dc:
|
||||
de:a9:d0:2f:7c:0d:be:1c:6f:3a:15:a3:16:d1:91:2e:c7:33:
|
||||
83:ed:84:25:18:1d:6c:7f:9b:4d:6d:da:06:8d:30:a6:7d:f9:
|
||||
8d:24:28:45:17:50:98:c9:e3:13:ae:44:f8:df:07:7b:f3:9a:
|
||||
d1:e0:7a:80
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUjCCAjqgAwIBAgIQauQpFUGUdWc8f4Um/zc9VTANBgkqhkiG9w0BAQsFADAY
|
||||
MRYwFAYDVQQKEw1jbHVzdGVyLmxvY2FsMB4XDTIzMTAyMDA2NTU1NFoXDTIzMTAy
|
||||
MTA2NTc1NFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOoMXiC
|
||||
0/ukAS4Cw3CG+/Tj+EL8Ts6X0FpquRvr/SytFkyKEqAj/uo8fT8/a7VsxdP+9Qlo
|
||||
uQPHHQjvluMaYLJenMb/E0IUCAz8ZiiYZdJJ75/Yw75wEhIk1iVdmetjThlRE8kJ
|
||||
c3m4rAfuopl1Yrv2Mkja+2kyzM/xfu/o3Y+IssPLc7DtpmDiPOGyfvSdrRy+nbob
|
||||
GuQMqKem6vX2lkd0d736Sl+9BZuL59VJJyIwW3niYqhxYDaKpnlBdgqv24/75PP6
|
||||
GTy9SUg5ln8WJA7GDSABnHAyzR2SMuCzcrsaeKxJsv+QNk3Q0OhcAgV8x7momXIO
|
||||
SIz1QX3OjZJzLDsCAwEAAaOBrzCBrDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw
|
||||
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU
|
||||
nU6TYWVbR4vltB1spwaeNa2bigswTAYDVR0RAQH/BEIwQIY+c3BpZmZlOi8vY2x1
|
||||
c3Rlci5sb2NhbC9ucy9kZWZhdWx0L3NhL210bHMtcmVzdHRlbXBsYXRlLWV4YW1w
|
||||
bGUwDQYJKoZIhvcNAQELBQADggEBAG+t+NLhHkWZY54KbAYoFPt/9m3CTOYvQyg0
|
||||
XqIWiA0c+4DTvXJ+zGNKkVQz6Q27vJJGE2gKn4MlnA2KzY9B1PPFtkg5BNqeelEA
|
||||
PDQVxSDJANGsU046bGimbIodwOYTBkVWE9VucuGDqDQrEicuD9SfidKPJHZTYv86
|
||||
MgrGSsrR3Ko5k93GZsOJHUZpL6h8QtXXKYHD1wdDvapNBFJenhX8a5fjsEq2P8/H
|
||||
R7hBj6GBwRIWSIdBP/uIos4RGFQRuVrSHy+T3N6p0C98Db4cbzoVoxbRkS7HM4Pt
|
||||
hCUYHWx/m01t2gaNMKZ9+Y0kKEUXUJjJ4xOuRPjfB3vzmtHgeoA=
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
#### Mutual TLS
|
||||
|
||||
The following RestTemplate client and SpringMVC (Tomcat) server as an example, to give a simple example of use.
|
||||
|
||||
Deploy the sample application to K8s and enter the RestTemplate application with the following command:
|
||||
|
||||
```shell
|
||||
kubectl exec -c istio-proxy -it ${resttemplate_pod_name} -- bash
|
||||
```
|
||||
|
||||
Use the following command within the container to send a request to the Tomcat server via the RestTemplate client:
|
||||
|
||||
```shell
|
||||
curl -k https://localhost:${port}/resttemplate/getMvc
|
||||
```
|
||||
|
||||
After a successful request, the SpringMVC (Tomcat) server will receive the certificate carried by the client. The certificate contains information about the identity of the application, i.e. ServiceAccount. The client certificate received can be found by viewing the logs of the pod where the server is located with the following command:
|
||||
|
||||
```shell
|
||||
kubectl logs ${springmvc_pod_name}
|
||||
```
|
||||
|
||||
The client certificate received in one of the requests is as follows. From the SubjectAlternativeName field in the certificate, you can identify that the certificate is from the application with ServiceAccount "mtls-resttemplate-example":
|
||||
|
||||
```
|
||||
[
|
||||
[
|
||||
Version: V3
|
||||
Subject:
|
||||
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
|
||||
|
||||
Key: Sun RSA public key, 2048 bits
|
||||
params: null
|
||||
modulus: 26719221528184068732015315264288604373679826539282478426097969053974382861354826542051876328932528175642713092529641146295102033253211316495853946817283249533389050199752927749843698539537721469963691881711082844075757454244576898594432695045339671702416660979527239503695271512833660067801224544638113738271334257178208382924689930111364189968004443796717959536203482853831898504813939931700174503652220712246767390007530274164485324125515933992826096083933791713331987686469400680968433332825899402171823122204857159285181103975487627104678111438273834092923367973802365694241887849829238593076455058952257637919803
|
||||
public exponent: 65537
|
||||
Validity: [From: Fri Oct 20 14:55:54 CST 2023,
|
||||
To: Sat Oct 21 14:57:54 CST 2023]
|
||||
Issuer: O=cluster.local
|
||||
SerialNumber: [ 6ae42915 41947567 3c7f8526 ff373d55]
|
||||
|
||||
Certificate Extensions: 5
|
||||
[1]: ObjectId: 2.5.29.35 Criticality=false
|
||||
AuthorityKeyIdentifier [
|
||||
KeyIdentifier [
|
||||
0000: 9D 4E 93 61 65 5B 47 8B E5 B4 1D 6C A7 06 9E 35 .N.ae[G....l...5
|
||||
0010: AD 9B 8A 0B ....
|
||||
]
|
||||
]
|
||||
|
||||
[2]: ObjectId: 2.5.29.19 Criticality=true
|
||||
BasicConstraints:[
|
||||
CA:false
|
||||
PathLen: undefined
|
||||
]
|
||||
|
||||
[3]: ObjectId: 2.5.29.37 Criticality=false
|
||||
ExtendedKeyUsages [
|
||||
serverAuth
|
||||
clientAuth
|
||||
]
|
||||
|
||||
[4]: ObjectId: 2.5.29.15 Criticality=true
|
||||
KeyUsage [
|
||||
DigitalSignature
|
||||
Key_Encipherment
|
||||
]
|
||||
|
||||
[5]: ObjectId: 2.5.29.17 Criticality=true
|
||||
SubjectAlternativeName [
|
||||
URIName: spiffe://cluster.local/ns/default/sa/mtls-resttemplate-example
|
||||
]
|
||||
|
||||
]
|
||||
Algorithm: [SHA256withRSA]
|
||||
Signature:
|
||||
0000: 6F AD F8 D2 E1 1E 45 99 63 9E 0A 6C 06 28 14 FB o.....E.c..l.(..
|
||||
0010: 7F F6 6D C2 4C E6 2F 43 28 34 5E A2 16 88 0D 1C ..m.L./C(4^.....
|
||||
0020: FB 80 D3 BD 72 7E CC 63 4A 91 54 33 E9 0D BB BC ....r..cJ.T3....
|
||||
0030: 92 46 13 68 0A 9F 83 25 9C 0D 8A CD 8F 41 D4 F3 .F.h...%.....A..
|
||||
0040: C5 B6 48 39 04 DA 9E 7A 51 00 3C 34 15 C5 20 C9 ..H9...zQ.<4.. .
|
||||
0050: 00 D1 AC 53 4E 3A 6C 68 A6 6C 8A 1D C0 E6 13 06 ...SN:lh.l......
|
||||
0060: 45 56 13 D5 6E 72 E1 83 A8 34 2B 12 27 2E 0F D4 EV..nr...4+.'...
|
||||
0070: 9F 89 D2 8F 24 76 53 62 FF 3A 32 0A C6 4A CA D1 ....$vSb.:2..J..
|
||||
0080: DC AA 39 93 DD C6 66 C3 89 1D 46 69 2F A8 7C 42 ..9...f...Fi/..B
|
||||
0090: D5 D7 29 81 C3 D7 07 43 BD AA 4D 04 52 5E 9E 15 ..)....C..M.R^..
|
||||
00A0: FC 6B 97 E3 B0 4A B6 3F CF C7 47 B8 41 8F A1 81 .k...J.?..G.A...
|
||||
00B0: C1 12 16 48 87 41 3F FB 88 A2 CE 11 18 54 11 B9 ...H.A?......T..
|
||||
00C0: 5A D2 1F 2F 93 DC DE A9 D0 2F 7C 0D BE 1C 6F 3A Z../...../....o:
|
||||
00D0: 15 A3 16 D1 91 2E C7 33 83 ED 84 25 18 1D 6C 7F .......3...%..l.
|
||||
00E0: 9B 4D 6D DA 06 8D 30 A6 7D F9 8D 24 28 45 17 50 .Mm...0....$(E.P
|
||||
00F0: 98 C9 E3 13 AE 44 F8 DF 07 7B F3 9A D1 E0 7A 80 .....D........z.
|
||||
|
||||
]
|
||||
```
|
||||
|
||||
#### Traffic Mode Switching
|
||||
|
||||
Dynamically switching the traffic mode to http can be achieved with the following command:
|
||||
|
||||
```shell
|
||||
curl -k 'https://localhost:${port}/actuator/sds' --header 'Content-Type: application/json' --data '{ "isTls":false }'
|
||||
```
|
||||
|
||||
Observing the application logs, you can see that the application was started in http mode:
|
||||
|
||||

|
||||
|
||||
Switch the traffic pattern to https:
|
||||
|
||||
```shell
|
||||
curl -k 'http://localhost:${port}/actuator/sds' --header 'Content-Type:
|
||||
application/json' --data '{ "isTls":true }'
|
||||
```
|
||||
|
||||
Observing the application logs, you can see that the application was started in https mode:
|
||||
|
||||

|
||||
|
||||
### AuthorizationPolicy principal
|
||||
|
||||
Take the example of an Openfeign client requesting a Webflux (Netty) server, and configure the following authorization policy for the application on the pod where the Webflux (Netty) application resides, i.e., allow access to the service with the service account "cluster.local/ns/default/sa/mtls-openfeign-example":
|
||||
|
||||
```yaml
|
||||
apiVersion: security.istio.io/v1beta1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: httpbin
|
||||
namespace: default
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
- from:
|
||||
- source:
|
||||
principals: ["cluster.local/ns/default/sa/mtls-openfeign-example"]
|
||||
```
|
||||
|
||||
Use the following command within the container to send a request to the Webflux (Netty) server via the Openfeign client:
|
||||
|
||||
```shell
|
||||
curl -k https://localhost:${port}/openfeign/getWebflux
|
||||
```
|
||||
|
||||
Server Response:
|
||||
|
||||
```
|
||||
webflux-server received request from client
|
||||
```
|
||||
|
||||
Update the authorization policy as follows to deny access from services with service account "cluster.local/ns/default/sa/mtls-openfeign-example":
|
||||
|
||||
```yaml
|
||||
apiVersion: security.istio.io/v1beta1
|
||||
kind: AuthorizationPolicy
|
||||
metadata:
|
||||
name: httpbin
|
||||
namespace: default
|
||||
spec:
|
||||
action: ALLOW
|
||||
rules:
|
||||
- from:
|
||||
- source:
|
||||
principals: ["cluster.local/ns/default/sa/mtls-openfeign-example"]
|
||||
```
|
||||
|
||||
Send the request to the Webflux (Netty) server again, the request fails, check the client application logs and you can see that the request is not authorized.
|
||||
|
||||

|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.commons.governance.tls;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class ServerTlsModeHolder {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ServerTlsModeHolder.class);
|
||||
|
||||
private static volatile Boolean isTls;
|
||||
|
||||
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
|
||||
|
||||
private ServerTlsModeHolder() {
|
||||
|
||||
}
|
||||
|
||||
public static void init(boolean initValue) {
|
||||
try {
|
||||
rwLock.writeLock().lock();
|
||||
if (isTls == null) {
|
||||
isTls = initValue;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
rwLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTlsMode(boolean isTls) {
|
||||
try {
|
||||
rwLock.writeLock().lock();
|
||||
ServerTlsModeHolder.isTls = isTls;
|
||||
}
|
||||
finally {
|
||||
rwLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean getTlsMode() {
|
||||
try {
|
||||
rwLock.readLock().lock();
|
||||
return isTls;
|
||||
}
|
||||
finally {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Verify whether the tls mode is initialized and also updated.
|
||||
public static boolean canModeUpdate(boolean isTls) {
|
||||
try {
|
||||
rwLock.readLock().lock();
|
||||
return ServerTlsModeHolder.isTls != null
|
||||
&& !Objects.equals(ServerTlsModeHolder.isTls, isTls);
|
||||
}
|
||||
finally {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.governance.auth.util;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class CertUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CertUtil.class);
|
||||
|
||||
private CertUtil() {
|
||||
|
||||
}
|
||||
|
||||
public static String getIstioIdentity(X509Certificate x509Certificate) {
|
||||
try {
|
||||
Collection<List<?>> san = x509Certificate.getSubjectAlternativeNames();
|
||||
return (String) san.iterator().next().get(1);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Failed to get istio SAN from X509Certificate", e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-starters</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-alibaba-governance-mtls</artifactId>
|
||||
<name>Spring Cloud Starter Alibaba Governance MTLS</name>
|
||||
|
||||
<properties>
|
||||
<okhttp-version>3.10.0</okhttp-version>
|
||||
<kickstart-version>8.1.5</kickstart-version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-xds-adapter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.hakky54</groupId>
|
||||
<artifactId>sslcontext-kickstart</artifactId>
|
||||
<version>${kickstart-version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.ControlPlaneInitedBean;
|
||||
import com.alibaba.cloud.commons.governance.tls.ServerTlsModeHolder;
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertUpdateCallback;
|
||||
import com.alibaba.cloud.mtls.client.MtlsClientSSLContext;
|
||||
import com.alibaba.cloud.mtls.server.ApplicationRestarter;
|
||||
import com.alibaba.cloud.mtls.server.netty.MtlsNettyServerCustomizer;
|
||||
import com.alibaba.cloud.mtls.server.tomcat.MtlsTomcatConnectCustomizer;
|
||||
import com.alibaba.cloud.nacos.registry.NacosRegistration;
|
||||
import com.alibaba.cloud.nacos.registry.NacosRegistrationCustomizer;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.netty.http.server.HttpServer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(value = "spring.cloud.mtls.config.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(MtlsConfigProperties.class)
|
||||
public class MtlsAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory
|
||||
.getLogger(MtlsAutoConfiguration.class);
|
||||
|
||||
@Autowired
|
||||
private MtlsConfigProperties mtlsConfigProperties;
|
||||
|
||||
@Bean
|
||||
public MtlsSslStoreProvider mtlsSslStoreProvider(AbstractCertManager certManager) {
|
||||
return new MtlsSslStoreProvider(certManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ApplicationRestarter applicationRestarter() {
|
||||
return new ApplicationRestarter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MtlsCertCallbackIniter mtlsClientCallbackIniter(
|
||||
AbstractCertManager certManager,
|
||||
@Autowired(required = false) List<CertUpdateCallback> callbacks) {
|
||||
return new MtlsCertCallbackIniter(certManager, callbacks);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MtlsClientSSLContext mtlsSSLContext(MtlsSslStoreProvider mtlsSslStoreProvider,
|
||||
AbstractCertManager abstractCertManager) {
|
||||
return new MtlsClientSSLContext(mtlsSslStoreProvider, abstractCertManager);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Tomcat.class })
|
||||
static class TomcatConnectionCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public TomcatConnectorCustomizer mtlsCustomizer(
|
||||
MtlsSslStoreProvider sslStoreProvider, AbstractCertManager certManager,
|
||||
ControlPlaneInitedBean controlPlaneInitedBean) {
|
||||
return new MtlsTomcatConnectCustomizer(sslStoreProvider, certManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ NacosRegistrationCustomizer.class, NacosRegistration.class })
|
||||
static class NacosCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public NacosRegistrationCustomizer nacosTlsCustomizer() {
|
||||
return registration -> {
|
||||
if (!ServerTlsModeHolder.getTlsMode()) {
|
||||
log.warn("Fetch tls mode failed, use plaintext to transport");
|
||||
return;
|
||||
}
|
||||
registration.getNacosDiscoveryProperties()
|
||||
.setSecure(ServerTlsModeHolder.getTlsMode());
|
||||
registration.getNacosDiscoveryProperties().getMetadata().put("secure",
|
||||
"true");
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(HttpServer.class)
|
||||
public class NettyCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public MtlsNettyServerCustomizer mtlsNettyCustomizer(
|
||||
AbstractCertManager certManager, MtlsSslStoreProvider sslStoreProvider) {
|
||||
return new MtlsNettyServerCustomizer(certManager, sslStoreProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertUpdateCallback;
|
||||
|
||||
class MtlsCertCallbackIniter {
|
||||
|
||||
MtlsCertCallbackIniter(AbstractCertManager certManager,
|
||||
List<CertUpdateCallback> callbacks) {
|
||||
if (callbacks == null || callbacks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (CertUpdateCallback callback : callbacks) {
|
||||
certManager.registerCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.tls.ServerTlsModeHolder;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author musi
|
||||
* @author <a href="liuziming@buaa.edu.cn"></a>
|
||||
* @since 2.2.10-RC1
|
||||
*/
|
||||
@ConfigurationProperties(MtlsConfigProperties.PREFIX)
|
||||
public class MtlsConfigProperties {
|
||||
|
||||
private Boolean serverTls;
|
||||
|
||||
/**
|
||||
* Prefix for mtls config.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.mtls.config";
|
||||
|
||||
public void setServerTls(boolean serverTls) {
|
||||
this.serverTls = serverTls;
|
||||
}
|
||||
|
||||
public boolean isServerTls() {
|
||||
return serverTls;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
if (serverTls == null) {
|
||||
serverTls = true;
|
||||
}
|
||||
ServerTlsModeHolder.init(serverTls);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertPair;
|
||||
import com.alibaba.cloud.mtls.constants.MtlsConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
|
||||
public class MtlsSslStoreProvider implements SslStoreProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MtlsSslStoreProvider.class);
|
||||
|
||||
private final AbstractCertManager certManager;
|
||||
|
||||
public MtlsSslStoreProvider(AbstractCertManager certManager) {
|
||||
this.certManager = certManager;
|
||||
}
|
||||
|
||||
public KeyStore getKeyStore(CertPair certPair) {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null, null);
|
||||
keyStore.setKeyEntry(MtlsConstants.MTLS_DEFAULT_KEY_STORE_ALIAS,
|
||||
certPair.getPrivateKey(), "".toCharArray(),
|
||||
certPair.getCertificateChain());
|
||||
return keyStore;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Unable to get key store", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() {
|
||||
CertPair certPair = certManager.getCertPair();
|
||||
return getKeyStore(certPair);
|
||||
}
|
||||
|
||||
public KeyStore getTrustStore(CertPair certPair) {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null, null);
|
||||
keyStore.setCertificateEntry(MtlsConstants.MTLS_DEFAULT_TRUST_STORE_ALIAS,
|
||||
certPair.getRootCA());
|
||||
return keyStore;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Unable to get trust store", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getTrustStore() {
|
||||
CertPair certPair = certManager.getCertPair();
|
||||
return getTrustStore(certPair);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.client;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import com.alibaba.cloud.governance.istio.sds.CertPair;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertUpdateCallback;
|
||||
import com.alibaba.cloud.mtls.MtlsSslStoreProvider;
|
||||
import nl.altindag.ssl.SSLFactory;
|
||||
import nl.altindag.ssl.util.SSLFactoryUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ClientCertUpdateCallback implements CertUpdateCallback {
|
||||
|
||||
private static final Logger log = LoggerFactory
|
||||
.getLogger(ClientCertUpdateCallback.class);
|
||||
|
||||
private MtlsSslStoreProvider mtlsSslStoreProvider;
|
||||
|
||||
private SSLFactory originFactory;
|
||||
|
||||
public ClientCertUpdateCallback(MtlsSslStoreProvider mtlsSslStoreProvider,
|
||||
SSLFactory originFactory) {
|
||||
this.mtlsSslStoreProvider = mtlsSslStoreProvider;
|
||||
this.originFactory = originFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onUpdateCert(CertPair certPair) {
|
||||
try {
|
||||
KeyStore keyStore = mtlsSslStoreProvider.getKeyStore();
|
||||
KeyStore trustStore = mtlsSslStoreProvider.getTrustStore();
|
||||
SSLFactory factory = SSLFactory.builder().withUnsafeHostnameVerifier()
|
||||
.withTrustMaterial(trustStore)
|
||||
.withIdentityMaterial(keyStore, "".toCharArray()).build();
|
||||
SSLFactoryUtils.reload(originFactory, factory);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
log.error("Failed to refresh x509KeyManager", t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.client;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.mtls.MtlsSslStoreProvider;
|
||||
import nl.altindag.ssl.SSLFactory;
|
||||
|
||||
public class MtlsClientSSLContext {
|
||||
|
||||
private SSLFactory sslFactory;
|
||||
|
||||
public MtlsClientSSLContext(MtlsSslStoreProvider mtlsSslStoreProvider,
|
||||
AbstractCertManager abstractCertManager) {
|
||||
if (sslFactory == null) {
|
||||
synchronized (MtlsClientSSLContext.class) {
|
||||
if (sslFactory == null) {
|
||||
KeyStore keyStore = mtlsSslStoreProvider.getKeyStore();
|
||||
// init trust store
|
||||
KeyStore trustStore = mtlsSslStoreProvider.getTrustStore();
|
||||
sslFactory = SSLFactory.builder().withUnsafeHostnameVerifier()
|
||||
.withSwappableIdentityMaterial().withSwappableTrustMaterial()
|
||||
.withIdentityMaterial(keyStore, "".toCharArray())
|
||||
.withTrustMaterial(trustStore).build();
|
||||
abstractCertManager.registerCallback(new ClientCertUpdateCallback(
|
||||
mtlsSslStoreProvider, sslFactory));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SSLContext getSslContext() {
|
||||
return sslFactory.getSslContext();
|
||||
}
|
||||
|
||||
public SSLSocketFactory getSslSocketFactory() {
|
||||
return sslFactory.getSslSocketFactory();
|
||||
}
|
||||
|
||||
public SSLServerSocketFactory getSslServerSocketFactory() {
|
||||
return sslFactory.getSslServerSocketFactory();
|
||||
}
|
||||
|
||||
public Optional<X509ExtendedKeyManager> getKeyManager() {
|
||||
return sslFactory.getKeyManager();
|
||||
}
|
||||
|
||||
public Optional<KeyManagerFactory> getKeyManagerFactory() {
|
||||
return sslFactory.getKeyManagerFactory();
|
||||
}
|
||||
|
||||
public Optional<X509ExtendedTrustManager> getTrustManager() {
|
||||
return sslFactory.getTrustManager();
|
||||
}
|
||||
|
||||
public Optional<TrustManagerFactory> getTrustManagerFactory() {
|
||||
return sslFactory.getTrustManagerFactory();
|
||||
}
|
||||
|
||||
public List<X509Certificate> getTrustedCertificates() {
|
||||
return sslFactory.getTrustedCertificates();
|
||||
}
|
||||
|
||||
public HostnameVerifier getHostnameVerifier() {
|
||||
return sslFactory.getHostnameVerifier();
|
||||
}
|
||||
|
||||
public List<String> getCiphers() {
|
||||
return sslFactory.getCiphers();
|
||||
}
|
||||
|
||||
public List<String> getProtocols() {
|
||||
return sslFactory.getProtocols();
|
||||
}
|
||||
|
||||
public SSLParameters getSslParameters() {
|
||||
return sslFactory.getSslParameters();
|
||||
}
|
||||
|
||||
public SSLEngine getSSLEngine() {
|
||||
return sslFactory.getSSLEngine();
|
||||
}
|
||||
|
||||
public SSLEngine getSSLEngine(String peerHost, Integer peerPort) {
|
||||
return sslFactory.getSSLEngine(peerHost, peerPort);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.constants;
|
||||
|
||||
public final class MtlsConstants {
|
||||
|
||||
/**
|
||||
* Mtls default key store alias key.
|
||||
*/
|
||||
public static final String MTLS_DEFAULT_KEY_STORE_ALIAS = "mtls-default-key-store";
|
||||
|
||||
/**
|
||||
* Mtls default trust store alias key.
|
||||
*/
|
||||
public static final String MTLS_DEFAULT_TRUST_STORE_ALIAS = "mtls-default-trust-store";
|
||||
|
||||
private MtlsConstants() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.endpoint;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.tls.ServerTlsModeHolder;
|
||||
import com.alibaba.cloud.mtls.server.ApplicationRestarter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
|
||||
/**
|
||||
* @author musi
|
||||
*/
|
||||
|
||||
@Endpoint(id = "sds")
|
||||
public class MtlsEndpoint {
|
||||
|
||||
@Autowired
|
||||
private ApplicationRestarter restarter;
|
||||
|
||||
@WriteOperation
|
||||
public String updateTlsMode(boolean isTls) {
|
||||
if (ServerTlsModeHolder.canModeUpdate(isTls)) {
|
||||
ServerTlsModeHolder.setTlsMode(isTls);
|
||||
restarter.restart();
|
||||
}
|
||||
return "update tls mode to " + isTls;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.endpoint;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* @author musi
|
||||
*/
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(Endpoint.class)
|
||||
@ConditionalOnProperty(name = "spring.cloud.mtls.config.enabled", matchIfMissing = true)
|
||||
public class MtlsEndpointAutoConfiguration {
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnAvailableEndpoint
|
||||
@Bean
|
||||
public MtlsEndpoint sdsEndpoint() {
|
||||
return new MtlsEndpoint();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.exception;
|
||||
|
||||
public class MtlsInitException extends RuntimeException {
|
||||
|
||||
public MtlsInitException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MtlsInitException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.server;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class ApplicationRestarter {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private SpringApplication application;
|
||||
|
||||
private String[] args;
|
||||
|
||||
private static final Log log = LogFactory.getLog(PreparedEventRecorder.class);
|
||||
|
||||
public void restart() {
|
||||
Thread thread = new Thread(this::safeRestart);
|
||||
thread.setDaemon(false);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public ApplicationRestarter() {
|
||||
this.application = PreparedEventRecorder.getApplication();
|
||||
this.context = PreparedEventRecorder.getContext();
|
||||
this.args = PreparedEventRecorder.getArgs();
|
||||
}
|
||||
|
||||
private Boolean safeRestart() {
|
||||
try {
|
||||
doRestart();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.info("Could not doRestart", e);
|
||||
}
|
||||
else {
|
||||
log.info("Could not doRestart: " + e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void doRestart() {
|
||||
if (this.context != null) {
|
||||
this.application.setEnvironment(this.context.getEnvironment());
|
||||
close();
|
||||
// If running in a webapp then the context classloader is probably going to
|
||||
// die so we need to revert to a safe place before starting again
|
||||
overrideClassLoaderForRestart();
|
||||
this.context = this.application.run(this.args);
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
ApplicationContext context = this.context;
|
||||
while (context instanceof Closeable) {
|
||||
try {
|
||||
((Closeable) context).close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.error("Cannot close context: " + context.getId(), e);
|
||||
}
|
||||
context = context.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
// @ManagedAttribute
|
||||
public boolean isRunning() {
|
||||
if (this.context != null) {
|
||||
return this.context.isRunning();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void overrideClassLoaderForRestart() {
|
||||
ClassUtils.overrideThreadContextClassLoader(
|
||||
this.application.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.server;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
public class PreparedEventRecorder
|
||||
implements ApplicationListener<ApplicationPreparedEvent> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(PreparedEventRecorder.class);
|
||||
|
||||
private static ConfigurableApplicationContext context;
|
||||
|
||||
private static SpringApplication application;
|
||||
|
||||
private static String[] args;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationPreparedEvent input) {
|
||||
synchronized (PreparedEventRecorder.class) {
|
||||
context = input.getApplicationContext();
|
||||
args = input.getArgs();
|
||||
application = input.getSpringApplication();
|
||||
application.addInitializers(new PostProcessorInitializer());
|
||||
}
|
||||
}
|
||||
|
||||
static ConfigurableApplicationContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
static SpringApplication getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
static String[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
class PostProcessorInitializer
|
||||
implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
context.registerBean(PostProcessor.class, () -> new PostProcessor());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PostProcessor implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof PreparedEventRecorder) {
|
||||
return PreparedEventRecorder.this;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.server.netty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNegotiator;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
||||
public class SslContextDelegate extends SslContext {
|
||||
|
||||
private SslContext context;
|
||||
|
||||
public SslContextDelegate(SslContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setContext(SslContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClient() {
|
||||
return context.isClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cipherSuites() {
|
||||
return context.cipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
|
||||
return context.applicationProtocolNegotiator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator byteBufAllocator) {
|
||||
return context.newEngine(byteBufAllocator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine newEngine(ByteBufAllocator byteBufAllocator, String s, int i) {
|
||||
return context.newEngine(byteBufAllocator, s, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSessionContext sessionContext() {
|
||||
return context.sessionContext();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.server.tomcat;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.tls.ServerTlsModeHolder;
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertPair;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertUpdateCallback;
|
||||
import com.alibaba.cloud.mtls.MtlsSslStoreProvider;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
|
||||
import org.apache.coyote.AbstractProtocol;
|
||||
import org.apache.coyote.ProtocolHandler;
|
||||
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
|
||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
|
||||
import org.springframework.boot.web.server.Ssl;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class MtlsTomcatConnectCustomizer
|
||||
implements TomcatConnectorCustomizer, ApplicationContextAware {
|
||||
|
||||
private static final Logger log = LoggerFactory
|
||||
.getLogger(MtlsTomcatConnectCustomizer.class);
|
||||
|
||||
private final AbstractCertManager certManager;
|
||||
|
||||
private final MtlsSslStoreProvider sslStoreProvider;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public MtlsTomcatConnectCustomizer(MtlsSslStoreProvider sslStoreProvider,
|
||||
AbstractCertManager certManager) {
|
||||
this.certManager = certManager;
|
||||
this.sslStoreProvider = sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Connector connector) {
|
||||
if (!validateContext()) {
|
||||
return;
|
||||
}
|
||||
// When the certificate is expired, we refresh the server certificate.
|
||||
certManager.registerCallback(new CertUpdateCallback() {
|
||||
@Override
|
||||
public synchronized void onUpdateCert(CertPair certPair) {
|
||||
try {
|
||||
if (!validateContext()) {
|
||||
return;
|
||||
}
|
||||
ProtocolHandler protocolHandler = connector.getProtocolHandler();
|
||||
AbstractProtocol<?> abstractProtocol = (AbstractProtocol<?>) protocolHandler;
|
||||
if (abstractProtocol instanceof AbstractHttp11Protocol<?>) {
|
||||
AbstractHttp11Protocol<?> proto = ((AbstractHttp11Protocol<?>) abstractProtocol);
|
||||
proto.reloadSslHostConfigs();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Failed to reload certificate of tomcat", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!ServerTlsModeHolder.getTlsMode()) {
|
||||
log.warn("Fetch tls mode failed, use plaintext to transport");
|
||||
return;
|
||||
}
|
||||
if (!ServerTlsModeHolder.getTlsMode()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ProtocolHandler handler = connector.getProtocolHandler();
|
||||
AbstractHttp11JsseProtocol<?> protocol = (AbstractHttp11JsseProtocol<?>) handler;
|
||||
Ssl ssl = new Ssl();
|
||||
ssl.setClientAuth(Ssl.ClientAuth.WANT);
|
||||
protocol.setSSLEnabled(true);
|
||||
protocol.setSslProtocol(ssl.getProtocol());
|
||||
configureSslClientAuth(protocol, ssl);
|
||||
protocol.setKeyAlias(ssl.getKeyAlias());
|
||||
String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
|
||||
if (StringUtils.hasText(ciphers)) {
|
||||
protocol.setCiphers(ciphers);
|
||||
}
|
||||
TomcatURLStreamHandlerFactory tomcatURLStreamHandlerFactory = TomcatURLStreamHandlerFactory
|
||||
.getInstance();
|
||||
SslStoreProviderUrlStreamHandlerFactory sslStoreProviderUrlStreamHandlerFactory = new SslStoreProviderUrlStreamHandlerFactory(
|
||||
sslStoreProvider);
|
||||
TomcatURLStreamHandlerFactory.release(
|
||||
sslStoreProviderUrlStreamHandlerFactory.getClass().getClassLoader());
|
||||
tomcatURLStreamHandlerFactory
|
||||
.addUserFactory(sslStoreProviderUrlStreamHandlerFactory);
|
||||
try {
|
||||
if (sslStoreProvider.getKeyStore() != null) {
|
||||
protocol.setKeystorePass("");
|
||||
protocol.setKeystoreFile(
|
||||
SslStoreProviderUrlStreamHandlerFactory.KEY_STORE_URL);
|
||||
}
|
||||
if (sslStoreProvider.getTrustStore() != null) {
|
||||
protocol.setTruststorePass("");
|
||||
protocol.setTruststoreFile(
|
||||
SslStoreProviderUrlStreamHandlerFactory.TRUST_STORE_URL);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new WebServerException("Could not load store: " + ex.getMessage(),
|
||||
ex);
|
||||
}
|
||||
connector.setScheme("https");
|
||||
connector.setSecure(true);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Custom tomcat ssl failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateContext() {
|
||||
if (applicationContext instanceof ConfigurableApplicationContext) {
|
||||
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
|
||||
return context.isActive();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
|
||||
if (ssl.getClientAuth() == Ssl.ClientAuth.NEED) {
|
||||
protocol.setClientAuth(Boolean.TRUE.toString());
|
||||
}
|
||||
else if (ssl.getClientAuth() == Ssl.ClientAuth.WANT) {
|
||||
protocol.setClientAuth("want");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls.server.tomcat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import org.springframework.boot.web.server.SslStoreProvider;
|
||||
|
||||
public class SslStoreProviderUrlStreamHandlerFactory implements URLStreamHandlerFactory {
|
||||
|
||||
private static final String PROTOCOL = "springbootssl";
|
||||
|
||||
private static final String KEY_STORE_PATH = "keyStore";
|
||||
|
||||
static final String KEY_STORE_URL = PROTOCOL + ":" + KEY_STORE_PATH;
|
||||
|
||||
private static final String TRUST_STORE_PATH = "trustStore";
|
||||
|
||||
static final String TRUST_STORE_URL = PROTOCOL + ":" + TRUST_STORE_PATH;
|
||||
|
||||
// Must be a static variable, or we can not reload sslStoreProvider when we restart
|
||||
// the spring context.
|
||||
private static SslStoreProvider sslStoreProvider;
|
||||
|
||||
SslStoreProviderUrlStreamHandlerFactory(SslStoreProvider sslStoreProvider) {
|
||||
SslStoreProviderUrlStreamHandlerFactory.sslStoreProvider = sslStoreProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URLStreamHandler createURLStreamHandler(String protocol) {
|
||||
if (PROTOCOL.equals(protocol)) {
|
||||
return new URLStreamHandler() {
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
try {
|
||||
if (KEY_STORE_PATH.equals(url.getPath())) {
|
||||
return new KeyStoreUrlConnection(url,
|
||||
SslStoreProviderUrlStreamHandlerFactory.sslStoreProvider
|
||||
.getKeyStore());
|
||||
}
|
||||
if (TRUST_STORE_PATH.equals(url.getPath())) {
|
||||
return new KeyStoreUrlConnection(url,
|
||||
SslStoreProviderUrlStreamHandlerFactory.sslStoreProvider
|
||||
.getTrustStore());
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
throw new IOException("Invalid path: " + url.getPath());
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class KeyStoreUrlConnection extends URLConnection {
|
||||
|
||||
private final KeyStore keyStore;
|
||||
|
||||
private KeyStoreUrlConnection(URL url, KeyStore keyStore) {
|
||||
super(url);
|
||||
this.keyStore = keyStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
this.keyStore.store(stream, new char[0]);
|
||||
return new ByteArrayInputStream(stream.toByteArray());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.alibaba.cloud.mtls.MtlsAutoConfiguration,\
|
||||
com.alibaba.cloud.mtls.endpoint.MtlsEndpointAutoConfiguration
|
||||
org.springframework.context.ApplicationListener=\
|
||||
com.alibaba.cloud.mtls.server.PreparedEventRecorder
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
import com.alibaba.cloud.governance.istio.XdsConfigProperties;
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.governance.istio.sds.CertPair;
|
||||
import com.alibaba.cloud.governance.istio.util.CertificateUtil;
|
||||
|
||||
public class MockCertManager extends AbstractCertManager {
|
||||
|
||||
public MockCertManager(XdsConfigProperties xdsConfigProperties) {
|
||||
super(xdsConfigProperties);
|
||||
}
|
||||
|
||||
public MockCertManager() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized CertPair getCertPair() {
|
||||
Certificate[] certificate = CertificateUtil.loadCertificateFromPath(
|
||||
MockCertManager.class.getResource("/cert/mtls.crt").getPath());
|
||||
PrivateKey privateKey = CertificateUtil.loadPrivateKeyFromPath(
|
||||
MockCertManager.class.getResource("/cert/mtls.key").getPath());
|
||||
CertPair p = new CertPair();
|
||||
p.setCertificateChain(certificate);
|
||||
p.setPrivateKey(privateKey);
|
||||
p.setExpireTime(Long.MAX_VALUE);
|
||||
return p;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.ControlPlaneInitedBean;
|
||||
import com.alibaba.cloud.governance.istio.XdsAutoConfiguration;
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.mtls.client.MtlsClientSSLContext;
|
||||
import com.alibaba.cloud.mtls.server.netty.MtlsNettyServerCustomizer;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.reactive.SslInfo;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "spring.main.web-application-type=reactive",
|
||||
"spring.cloud.nacos.discovery.enabled=false" },
|
||||
classes = { MtlsClientTests.TestConfig.class })
|
||||
public class MtlsClientTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
private static CloseableHttpClient client;
|
||||
|
||||
private static CloseableHttpClient noCertClient;
|
||||
|
||||
@Autowired
|
||||
private MtlsClientSSLContext context;
|
||||
|
||||
public void init() {
|
||||
if (client == null) {
|
||||
client = NoVerifyHttpClientFactory.getClient(context.getSslContext());
|
||||
}
|
||||
if (noCertClient == null) {
|
||||
noCertClient = NoVerifyHttpClientFactory.getClient();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusCode() throws Exception {
|
||||
init();
|
||||
CloseableHttpResponse response = client
|
||||
.execute(new HttpGet("https://127.0.0.1:" + port + "/echo"));
|
||||
Assert.assertEquals(response.getStatusLine().getStatusCode(), 200);
|
||||
response = noCertClient
|
||||
.execute(new HttpGet("https://127.0.0.1:" + port + "/echo"));
|
||||
Assert.assertEquals(response.getStatusLine().getStatusCode(), 500);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration(exclude = XdsAutoConfiguration.class)
|
||||
@ImportAutoConfiguration(MtlsAutoConfiguration.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public AbstractCertManager mockCertManager() {
|
||||
return new MockCertManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ControlPlaneInitedBean mockInitBean() {
|
||||
return new ControlPlaneInitedBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NettyReactiveWebServerFactory nettyFactory(
|
||||
MtlsNettyServerCustomizer customizer) {
|
||||
NettyReactiveWebServerFactory nettyReactiveWebServerFactory = new NettyReactiveWebServerFactory();
|
||||
nettyReactiveWebServerFactory.addServerCustomizers(customizer);
|
||||
return nettyReactiveWebServerFactory;
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class EchoController {
|
||||
|
||||
@RequestMapping("/echo")
|
||||
public String echo(ServerWebExchange exchange) {
|
||||
if (exchange.getRequest().getSslInfo() != null) {
|
||||
SslInfo sslInfo = exchange.getRequest().getSslInfo();
|
||||
if (sslInfo.getPeerCertificates() != null) {
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("No client certificate");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.ControlPlaneInitedBean;
|
||||
import com.alibaba.cloud.governance.istio.XdsAutoConfiguration;
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "spring.main.web-application-type=servlet",
|
||||
"spring.cloud.nacos.discovery.enabled=false" },
|
||||
classes = { MtlsClientTests.TestConfig.class })
|
||||
public class MtlsMvcServerTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
private CloseableHttpClient client = NoVerifyHttpClientFactory.getClient();
|
||||
|
||||
@Test
|
||||
public void testStatusCode() throws Exception {
|
||||
boolean flag = false;
|
||||
CloseableHttpResponse response = client
|
||||
.execute(new HttpGet("http://127.0.0.1:" + port + "/echo"));
|
||||
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
|
||||
response = client.execute(new HttpGet("https://127.0.0.1:" + port + "/echo"));
|
||||
Assert.assertEquals(404, response.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration(exclude = XdsAutoConfiguration.class)
|
||||
@ImportAutoConfiguration(MtlsAutoConfiguration.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public AbstractCertManager mockCertManager() {
|
||||
return new MockCertManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ControlPlaneInitedBean mockInitBean() {
|
||||
return new ControlPlaneInitedBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import com.alibaba.cloud.commons.governance.ControlPlaneInitedBean;
|
||||
import com.alibaba.cloud.governance.istio.XdsAutoConfiguration;
|
||||
import com.alibaba.cloud.governance.istio.sds.AbstractCertManager;
|
||||
import com.alibaba.cloud.mtls.server.netty.MtlsNettyServerCustomizer;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "spring.main.web-application-type=reactive",
|
||||
"spring.cloud.nacos.discovery.enabled=false" },
|
||||
classes = { MtlsWebfluxServerTest.TestConfig.class })
|
||||
public class MtlsWebfluxServerTest {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
private CloseableHttpClient client = NoVerifyHttpClientFactory.getClient();
|
||||
|
||||
@Test
|
||||
public void testStatusCode() throws Exception {
|
||||
boolean flag = false;
|
||||
CloseableHttpResponse response;
|
||||
try {
|
||||
response = client.execute(new HttpGet("http://127.0.0.1:" + port + "/echo"));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
flag = true;
|
||||
}
|
||||
Assert.assertTrue(flag);
|
||||
response = client.execute(new HttpGet("https://127.0.0.1:" + port + "/echo"));
|
||||
Assert.assertEquals(response.getStatusLine().getStatusCode(), 200);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration(exclude = XdsAutoConfiguration.class)
|
||||
@ImportAutoConfiguration(MtlsAutoConfiguration.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public AbstractCertManager mockCertManager() {
|
||||
return new MockCertManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ControlPlaneInitedBean mockInitBean() {
|
||||
return new ControlPlaneInitedBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NettyReactiveWebServerFactory nettyFactory(
|
||||
MtlsNettyServerCustomizer customizer) {
|
||||
NettyReactiveWebServerFactory nettyReactiveWebServerFactory = new NettyReactiveWebServerFactory();
|
||||
nettyReactiveWebServerFactory.addServerCustomizers(customizer);
|
||||
return nettyReactiveWebServerFactory;
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class EchoController {
|
||||
|
||||
@RequestMapping("/echo")
|
||||
public String echo() {
|
||||
return "echo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.mtls;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
|
||||
public final class NoVerifyHttpClientFactory {
|
||||
|
||||
private NoVerifyHttpClientFactory() {
|
||||
|
||||
}
|
||||
|
||||
private static final CloseableHttpClient CLIENT;
|
||||
|
||||
static {
|
||||
try {
|
||||
CLIENT = HttpClients.custom()
|
||||
.setSSLSocketFactory(new SSLConnectionSocketFactory(
|
||||
createIgnoreVerifySSL(), null, null, new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname,
|
||||
SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}))
|
||||
.build();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static CloseableHttpClient getClient() {
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
public static CloseableHttpClient getClient(SSLContext sslContext) {
|
||||
return HttpClients.custom()
|
||||
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext, null,
|
||||
null, new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SSLContext createIgnoreVerifySSL() throws Exception {
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
X509TrustManager trustManager = new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(
|
||||
java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
|
||||
String paramString) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(
|
||||
java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
|
||||
String paramString) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
sc.init(null, new TrustManager[] { trustManager }, null);
|
||||
return sc;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICyTCCAbGgAwIBAgIEQDwBNzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwpj
|
||||
c2Iub25saW5lMB4XDTIyMTAxOTA5MzY0MFoXDTIzMDExNzA5MzY0MFowFTETMBEG
|
||||
A1UEAxMKY3NiLm9ubGluZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AIFNp9P+w2Oxg7jtXKRWEBwHvQDzIxoORPARi9rHZDtJbvUPm9AQ+4RG59J1UV5t
|
||||
Bf6HwGBQHOTeN5wVP40VZCPLPsyWFbptWk9DHpleW6RkV8GWvEf7rsecrXG/aO1t
|
||||
sT7+L6P3XAdsxeSjXgvXr3+kjWz9AiVwLPdZuY++55ZUOnykeHdpqmedraN0N7Q8
|
||||
M6JpjDn+KFnk16kqgIl5f0Z56iPeKmfu7JFE+yX9glwsrfwHeokgS93GKhOo9HLJ
|
||||
KBaY7/LobCnzUxl7meOy02PAA7bMjI2peL0H/kc8yW7o6dtKIMt+wRk0d4Jfmzqh
|
||||
fNNCNCnhEhiGTrCNKnt50h0CAwEAAaMhMB8wHQYDVR0OBBYEFA3VC9jg2HmhlSG0
|
||||
13Idl3qa8KMLMA0GCSqGSIb3DQEBCwUAA4IBAQBas1jqiZEmyb36VQqgMtzu8fvt
|
||||
wsQCE1D751MejDz0lMSNVd/Ze48OttxkmTQY0Qvf9B4NUMktZbDgqg5Gg9OSBTPM
|
||||
RJGw4/e2J5VGydmNPGVGzJWQnrCpYWrZvv/tQCja7Y4d45PJSC4zsvTX5D8zjMdD
|
||||
u/MI+K6BmYvktlE+TOk3jr2KahgrZFdXY1vV25jAmZbRksCswrbq6bwdUCjZuGG2
|
||||
euTPI9JAEJqOGg+cniIoBQiIBjMnoeZUcO5Rmbm8iYtOMb6skmN8FUEIYxbr6xuw
|
||||
Q8wa2AfKUrRwAWQAp9HMqj8xo42Iwq+eIxpwhZEGXS+vM3dqovISDNrVRjyT
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBTafT/sNjsYO4
|
||||
7VykVhAcB70A8yMaDkTwEYvax2Q7SW71D5vQEPuERufSdVFebQX+h8BgUBzk3jec
|
||||
FT+NFWQjyz7MlhW6bVpPQx6ZXlukZFfBlrxH+67HnK1xv2jtbbE+/i+j91wHbMXk
|
||||
o14L169/pI1s/QIlcCz3WbmPvueWVDp8pHh3aapnna2jdDe0PDOiaYw5/ihZ5Nep
|
||||
KoCJeX9Geeoj3ipn7uyRRPsl/YJcLK38B3qJIEvdxioTqPRyySgWmO/y6Gwp81MZ
|
||||
e5njstNjwAO2zIyNqXi9B/5HPMlu6OnbSiDLfsEZNHeCX5s6oXzTQjQp4RIYhk6w
|
||||
jSp7edIdAgMBAAECggEABz2oBlEf8/c+3x47r+A56rfsN59l+dGOvrrc0BWWYVON
|
||||
aR/Eo/QvgYrO9JQpSFZmnYhQl5Qk6hQfnRf7hRULlI2PKLnG4be59PJXRlNoYl5U
|
||||
I70jMgzADuGRPOtxHsqjwFlPpaj2eFv5AQK9A4DCjS1T1iSc+Ce9/OQDZi9UnLBq
|
||||
IJF8+zBDCgihpaGxlMAJpSHol8UBsE2rC6UVh+CcpgN708gwFfmcUk9BZy+GlAuF
|
||||
xTSbJWBWsbSUdf762QUb2I4er3TdAJKN+VYbfNHqsozQrJ73ME0NjOdrsF/pA9LF
|
||||
Ki9sgThk/IcB7yKMiJ6Gs8B2fONxbPh/vNjIV7oPAQKBgQDX/YDc1mZ3CF8vs/ZL
|
||||
tPfM5fLdibOLiKUNETF/VBnO7G4rdIlCKMaXympyV2Kq2euEWfR08Po94RMYuIIJ
|
||||
CUvkrfjaLK0VvRxTJyTSFwQSrEMvVU4D0AyqqA4U7/18cOB9Jxz3+wnpDDg6fU10
|
||||
t4nwn/FpFmFsmm0DtmzhL6eYYQKBgQCZQVz6TwigKzrjD8O7RwBuvwGDdo/8ka19
|
||||
xZVQGpP34GJkMcWj4Cw3fD7cnxtYuX5H2xuB5gfqaLsCG/5DcU4MdyL/e3x04Knm
|
||||
bIstt2EUY9DeU0oXgpVIJ+G8J2xnqD/haBpHCvzZ8IaZgTNgd3BStABrR0LwGe+r
|
||||
bHzWcb1jPQKBgF2Z5WtOmo8BGMcCdTzvyueHy00IbL+OUwCrr8ifOKP8v+2jDgyW
|
||||
wSFsvd6ZUg/6al8r4I2BFOEWFgGDjA7AcZxDbHGYJNYj4w+CCinlgYVaE6+Ch1GN
|
||||
qr+WHqwiKLbx78cs2Rf6OZw+CGwIezWWiHe3yJWi9ktrTMzsFJkt3rJhAoGADYL3
|
||||
wZHKPuTQ0kgHh7Fg1mK8rWx8kVX+p8INwfw143rC8fZ5aFNRUqr/l8/nR1FDUu58
|
||||
ZF11gTMumacCKcwJh4vRaBjpBhzwncIgGy25v2R1e8R4Gc6Hfs8VVdNb+V+aEjNt
|
||||
baoIVOah11LOxsiA/KmmB89GlYiT2tc7wmRQwDkCgYEAvy6sDfpBa0CBAWOe/xL+
|
||||
Nk9VzJO+hb4eRV+o6BGOCiGtjbhMBlFDsNpqCIcg/zE8VM7xOuNTUFPiwL6Aqpjo
|
||||
xq9Zy8RyIejoN9vcZ1hjClozMOhhm/24+ItSzuIj76BZJ3g7WJNM29kGqtURDO8m
|
||||
5JqOo9UOZoRLSV95TmfUiMg=
|
||||
-----END PRIVATE KEY-----
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2022-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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 com.alibaba.cloud.governance.istio;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import com.alibaba.cloud.governance.istio.constant.IstioConstants;
|
||||
import com.alibaba.cloud.governance.istio.protocol.AbstractXdsProtocol;
|
||||
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
|
||||
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author musi
|
||||
* @author <a href="liuziming@buaa.edu.cn"></a>
|
||||
* @since 2.2.10-RC1 AggregateDiscoveryService is used to send xds request and handle xds
|
||||
* response.
|
||||
*/
|
||||
public class AggregateDiscoveryService {
|
||||
|
||||
private static final Logger log = LoggerFactory
|
||||
.getLogger(AggregateDiscoveryService.class);
|
||||
|
||||
private final Map<String, AbstractXdsProtocol> protocolMap = new HashMap<>();
|
||||
|
||||
private final Map<String, Set<String>> requestResource = new ConcurrentHashMap<>();
|
||||
|
||||
private StreamObserver<DiscoveryRequest> observer;
|
||||
|
||||
private final XdsConfigProperties xdsConfigProperties;
|
||||
|
||||
private final XdsChannel xdsChannel;
|
||||
|
||||
private final ScheduledExecutorService retry;
|
||||
|
||||
public AggregateDiscoveryService(XdsChannel xdsChannel,
|
||||
XdsConfigProperties xdsConfigProperties) {
|
||||
this.xdsChannel = xdsChannel;
|
||||
this.xdsConfigProperties = xdsConfigProperties;
|
||||
this.observer = xdsChannel.createDiscoveryRequest(new XdsObserver());
|
||||
this.retry = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
|
||||
public void addProtocol(AbstractXdsProtocol abstractXdsProtocol) {
|
||||
protocolMap.put(abstractXdsProtocol.getTypeUrl(), abstractXdsProtocol);
|
||||
}
|
||||
|
||||
public void sendXdsRequest(String typeUrl, Set<String> resourceNames) {
|
||||
requestResource.put(typeUrl, resourceNames);
|
||||
DiscoveryRequest request = DiscoveryRequest.newBuilder()
|
||||
.setNode(xdsChannel.getNode()).setTypeUrl(typeUrl)
|
||||
.addAllResourceNames(resourceNames).build();
|
||||
observer.onNext(request);
|
||||
}
|
||||
|
||||
private void sendAckRequest(DiscoveryResponse response) {
|
||||
Set<String> ackResource = requestResource.getOrDefault(response.getTypeUrl(),
|
||||
new HashSet<>());
|
||||
DiscoveryRequest request = DiscoveryRequest.newBuilder()
|
||||
.setVersionInfo(response.getVersionInfo()).setNode(xdsChannel.getNode())
|
||||
.addAllResourceNames(ackResource).setTypeUrl(response.getTypeUrl())
|
||||
.setResponseNonce(response.getNonce()).build();
|
||||
observer.onNext(request);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
retry.shutdown();
|
||||
}
|
||||
|
||||
private class XdsObserver implements StreamObserver<DiscoveryResponse> {
|
||||
|
||||
@Override
|
||||
public void onNext(DiscoveryResponse discoveryResponse) {
|
||||
String typeUrl = discoveryResponse.getTypeUrl();
|
||||
if (xdsConfigProperties.isLogXds()) {
|
||||
log.info("Receive notification from xds server, type: {}, size: {}",
|
||||
typeUrl, discoveryResponse.getResourcesCount());
|
||||
}
|
||||
AbstractXdsProtocol protocol = protocolMap.get(typeUrl);
|
||||
if (protocol == null) {
|
||||
throw new UnsupportedOperationException("No protocol of type " + typeUrl);
|
||||
}
|
||||
List<?> responses = protocol.decodeXdsResponse(discoveryResponse);
|
||||
sendAckRequest(discoveryResponse);
|
||||
protocol.onResponseDecoded(responses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
if (xdsConfigProperties.isLogXds()) {
|
||||
log.error("Connect to xds server failed, reconnect after 3 seconds",
|
||||
throwable);
|
||||
}
|
||||
|
||||
requestResource.clear();
|
||||
retry.schedule(() -> {
|
||||
xdsChannel.restart();
|
||||
observer = xdsChannel.createDiscoveryRequest(this);
|
||||
sendXdsRequest(IstioConstants.CDS_URL, new HashSet<>());
|
||||
log.info("Reconnecting to istio control plane!");
|
||||
}, 3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
log.info("Xds connect completed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|