diff --git a/.gitignore b/.gitignore index 704fcd5cd..ed568a959 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ applog/ */backup /backup backup +*/tls diff --git a/CHANGELOG.md b/CHANGELOG.md index 19c4a239f..d6ea8f23a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,3 +23,4 @@ - [fix: polaris.discovery.heartbeat.enabled not effective.](https://github.com/Tencent/spring-cloud-tencent/pull/1694) - [feat: support config ratelimit addresses and remote task interval.](https://github.com/Tencent/spring-cloud-tencent/pull/1695) - [docs:optimize tsf example.](https://github.com/Tencent/spring-cloud-tencent/pull/1711) +- [feat:support TSF certificate manager.](https://github.com/Tencent/spring-cloud-tencent/pull/1716) diff --git a/spring-cloud-starter-tencent-all/pom.xml b/spring-cloud-starter-tencent-all/pom.xml index 975f99bd9..3758dd093 100644 --- a/spring-cloud-starter-tencent-all/pom.xml +++ b/spring-cloud-starter-tencent-all/pom.xml @@ -84,5 +84,10 @@ com.tencent.cloud spring-cloud-starter-tencent-fault-injection-plugin + + + com.tencent.cloud + spring-cloud-starter-tencent-tsf-tls-plugin + diff --git a/spring-cloud-starter-tencent-polaris-config/pom.xml b/spring-cloud-starter-tencent-polaris-config/pom.xml index 018d12f60..99d4f6609 100644 --- a/spring-cloud-starter-tencent-polaris-config/pom.xml +++ b/spring-cloud-starter-tencent-polaris-config/pom.xml @@ -19,6 +19,12 @@ com.tencent.cloud spring-cloud-tencent-polaris-context + + + com.tencent.cloud + spring-cloud-starter-tencent-tsf-tls-plugin + true + diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java index 795d99b2a..f21285a70 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java @@ -19,12 +19,16 @@ package com.tencent.cloud.polaris.config.adapter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import com.tencent.cloud.plugin.tsf.tls.utils.SyncUtils; import com.tencent.cloud.polaris.config.config.ConfigFileGroup; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; +import com.tencent.cloud.polaris.context.config.extend.tsf.TsfTlsProperties; import com.tencent.polaris.api.utils.ClassUtils; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.api.utils.StringUtils; @@ -38,6 +42,7 @@ import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.core.annotation.Order; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import static com.tencent.cloud.polaris.config.utils.PolarisPropertySourceUtils.loadGroupPolarisPropertySource; @@ -100,6 +105,8 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { } // load tsf default config group initTsfConfigGroups(compositePropertySource); + // load tsf tls properties if need. + initTsfTlsPropertySource(compositePropertySource); return compositePropertySource; } finally { @@ -229,6 +236,90 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { } } + void initTsfTlsPropertySource(CompositePropertySource compositePropertySource) { + String address = System.getProperty("MESH_CITADEL_ADDR", System.getenv("MESH_CITADEL_ADDR")); + if (StringUtils.isNotBlank(address) + && (StringUtils.equals("tsf", environment.getProperty("server.ssl.bundle")) + || "tsf".equals(compositePropertySource.getProperty(("server.ssl.bundle")))) + && ClassUtils.isClassPresent("com.tencent.cloud.plugin.tsf.tls.utils.SyncUtils") + && !SyncUtils.isInitialized()) { + // get common name + Object commonName = compositePropertySource.getProperty("spring.cloud.polaris.service"); + if (commonName == null) { + commonName = compositePropertySource.getProperty("spring.cloud.polaris.discovery.service"); + } + if (commonName == null) { + commonName = compositePropertySource.getProperty("spring.application.name"); + } + if (commonName == null) { + commonName = environment.getProperty("spring.cloud.polaris.service"); + } + if (commonName == null) { + commonName = environment.getProperty("spring.cloud.polaris.discovery.service"); + } + if (commonName == null) { + commonName = environment.getProperty("spring.application.name"); + } + // get certPath + String certPath = System.getProperty("MESH_CITADEL_CERT", System.getenv("MESH_CITADEL_CERT")); + // get token + String token = System.getProperty("tsf_token", System.getenv("tsf_token")); + // get validityDuration + Object validityDuration = compositePropertySource.getProperty("spring.cloud.polaris.tls.validityDuration"); + if (validityDuration == null) { + validityDuration = environment.getProperty("spring.cloud.polaris.tls.validityDuration", Long.class, TsfTlsProperties.DEFAULT_VALIDITY_DURATION); + } + if (validityDuration instanceof String) { + validityDuration = Long.valueOf((String) validityDuration); + } + // get refreshBefore + Object refreshBefore = compositePropertySource.getProperty("spring.cloud.polaris.tls.refreshBefore"); + if (refreshBefore == null) { + refreshBefore = environment.getProperty("spring.cloud.polaris.tls.refreshBefore", Long.class, TsfTlsProperties.DEFAULT_REFRESH_BEFORE); + } + if (refreshBefore instanceof String) { + refreshBefore = Long.valueOf((String) refreshBefore); + } + // get watchInterval + Object watchInterval = compositePropertySource.getProperty("spring.cloud.polaris.tls.watchInterval"); + if (watchInterval == null) { + watchInterval = environment.getProperty("spring.cloud.polaris.tls.watchInterval", Long.class, TsfTlsProperties.DEFAULT_WATCH_INTERVAL); + } + if (watchInterval instanceof String) { + watchInterval = Long.valueOf((String) watchInterval); + } + SyncUtils.init((String) commonName, address, certPath, token, (Long) validityDuration, (Long) refreshBefore, (Long) watchInterval); + if (SyncUtils.isVerified()) { + Map tlsEnvProperties = new HashMap<>(); + // set ssl + Object clientAuth = compositePropertySource.getProperty("server.ssl.client-auth"); + if (clientAuth == null) { + clientAuth = environment.getProperty("server.ssl.client-auth", "want"); + } + tlsEnvProperties.put("server.ssl.client-auth", clientAuth); + Object protocol = compositePropertySource.getProperty("spring.cloud.polaris.discovery.protocol"); + if (protocol == null) { + protocol = environment.getProperty("spring.cloud.polaris.discovery.protocol", "https"); + } + tlsEnvProperties.put("spring.cloud.polaris.discovery.protocol", protocol); + tlsEnvProperties.put("tsf.discovery.scheme", protocol); + // set tsf spring ssl bundle + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.reload-on-update", "true"); + if (StringUtils.isNotBlank(SyncUtils.getPemKeyStoreCertPath()) && StringUtils.isNotBlank(SyncUtils.getPemKeyStoreKeyPath())) { + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.keystore.certificate", SyncUtils.getPemKeyStoreCertPath()); + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.keystore.private-key", SyncUtils.getPemKeyStoreKeyPath()); + } + if (StringUtils.isNotBlank(SyncUtils.getPemTrustStoreCertPath())) { + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.truststore.certificate", SyncUtils.getPemTrustStoreCertPath()); + } + + // process environment + MapPropertySource propertySource = new MapPropertySource("tsf-tls-config", tlsEnvProperties); + compositePropertySource.addPropertySource(propertySource); + } + } + } + private void initCustomPolarisConfigFiles(CompositePropertySource compositePropertySource, List configFileGroups) { String namespace = polarisContextProperties.getNamespace(); diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFilePuller.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFilePuller.java index 90a3483a0..eb6212f88 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFilePuller.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFilePuller.java @@ -19,12 +19,16 @@ package com.tencent.cloud.polaris.config.adapter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import com.tencent.cloud.plugin.tsf.tls.utils.SyncUtils; import com.tencent.cloud.polaris.config.config.ConfigFileGroup; import com.tencent.cloud.polaris.config.configdata.PolarisConfigDataLoader; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; +import com.tencent.cloud.polaris.context.config.extend.tsf.TsfTlsProperties; import com.tencent.polaris.api.utils.ClassUtils; import com.tencent.polaris.api.utils.CollectionUtils; import com.tencent.polaris.api.utils.StringUtils; @@ -35,6 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.MapPropertySource; import static com.tencent.cloud.polaris.config.utils.PolarisPropertySourceUtils.loadGroupPolarisPropertySource; import static com.tencent.cloud.polaris.config.utils.PolarisPropertySourceUtils.loadPolarisPropertySource; @@ -164,6 +169,79 @@ public final class PolarisConfigFilePuller { } } + /** + * Init TSF TLS property source. + * @param compositePropertySource compositePropertySource + */ + public void initTsfTlsPropertySource(CompositePropertySource compositePropertySource, + TsfTlsProperties tsfTlsProperties, String serviceName) { + String address = System.getProperty("MESH_CITADEL_ADDR", System.getenv("MESH_CITADEL_ADDR")); + if (StringUtils.isNotBlank(address) + && (StringUtils.equals("tsf", System.getProperty("server.ssl.bundle")) + || "tsf".equals(compositePropertySource.getProperty(("server.ssl.bundle")))) + && ClassUtils.isClassPresent("com.tencent.cloud.plugin.tsf.tls.utils.SyncUtils") + && !SyncUtils.isInitialized()) { + // get certPath + String certPath = System.getProperty("MESH_CITADEL_CERT", System.getenv("MESH_CITADEL_CERT")); + // get token + String token = System.getProperty("tsf_token", System.getenv("tsf_token")); + // get validityDuration + Object validityDuration = compositePropertySource.getProperty("spring.cloud.polaris.tls.validityDuration"); + if (validityDuration == null) { + validityDuration = tsfTlsProperties.getValidityDuration(); + } + if (validityDuration instanceof String) { + validityDuration = Long.valueOf((String) validityDuration); + } + // get refreshBefore + Object refreshBefore = compositePropertySource.getProperty("spring.cloud.polaris.tls.refreshBefore"); + if (refreshBefore == null) { + refreshBefore = tsfTlsProperties.getRefreshBefore(); + } + if (refreshBefore instanceof String) { + refreshBefore = Long.valueOf((String) refreshBefore); + } + // get watchInterval + Object watchInterval = compositePropertySource.getProperty("spring.cloud.polaris.tls.watchInterval"); + if (watchInterval == null) { + watchInterval = tsfTlsProperties.getWatchInterval(); + } + if (watchInterval instanceof String) { + watchInterval = Long.valueOf((String) watchInterval); + } + SyncUtils.init(serviceName, address, certPath, token, (Long) validityDuration, (Long) refreshBefore, (Long) watchInterval); + if (SyncUtils.isVerified()) { + Map tlsEnvProperties = new HashMap<>(); + // set ssl + Object clientAuth = compositePropertySource.getProperty("server.ssl.client-auth"); + if (clientAuth == null) { + clientAuth = System.getProperty("server.ssl.client-auth", "want"); + } + tlsEnvProperties.put("server.ssl.client-auth", clientAuth); + Object protocol = compositePropertySource.getProperty("spring.cloud.polaris.discovery.protocol"); + if (protocol == null) { + protocol = System.getProperty("spring.cloud.polaris.discovery.protocol", "https"); + } + tlsEnvProperties.put("spring.cloud.polaris.discovery.protocol", protocol); + tlsEnvProperties.put("tsf.discovery.scheme", protocol); + + // set tsf spring ssl bundle + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.reload-on-update", "true"); + if (StringUtils.isNotBlank(SyncUtils.getPemKeyStoreCertPath()) && StringUtils.isNotBlank(SyncUtils.getPemKeyStoreKeyPath())) { + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.keystore.certificate", SyncUtils.getPemKeyStoreCertPath()); + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.keystore.private-key", SyncUtils.getPemKeyStoreKeyPath()); + } + if (StringUtils.isNotBlank(SyncUtils.getPemTrustStoreCertPath())) { + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.truststore.certificate", SyncUtils.getPemTrustStoreCertPath()); + } + + // process environment + MapPropertySource propertySource = new MapPropertySource("tsf-tls-config-data", tlsEnvProperties); + compositePropertySource.addPropertySource(propertySource); + } + } + } + private List getInternalConfigFiles( String[] activeProfiles, String[] defaultProfiles, String serviceName) { String namespace = polarisContextProperties.getNamespace(); diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLoader.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLoader.java index 1f619088c..3483795c8 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLoader.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataLoader.java @@ -132,6 +132,8 @@ public class PolarisConfigDataLoader implements ConfigDataLoader result = new ArrayList<>(); boolean optional = location.isOptional(); String groupFileName = getRealGroupFileName(location); String serviceName = loadPolarisConfigProperties(resolverContext, - String.class, "spring.application.name"); + String.class, "spring.cloud.polaris.discovery.service"); + if (StringUtils.isBlank(serviceName)) { + serviceName = loadPolarisConfigProperties(resolverContext, + String.class, "spring.cloud.polaris.service"); + } + if (StringUtils.isBlank(serviceName)) { + serviceName = loadPolarisConfigProperties(resolverContext, + String.class, "spring.application.name"); + } if (StringUtils.isBlank(serviceName)) { serviceName = "application"; log.warn("No spring.application.name found, defaulting to 'application'"); @@ -233,6 +254,7 @@ public class PolarisConfigDataLocationResolver implements polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties, + tsfTlsProperties, profiles, optional, fileName, groupName, serviceName ); diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataResource.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataResource.java index a6b740950..5d396abd1 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataResource.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/configdata/PolarisConfigDataResource.java @@ -22,6 +22,7 @@ import java.util.Objects; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.config.PolarisCryptoConfigProperties; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; +import com.tencent.cloud.polaris.context.config.extend.tsf.TsfTlsProperties; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataResource; @@ -40,6 +41,8 @@ public class PolarisConfigDataResource extends ConfigDataResource { private final PolarisContextProperties polarisContextProperties; + private final TsfTlsProperties tsfTlsProperties; + private final Profiles profiles; private final boolean optional; @@ -53,11 +56,13 @@ public class PolarisConfigDataResource extends ConfigDataResource { public PolarisConfigDataResource(PolarisConfigProperties polarisConfigProperties, PolarisCryptoConfigProperties polarisCryptoConfigProperties, PolarisContextProperties polarisContextProperties, + TsfTlsProperties tsfTlsProperties, Profiles profiles, boolean optional, String fileName, String groupName, String serviceName) { this.polarisConfigProperties = polarisConfigProperties; this.polarisCryptoConfigProperties = polarisCryptoConfigProperties; this.polarisContextProperties = polarisContextProperties; + this.tsfTlsProperties = tsfTlsProperties; this.profiles = profiles; this.optional = optional; this.fileName = fileName; @@ -77,6 +82,10 @@ public class PolarisConfigDataResource extends ConfigDataResource { return polarisContextProperties; } + public TsfTlsProperties getTsfTlsProperties() { + return tsfTlsProperties; + } + public Profiles getProfiles() { return profiles; } @@ -110,6 +119,7 @@ public class PolarisConfigDataResource extends ConfigDataResource { polarisConfigProperties.equals(that.polarisConfigProperties) && polarisCryptoConfigProperties.equals(that.polarisCryptoConfigProperties) && polarisContextProperties.equals(that.polarisContextProperties) && + tsfTlsProperties.equals(that.tsfTlsProperties) && profiles.equals(that.profiles) && fileName.equals(that.fileName) && groupName.equals(that.groupName) && @@ -118,7 +128,7 @@ public class PolarisConfigDataResource extends ConfigDataResource { @Override public int hashCode() { - return Objects.hash(polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties, profiles, - optional, fileName, groupName, serviceName); + return Objects.hash(polarisConfigProperties, polarisCryptoConfigProperties, polarisContextProperties, + tsfTlsProperties, profiles, optional, fileName, groupName, serviceName); } } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulDiscoveryConfigModifier.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulDiscoveryConfigModifier.java index 7ac4f11ae..f17cf29fa 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulDiscoveryConfigModifier.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/extend/consul/ConsulDiscoveryConfigModifier.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.Map; import com.tencent.cloud.common.constant.OrderConstant; -import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.context.PolarisConfigModifier; import com.tencent.cloud.polaris.context.config.extend.consul.ConsulProperties; @@ -126,10 +125,6 @@ public class ConsulDiscoveryConfigModifier implements PolarisConfigModifier { String.valueOf(consulDiscoveryProperties.isPreferIpAddress())); metadata.put(ConsulConstant.MetadataMapKey.IP_ADDRESS_KEY, consulDiscoveryProperties.getHostname()); } - if (tsfCoreProperties != null) { - // tags - metadata.put(ConsulConstant.MetadataMapKey.TAGS_KEY, JacksonUtils.serialize2Json(tsfCoreProperties.getTsfTags())); - } configuration.getGlobal().getServerConnectors().add(serverConnectorConfig); // discovery diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfDiscoveryRegistryAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfDiscoveryRegistryAutoConfiguration.java index 137aab48c..0bcc9c3c7 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfDiscoveryRegistryAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfDiscoveryRegistryAutoConfiguration.java @@ -18,6 +18,7 @@ package com.tencent.cloud.polaris.registry.tsf; import com.tencent.cloud.common.tsf.ConditionalOnTsfConsulEnabled; +import com.tencent.cloud.polaris.PolarisDiscoveryProperties; import com.tencent.cloud.polaris.context.PolarisSDKContextManager; import com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreProperties; import com.tencent.cloud.polaris.extend.consul.ConsulDiscoveryProperties; @@ -68,4 +69,11 @@ public class TsfDiscoveryRegistryAutoConfiguration { public TsfApiPolarisRegistrationCustomizer tsfApiPolarisRegistrationCustomizer(ApplicationContext context) { return new TsfApiPolarisRegistrationCustomizer(context); } + + @Bean + @ConditionalOnMissingBean + public TsfTagsRegistrationCustomizer tsfTagsRegistrationCustomizer(TsfCoreProperties tsfCoreProperties, + PolarisDiscoveryProperties polarisDiscoveryProperties) { + return new TsfTagsRegistrationCustomizer(tsfCoreProperties, polarisDiscoveryProperties); + } } diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfTagsRegistrationCustomizer.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfTagsRegistrationCustomizer.java new file mode 100644 index 000000000..3e6b8f402 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/tsf/TsfTagsRegistrationCustomizer.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.registry.tsf; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import com.tencent.cloud.polaris.PolarisDiscoveryProperties; +import com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreProperties; +import com.tencent.cloud.polaris.registry.PolarisRegistration; +import com.tencent.cloud.polaris.registry.PolarisRegistrationCustomizer; +import com.tencent.polaris.api.utils.StringUtils; + +import static com.tencent.polaris.plugins.connector.common.constant.ConsulConstant.MetadataMapKey.TAGS_KEY; + +/** + * @author Haotian Zhang + */ +public class TsfTagsRegistrationCustomizer implements PolarisRegistrationCustomizer { + + private final TsfCoreProperties tsfCoreProperties; + + private final PolarisDiscoveryProperties polarisDiscoveryProperties; + + public TsfTagsRegistrationCustomizer(TsfCoreProperties tsfCoreProperties, PolarisDiscoveryProperties polarisDiscoveryProperties) { + this.tsfCoreProperties = tsfCoreProperties; + this.polarisDiscoveryProperties = polarisDiscoveryProperties; + } + + @Override + public void customize(PolarisRegistration registration) { + if (tsfCoreProperties == null) { + return; + } + + String protocol = ApplicationContextAwareUtils.getProperties("tsf.discovery.scheme", + ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.discovery.protocol", "http")); + if (StringUtils.isNotBlank(protocol)) { + tsfCoreProperties.setScheme(protocol); + polarisDiscoveryProperties.setProtocol(protocol); + } + Map metadata = registration.getExtendedMetadata() + .computeIfAbsent(TAGS_KEY, k -> new HashMap<>()); + for (String tag : tsfCoreProperties.getTsfTags()) { + metadata.put(TAGS_KEY, tag); + } + } +} diff --git a/spring-cloud-tencent-coverage/pom.xml b/spring-cloud-tencent-coverage/pom.xml index 689000c07..5919c69ca 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -106,6 +106,12 @@ spring-cloud-starter-tencent-fault-injection-plugin ${revision} + + + com.tencent.cloud + spring-cloud-starter-tencent-tsf-tls-plugin + ${revision} + diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 053d14568..5e99e5205 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -214,6 +214,12 @@ ${revision} + + com.tencent.cloud + spring-cloud-starter-tencent-tsf-tls-plugin + ${revision} + + org.springdoc diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo/pom.xml b/spring-cloud-tencent-examples/tsf-example/consumer-demo/pom.xml index 026d8b065..b6e40cfb6 100644 --- a/spring-cloud-tencent-examples/tsf-example/consumer-demo/pom.xml +++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo/pom.xml @@ -31,5 +31,10 @@ org.springframework.cloud spring-cloud-starter-bootstrap + + + io.github.openfeign + feign-hc5 + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java index 87694e5b2..6c6871337 100644 --- a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java +++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/java/com/tencent/cloud/tsf/demo/consumer/ConsumerApplication.java @@ -17,11 +17,30 @@ package com.tencent.cloud.tsf.demo.consumer; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; + +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.ssl.SSLContextBuilder; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.tsf.annotation.EnableTsf; import org.springframework.web.client.RestTemplate; @@ -33,9 +52,43 @@ public class ConsumerApplication { SpringApplication.run(ConsumerApplication.class, args); } - @LoadBalanced @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); + @RefreshScope + @ConditionalOnProperty(value = "server.ssl.bundle", havingValue = "tsf") + public HttpClientConnectionManager connectionManagerWithSSL(SslBundles sslBundles) { + SSLContext sslContext = sslBundles.getBundle("tsf").createSslContext(); + SSLContext.setDefault(sslContext); + return PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(new SSLConnectionSocketFactory( + sslContext, + NoopHostnameVerifier.INSTANCE + )) + .build(); + } + + @Bean + @ConditionalOnExpression("'${server.ssl.bundle:}' != 'tsf'") + public HttpClientConnectionManager connectionManager() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + SSLContext sslContext = SSLContextBuilder.create() + .loadTrustMaterial(null, (chain, authType) -> true) + .build(); + return PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(new SSLConnectionSocketFactory( + sslContext, + NoopHostnameVerifier.INSTANCE + )) + .build(); + } + + @Bean + @LoadBalanced + public RestTemplate restTemplate( + RestTemplateBuilder builder, HttpClientConnectionManager connectionManager) { + return builder + .requestFactory(() -> new HttpComponentsClientHttpRequestFactory( + HttpClients.custom() + .setConnectionManager(connectionManager) + .build())) + .build(); } } diff --git a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/resources/bootstrap.yml index eb7a01b09..7236c2952 100644 --- a/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/tsf-example/consumer-demo/src/main/resources/bootstrap.yml @@ -3,8 +3,6 @@ server: spring: application: name: consumer-demo - config: - import: optional:polaris feign: tsf: diff --git a/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java b/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java index bdb9766d8..5f119aafe 100644 --- a/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java +++ b/spring-cloud-tencent-examples/tsf-example/msgw-scg/src/main/java/com/tencent/cloud/tsf/msgw/scg/ScgApplication.java @@ -17,8 +17,23 @@ package com.tencent.cloud.tsf.msgw.scg; +import java.time.Duration; + +import javax.net.ssl.SSLException; + +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Bean; import org.springframework.tsf.annotation.EnableTsf; /** @@ -31,4 +46,29 @@ public class ScgApplication { public static void main(String[] args) { SpringApplication.run(ScgApplication.class, args); } + + @Bean + @RefreshScope + @ConditionalOnProperty(value = "server.ssl.bundle", havingValue = "tsf") + public HttpClient httpClient(SslBundles sslBundles) throws SSLException { + // 自定义连接池 + ConnectionProvider connectionProvider = ConnectionProvider.builder("customPool") + .maxConnections(500) + .pendingAcquireTimeout(Duration.ofSeconds(10)) + .build(); + + // 配置 SSL 上下文 + SslBundle sslBundle = sslBundles.getBundle("tsf"); + + SslContext sslContext = SslContextBuilder.forClient() + .keyManager(sslBundle.getManagers().getKeyManagerFactory()) + .trustManager(sslBundle.getManagers().getTrustManagerFactory()) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + + // 自定义 HttpClient + return HttpClient.create(connectionProvider) + .responseTimeout(Duration.ofSeconds(5)) + .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext)); + } } diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml index 28f294aa6..149ece160 100644 --- a/spring-cloud-tencent-plugin-starters/pom.xml +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -24,6 +24,7 @@ spring-cloud-starter-tencent-multi-discovery-plugin spring-cloud-starter-tencent-traffic-mirroring-plugin spring-cloud-starter-tencent-fault-injection-plugin + spring-cloud-starter-tencent-tsf-tls-plugin diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/pom.xml new file mode 100644 index 000000000..70f179f41 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/pom.xml @@ -0,0 +1,20 @@ + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + spring-cloud-starter-tencent-tsf-tls-plugin + Spring Cloud Tencent TSF TLS Plugin + + + + + com.tencent.cloud + spring-cloud-tencent-rpc-enhancement + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/TlsEnvironmentPostProcessor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/TlsEnvironmentPostProcessor.java new file mode 100644 index 000000000..75121652e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/TlsEnvironmentPostProcessor.java @@ -0,0 +1,100 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.plugin.tsf.tls; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.plugin.tsf.tls.utils.SyncUtils; +import com.tencent.cloud.polaris.context.config.extend.tsf.TsfTlsProperties; +import com.tencent.polaris.api.utils.ClassUtils; +import com.tencent.polaris.api.utils.StringUtils; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; + +/** + * Environment post processor for polaris tls. + * + * @author Haotian Zhang + */ +public class TlsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + String address = environment.getProperty("MESH_CITADEL_ADDR"); + if (StringUtils.isNotBlank(address) + && StringUtils.equals("tsf", environment.getProperty("server.ssl.bundle")) + && ClassUtils.isClassPresent("com.tencent.cloud.plugin.tsf.tls.utils.SyncUtils") + && !ClassUtils.isClassPresent("com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator")) { + // get common name + String commonName = environment.getProperty("spring.cloud.polaris.service"); + if (StringUtils.isBlank(commonName)) { + commonName = environment.getProperty("spring.cloud.polaris.discovery.service"); + } + if (StringUtils.isBlank(commonName)) { + commonName = environment.getProperty("spring.application.name"); + } + // get certPath + String certPath = environment.getProperty("MESH_CITADEL_CERT"); + // get token + String token = environment.getProperty("tsf_token"); + // get validityDuration + Long validityDuration = environment.getProperty("spring.cloud.polaris.tls.validityDuration", Long.class, TsfTlsProperties.DEFAULT_VALIDITY_DURATION); + // get refreshBefore + Long refreshBefore = environment.getProperty("spring.cloud.polaris.tls.refreshBefore", Long.class, TsfTlsProperties.DEFAULT_REFRESH_BEFORE); + // get watchInterval + Long watchInterval = environment.getProperty("spring.cloud.polaris.tls.watchInterval", Long.class, TsfTlsProperties.DEFAULT_WATCH_INTERVAL); + SyncUtils.init(commonName, address, certPath, token, validityDuration, refreshBefore, watchInterval); + System.setProperty("server.ssl.bundle", "tsf"); + if (SyncUtils.isVerified()) { + Map tlsEnvProperties = new HashMap<>(); + // set ssl + String clientAuth = environment.getProperty("server.ssl.client-auth", "want"); + tlsEnvProperties.put("server.ssl.client-auth", clientAuth); + System.setProperty("server.ssl.client-auth", clientAuth); + String protocol = environment.getProperty("spring.cloud.polaris.discovery.protocol", "https"); + tlsEnvProperties.put("spring.cloud.polaris.discovery.protocol", protocol); + System.setProperty("spring.cloud.polaris.discovery.protocol", protocol); + tlsEnvProperties.put("tsf.discovery.scheme", protocol); + System.setProperty("tsf.discovery.scheme", protocol); + + // set tsf spring ssl bundle + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.reload-on-update", "true"); + if (StringUtils.isNotBlank(SyncUtils.getPemKeyStoreCertPath()) && StringUtils.isNotBlank(SyncUtils.getPemKeyStoreKeyPath())) { + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.keystore.certificate", SyncUtils.getPemKeyStoreCertPath()); + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.keystore.private-key", SyncUtils.getPemKeyStoreKeyPath()); + } + if (StringUtils.isNotBlank(SyncUtils.getPemTrustStoreCertPath())) { + tlsEnvProperties.put("spring.ssl.bundle.pem.tsf.truststore.certificate", SyncUtils.getPemTrustStoreCertPath()); + } + + // process environment + MapPropertySource propertySource = new MapPropertySource("tsf-tls-properties", tlsEnvProperties); + environment.getPropertySources().addFirst(propertySource); + } + } + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/TlsReadyApplicationListener.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/TlsReadyApplicationListener.java new file mode 100644 index 000000000..39a248a3a --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/TlsReadyApplicationListener.java @@ -0,0 +1,63 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.plugin.tsf.tls; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.ssl.NoSuchSslBundleException; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.ApplicationListener; + +/** + * Application listener for tls init. + * + * @author Haotian Zhang + */ +public class TlsReadyApplicationListener implements ApplicationListener { + + private static final Logger log = LoggerFactory.getLogger(TlsReadyApplicationListener.class); + + private final AtomicBoolean isSet = new AtomicBoolean(false); + + @Override + public void onApplicationEvent(@NotNull ApplicationStartedEvent event) { + SslBundles sslBundles = ApplicationContextAwareUtils.getBeanIfExists(SslBundles.class); + ContextRefresher contextRefresher = ApplicationContextAwareUtils.getBeanIfExists(ContextRefresher.class); + try { + if (sslBundles != null && contextRefresher != null && isSet.compareAndSet(false, true)) { + try { + sslBundles.getBundle("tsf"); + sslBundles.addBundleUpdateHandler("tsf", sslBundle -> contextRefresher.refresh()); + } + catch (NoSuchSslBundleException e) { + log.warn("tsf ssl bundle is not registered."); + } + } + } + catch (Throwable throwable) { + log.warn("tsf ssl bundle is not registered correctly.", throwable); + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/config/TsfTlsAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/config/TsfTlsAutoConfiguration.java new file mode 100644 index 000000000..295736c33 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/config/TsfTlsAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.plugin.tsf.tls.config; + +import com.tencent.cloud.plugin.tsf.tls.TlsReadyApplicationListener; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration for TSF TLS. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "server.ssl.bundle", havingValue = "tsf") +public class TsfTlsAutoConfiguration { + + @Bean + public TlsReadyApplicationListener tlsReadyApplicationListener() { + return new TlsReadyApplicationListener(); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/utils/SyncUtils.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/utils/SyncUtils.java new file mode 100644 index 000000000..fb7823dc5 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/java/com/tencent/cloud/plugin/tsf/tls/utils/SyncUtils.java @@ -0,0 +1,147 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.plugin.tsf.tls.utils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import com.tencent.polaris.api.config.ConfigProvider; +import com.tencent.polaris.api.config.consumer.OutlierDetectionConfig; +import com.tencent.polaris.api.config.plugin.DefaultPlugins; +import com.tencent.polaris.api.plugin.certificate.CertFile; +import com.tencent.polaris.api.plugin.certificate.CertFileKey; +import com.tencent.polaris.api.utils.CollectionUtils; +import com.tencent.polaris.api.utils.StringUtils; +import com.tencent.polaris.certificate.api.core.CertificateAPI; +import com.tencent.polaris.certificate.factory.CertificateAPIFactory; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.ConfigAPIFactory; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import com.tencent.polaris.plugins.certificate.tsf.TsfCertificateManagerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utils for sync. + * + * @author Haotian Zhang + */ +public final class SyncUtils { + + private static final Logger log = LoggerFactory.getLogger(SyncUtils.class); + private static final Map pemFileMap = new HashMap<>(); + private volatile static SDKContext certificateSDKContext; + private volatile static CertificateAPI certificateAPI; + + private SyncUtils() { + + } + + public static void init(String commonName, String address, String certPath, String token, Long validityDuration, + Long refreshBefore, Long watchInterval) { + log.info("begin init SyncUtils with commonName: {}, address: {}, certPath: {}, token: {}", commonName, address, certPath, token); + try { + if (!isInitialized() && StringUtils.isNotBlank(commonName) && StringUtils.isNotBlank(address)) { + initCertificateSDKContext(commonName, address, certPath, token, validityDuration, refreshBefore, watchInterval); + log.info("init SyncUtils with commonName: {}, address: {}, certPath: {}, token: {} successfully", commonName, address, certPath, token); + } + } + catch (Throwable throwable) { + log.error("init SyncUtils with commonName: {}, address: {}, certPath: {}, token: {} failed.", commonName, address, certPath, token, throwable); + } + } + + private static void initCertificateSDKContext(String commonName, String address, String certPath, String token, + Long validityDuration, Long refreshBefore, Long watchInterval) { + // 1. Read user-defined polaris.yml configuration + ConfigurationImpl configuration = (ConfigurationImpl) ConfigAPIFactory + .defaultConfig(ConfigProvider.DEFAULT_CONFIG); + + // 2. Override user-defined polaris.yml configuration with SCT configuration + configuration.getGlobal().getAPI().setReportEnable(false); + configuration.getGlobal().getStatReporter().setEnable(false); + configuration.getConsumer().getOutlierDetection().setWhen(OutlierDetectionConfig.When.never); + configuration.getGlobal().getCertificate().setEnable(true); + configuration.getGlobal().getCertificate().setCommonName(commonName); + configuration.getGlobal().getCertificate().setPluginName(DefaultPlugins.TSF_CERTIFICATE_MANAGER); + if (validityDuration != null) { + configuration.getGlobal().getCertificate().setValidityDuration(validityDuration); + } + if (refreshBefore != null) { + configuration.getGlobal().getCertificate().setRefreshBefore(refreshBefore); + } + if (watchInterval != null) { + configuration.getGlobal().getCertificate().setWatchInterval(watchInterval); + } + TsfCertificateManagerConfig tsfCertificateManagerConfig = new TsfCertificateManagerConfig(); + tsfCertificateManagerConfig.setAddress(address); + tsfCertificateManagerConfig.setCertPath(certPath); + tsfCertificateManagerConfig.setToken(token); + configuration.getGlobal().getCertificate() + .setPluginConfig(DefaultPlugins.TSF_CERTIFICATE_MANAGER, tsfCertificateManagerConfig); + + certificateSDKContext = SDKContext.initContextByConfig(configuration); + certificateSDKContext.init(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + if (Objects.nonNull(certificateSDKContext)) { + certificateSDKContext.destroy(); + certificateSDKContext = null; + } + log.info("Polaris SDK certificate context is destroyed."); + } + catch (Throwable throwable) { + log.info("Polaris SDK certificate context is destroyed failed.", throwable); + } + })); + log.info("create Polaris certificate SDK context successfully."); + + certificateAPI = CertificateAPIFactory.createCertificateAPIByContext(certificateSDKContext); + pemFileMap.putAll(certificateAPI.getPemFileMap()); + } + + public static boolean isInitialized() { + return certificateSDKContext != null && certificateAPI != null; + } + + public static boolean isVerified() { + return isInitialized() && CollectionUtils.isNotEmpty(certificateAPI.getPemFileMap()); + } + + public static String getPemKeyStoreCertPath() { + if (pemFileMap.containsKey(CertFileKey.PemKeyStoreCertPath)) { + return pemFileMap.get(CertFileKey.PemKeyStoreCertPath).getPath(); + } + return null; + } + + public static String getPemKeyStoreKeyPath() { + if (pemFileMap.containsKey(CertFileKey.PrivateKeyFile)) { + return pemFileMap.get(CertFileKey.PrivateKeyFile).getPath(); + } + return null; + } + + public static String getPemTrustStoreCertPath() { + if (pemFileMap.containsKey(CertFileKey.PemTrustStoreCertPath)) { + return pemFileMap.get(CertFileKey.PemTrustStoreCertPath).getPath(); + } + return null; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..9fe401eda --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + com.tencent.cloud.plugin.tsf.tls.TlsEnvironmentPostProcessor diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..fddd05434 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-tsf-tls-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.tencent.cloud.plugin.tsf.tls.config.TsfTlsAutoConfiguration diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextAutoConfiguration.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextAutoConfiguration.java index 9c8bd38ae..d06323720 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextAutoConfiguration.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextAutoConfiguration.java @@ -29,6 +29,7 @@ import com.tencent.cloud.polaris.context.admin.PolarisAdminProperties; import com.tencent.cloud.polaris.context.config.extend.consul.ConsulProperties; import com.tencent.cloud.polaris.context.config.extend.tsf.TsfCoreProperties; import com.tencent.cloud.polaris.context.config.extend.tsf.TsfInstanceMetadataProvider; +import com.tencent.cloud.polaris.context.config.extend.tsf.TsfTlsProperties; import com.tencent.cloud.polaris.context.event.PushGatewayEventReporterConfigModifier; import com.tencent.cloud.polaris.context.event.PushGatewayEventReporterProperties; import com.tencent.cloud.polaris.context.listener.PolarisContextApplicationEventListener; @@ -113,6 +114,12 @@ public class PolarisContextAutoConfiguration { return new TsfCoreProperties(); } + @Bean + @ConditionalOnMissingBean + public TsfTlsProperties tsfTlsProperties() { + return new TsfTlsProperties(); + } + @Bean @ConditionalOnMissingBean public TsfInstanceMetadataProvider tsfInstanceMetadataProvider(TsfCoreProperties tsfCoreProperties) { diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextProperties.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextProperties.java index 25f5a891a..6c89fa366 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextProperties.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/PolarisContextProperties.java @@ -89,6 +89,8 @@ public class PolarisContextProperties { @Value("${spring.cloud.polaris.service:${spring.application.name:}}") private String service; + private Long apiTimeout = 1000L; + public Configuration configuration(List modifierList, Supplier ipAddressSupplier, Supplier portSupplier) { // 1. Read user-defined polaris.yml configuration ConfigurationImpl configuration = (ConfigurationImpl) ConfigAPIFactory @@ -105,6 +107,7 @@ public class PolarisContextProperties { } configuration.getGlobal().getAPI().setBindIP(defaultHost); + configuration.getGlobal().getAPI().setTimeout(apiTimeout); Collection modifiers = modifierList; modifiers = modifiers.stream() @@ -183,6 +186,14 @@ public class PolarisContextProperties { this.service = service; } + public Long getApiTimeout() { + return apiTimeout; + } + + public void setApiTimeout(Long apiTimeout) { + this.apiTimeout = apiTimeout; + } + @Override public String toString() { return "PolarisContextProperties{" + @@ -194,6 +205,7 @@ public class PolarisContextProperties { ", enabled=" + enabled + ", namespace='" + namespace + '\'' + ((StringUtils.isBlank(this.service)) ? "" : ", service='" + service + '\'') + + ", apiTimeout=" + apiTimeout + '}'; } } diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreProperties.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreProperties.java index 8fa0afd88..705a2df1d 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreProperties.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfCoreProperties.java @@ -223,8 +223,6 @@ public class TsfCoreProperties { if (StringUtils.isNotBlank(getInstanceGroup())) { tags.add("group=" + getInstanceGroup()); } - //store the secure flag in the tags so that clients will be able to figure out whether to use http or https automatically - tags.add("secure=" + getScheme().equalsIgnoreCase("https")); return tags; } diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfTlsProperties.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfTlsProperties.java new file mode 100644 index 000000000..8dd746fc1 --- /dev/null +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/config/extend/tsf/TsfTlsProperties.java @@ -0,0 +1,112 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.context.config.extend.tsf; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties for polaris tls. + * + * @author Haotian Zhang + */ +@ConfigurationProperties(prefix = "spring.cloud.polaris.tls") +public class TsfTlsProperties { + + /** + * default validity duration. + */ + public static final long DEFAULT_VALIDITY_DURATION = 30 * 24 * 60 * 60 * 1000L; + /** + * default refresh before. + */ + public static final long DEFAULT_REFRESH_BEFORE = 24 * 60 * 60 * 1000L; + /** + * default watch interval. + */ + public static final long DEFAULT_WATCH_INTERVAL = 60 * 60 * 1000L; + + private Long validityDuration = DEFAULT_VALIDITY_DURATION; + + private Long refreshBefore = DEFAULT_REFRESH_BEFORE; + + private Long watchInterval = DEFAULT_WATCH_INTERVAL; + + private Tsf tsf = new Tsf(); + + public Long getValidityDuration() { + return validityDuration; + } + + public void setValidityDuration(Long validityDuration) { + this.validityDuration = validityDuration; + } + + public Long getRefreshBefore() { + return refreshBefore; + } + + public void setRefreshBefore(Long refreshBefore) { + this.refreshBefore = refreshBefore; + } + + public Long getWatchInterval() { + return watchInterval; + } + + public void setWatchInterval(Long watchInterval) { + this.watchInterval = watchInterval; + } + + public Tsf getTsf() { + return tsf; + } + + public void setTsf(Tsf tsf) { + this.tsf = tsf; + } + + @Override + public String toString() { + return "TsfTlsProperties{" + + "validityDuration=" + validityDuration + + ", refreshBefore=" + refreshBefore + + ", watchInterval=" + watchInterval + + ", tsf=" + tsf + + '}'; + } + + public static class Tsf { + + private String address; + + public String getAddress() { + return address; + } + + void setAddress(String address) { + this.address = address; + } + + @Override + public String toString() { + return "Tsf{" + + "address='" + address + '\'' + + '}'; + } + } +}