Skip to content

Commit

Permalink
[ISSUE alibaba#3315] nacos client support https
Browse files Browse the repository at this point in the history
* common module add tls related classes
* JdkHttpClientRequest support https
* unified IpUtils
  • Loading branch information
wangweizZZ committed Aug 18, 2020
1 parent e7c269a commit 2391453
Show file tree
Hide file tree
Showing 8 changed files with 490 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.alibaba.nacos.client.config.utils;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.utils.IpUtil;
import com.alibaba.nacos.common.utils.IpUtils;
import com.alibaba.nacos.common.utils.StringUtils;

import java.util.List;
Expand Down Expand Up @@ -190,7 +190,7 @@ public static void checkBetaIps(String betaIps) throws NacosException {
}
String[] ipsArr = betaIps.split(",");
for (String ip : ipsArr) {
if (!IpUtil.isIpv4(ip)) {
if (!IpUtils.isIpv4(ip)) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "betaIps invalid");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,22 @@
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.MediaType;
import com.alibaba.nacos.common.model.RequestHttpEntity;
import com.alibaba.nacos.common.tls.SelfHostnameVerifier;
import com.alibaba.nacos.common.tls.SelfTrustManager;
import com.alibaba.nacos.common.tls.TlsFileWatcher;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.tls.TlsSystemConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -39,10 +50,47 @@
*/
public class JdkHttpClientRequest implements HttpClientRequest {

private static final Logger LOGGER = LoggerFactory.getLogger(JdkHttpClientRequest.class);

private HttpClientConfig httpClientConfig;

public JdkHttpClientRequest(HttpClientConfig httpClientConfig) {
this.httpClientConfig = httpClientConfig;
loadSSLContext();
replaceHostnameVerifier();
try {
TlsFileWatcher.getInstance().addFileChangeListener(new TlsFileWatcher.FileChangeListener() {
@Override
public void onChanged(String filePath) {
loadSSLContext();
}
}, TlsSystemConfig.tlsClientTrustCertPath);
} catch (IOException e) {
LOGGER.error("add tls file listener fail", e);
}
}

@SuppressWarnings("checkstyle:abbreviationaswordinname")
private void loadSSLContext() {
if (TlsSystemConfig.tlsEnable) {
try {
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, SelfTrustManager
.trustManager(TlsSystemConfig.tlsClientAuthServer, TlsSystemConfig.tlsClientTrustCertPath),
new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());

} catch (NoSuchAlgorithmException e) {
LOGGER.error("Failed to create SSLContext", e);
} catch (KeyManagementException e) {
LOGGER.error("Failed to create SSLContext", e);
}
}
}

private void replaceHostnameVerifier() {
final HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(new SelfHostnameVerifier(hv));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.alibaba.nacos.common.tls;

import com.alibaba.nacos.common.utils.IpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.util.concurrent.ConcurrentHashMap;

/**
* A HostnameVerifier verify ipv4 and localhost.
*
* @author wangwei
*/

public final class SelfHostnameVerifier implements HostnameVerifier {

private static final Logger LOGGER = LoggerFactory.getLogger(SelfHostnameVerifier.class);

private final HostnameVerifier hv;

private static ConcurrentHashMap<String, Boolean> hosts = new ConcurrentHashMap<String, Boolean>();

private static final String[] LOCALHOST_HOSTNAME = new String[] {"localhost", "127.0.0.1"};

public SelfHostnameVerifier(HostnameVerifier hv) {
this.hv = hv;
}

@Override
public boolean verify(String hostname, SSLSession session) {
if (LOCALHOST_HOSTNAME[0].equalsIgnoreCase(hostname) || LOCALHOST_HOSTNAME[1].equals(hostname)) {
return true;
}
if (isIpv4(hostname)) {
return true;
}
return hv.verify(hostname, session);
}

private static boolean isIpv4(String host) {
if (host == null || host.isEmpty()) {
LOGGER.warn("host is empty, isIPv4 = false");
return false;
}
Boolean cacheHostVerify = hosts.get(host);
if (cacheHostVerify != null) {
return cacheHostVerify;
}
boolean isIp = IpUtils.isIpv4(host);
hosts.putIfAbsent(host, isIp);
return isIp;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.alibaba.nacos.common.tls;

import com.alibaba.nacos.common.utils.IoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;

/**
* A TrustManager tool returns the specified TrustManager.
*
* @author wangwei
*/
public final class SelfTrustManager {

private static final Logger LOGGER = LoggerFactory.getLogger(SelfHostnameVerifier.class);

@SuppressWarnings("checkstyle:WhitespaceAround")
static TrustManager[] trustAll = new TrustManager[] {new X509TrustManager() {

@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}

@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}};

/**
* Returns the result of calling {@link #buildSecureTrustManager} if {@code clientAuth} is enable and {@code
* trustCertPath} exists. Returns the {@link trustAll} otherwise.
*
* @param clientAuth whether need client auth
* @param trustCertPath trust certificate path
* @return Array of {@link TrustManager }
*/
public static TrustManager[] trustManager(boolean clientAuth, String trustCertPath) {
if (clientAuth) {
try {
return trustCertPath == null ? null : buildSecureTrustManager(trustCertPath);
} catch (SSLException e) {
LOGGER.warn("degrade trust manager as build failed, " + "will trust all certs.");
return trustAll;
}
} else {
return trustAll;
}
}

private static TrustManager[] buildSecureTrustManager(String trustCertPath) throws SSLException {
TrustManagerFactory selfTmf = null;
InputStream in = null;

try {
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
selfTmf = TrustManagerFactory.getInstance(algorithm);

KeyStore trustKeyStore = KeyStore.getInstance("JKS");
trustKeyStore.load(null, null);

in = new FileInputStream(trustCertPath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");

Collection<X509Certificate> certs = (Collection<X509Certificate>) cf.generateCertificates(in);
int count = 0;
for (Certificate cert : certs) {
trustKeyStore.setCertificateEntry("cert-" + (count++), cert);
}
selfTmf.init(trustKeyStore);
return selfTmf.getTrustManagers();
} catch (Exception e) {
LOGGER.error("build client trustManagerFactory failed", e);
throw new SSLException(e);
} finally {
IoUtils.closeQuietly(in);
}
}
}
Loading

0 comments on commit 2391453

Please sign in to comment.