mirror of https://github.com/grpc/grpc-java.git
xds: add CertProviderSslContextProvider support (#7309)
This commit is contained in:
parent
1c269e4289
commit
39c49b0408
|
|
@ -76,9 +76,10 @@ public abstract class Bootstrapper {
|
||||||
*/
|
*/
|
||||||
public abstract BootstrapInfo readBootstrap() throws IOException;
|
public abstract BootstrapInfo readBootstrap() throws IOException;
|
||||||
|
|
||||||
|
/** Parses a raw string into {@link BootstrapInfo}. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
static BootstrapInfo parseConfig(String rawData) throws IOException {
|
public static BootstrapInfo parseConfig(String rawData) throws IOException {
|
||||||
XdsLogger logger = XdsLogger.withPrefix(LOG_PREFIX);
|
XdsLogger logger = XdsLogger.withPrefix(LOG_PREFIX);
|
||||||
logger.log(XdsLogLevel.INFO, "Reading bootstrap information");
|
logger.log(XdsLogLevel.INFO, "Reading bootstrap information");
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
@ -264,11 +265,11 @@ public abstract class Bootstrapper {
|
||||||
this.config = checkNotNull(config, "config");
|
this.config = checkNotNull(config, "config");
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPluginName() {
|
public String getPluginName() {
|
||||||
return pluginName;
|
return pluginName;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, ?> getConfig() {
|
public Map<String, ?> getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,8 @@ final class EnvoyProtoData {
|
||||||
return listeningAddresses;
|
return listeningAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() {
|
@VisibleForTesting
|
||||||
|
public io.envoyproxy.envoy.config.core.v3.Node toEnvoyProtoNode() {
|
||||||
io.envoyproxy.envoy.config.core.v3.Node.Builder builder =
|
io.envoyproxy.envoy.config.core.v3.Node.Builder builder =
|
||||||
io.envoyproxy.envoy.config.core.v3.Node.newBuilder();
|
io.envoyproxy.envoy.config.core.v3.Node.newBuilder();
|
||||||
builder.setId(id);
|
builder.setId(id);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal.certprovider;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import io.envoyproxy.envoy.config.core.v3.Node;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
|
||||||
|
import io.grpc.netty.GrpcSslContexts;
|
||||||
|
import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
|
||||||
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
|
import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import java.security.cert.CertStoreException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** A client SslContext provider using CertificateProviderInstance to fetch secrets. */
|
||||||
|
final class CertProviderClientSslContextProvider extends CertProviderSslContextProvider {
|
||||||
|
|
||||||
|
private CertProviderClientSslContextProvider(
|
||||||
|
Node node,
|
||||||
|
Map<String, CertificateProviderInfo> certProviders,
|
||||||
|
CommonTlsContext.CertificateProviderInstance certInstance,
|
||||||
|
CommonTlsContext.CertificateProviderInstance rootCertInstance,
|
||||||
|
CertificateValidationContext staticCertValidationContext,
|
||||||
|
UpstreamTlsContext upstreamTlsContext,
|
||||||
|
CertificateProviderStore certificateProviderStore) {
|
||||||
|
super(
|
||||||
|
node,
|
||||||
|
certProviders,
|
||||||
|
certInstance,
|
||||||
|
checkNotNull(rootCertInstance, "Client SSL requires rootCertInstance"),
|
||||||
|
staticCertValidationContext,
|
||||||
|
upstreamTlsContext,
|
||||||
|
certificateProviderStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final SslContextBuilder getSslContextBuilder(
|
||||||
|
CertificateValidationContext certificateValidationContextdationContext)
|
||||||
|
throws CertStoreException {
|
||||||
|
SslContextBuilder sslContextBuilder =
|
||||||
|
GrpcSslContexts.forClient()
|
||||||
|
.trustManager(
|
||||||
|
new SdsTrustManagerFactory(
|
||||||
|
savedTrustedRoots.toArray(new X509Certificate[0]),
|
||||||
|
certificateValidationContextdationContext));
|
||||||
|
if (isMtls()) {
|
||||||
|
sslContextBuilder.keyManager(savedKey, savedCertChain);
|
||||||
|
}
|
||||||
|
return sslContextBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates CertProviderClientSslContextProvider. */
|
||||||
|
static final class Factory {
|
||||||
|
private static final Factory DEFAULT_INSTANCE =
|
||||||
|
new Factory(CertificateProviderStore.getInstance());
|
||||||
|
private final CertificateProviderStore certificateProviderStore;
|
||||||
|
|
||||||
|
@VisibleForTesting Factory(CertificateProviderStore certificateProviderStore) {
|
||||||
|
this.certificateProviderStore = certificateProviderStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Factory getInstance() {
|
||||||
|
return DEFAULT_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CertProviderClientSslContextProvider getProvider(
|
||||||
|
UpstreamTlsContext upstreamTlsContext,
|
||||||
|
Node node,
|
||||||
|
Map<String, CertificateProviderInfo> certProviders) {
|
||||||
|
checkNotNull(upstreamTlsContext, "upstreamTlsContext");
|
||||||
|
CommonTlsContext commonTlsContext = upstreamTlsContext.getCommonTlsContext();
|
||||||
|
CommonTlsContext.CertificateProviderInstance rootCertInstance = null;
|
||||||
|
CertificateValidationContext staticCertValidationContext = null;
|
||||||
|
if (commonTlsContext.hasCombinedValidationContext()) {
|
||||||
|
CombinedCertificateValidationContext combinedValidationContext =
|
||||||
|
commonTlsContext.getCombinedValidationContext();
|
||||||
|
if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) {
|
||||||
|
rootCertInstance =
|
||||||
|
combinedValidationContext.getValidationContextCertificateProviderInstance();
|
||||||
|
}
|
||||||
|
if (combinedValidationContext.hasDefaultValidationContext()) {
|
||||||
|
staticCertValidationContext = combinedValidationContext.getDefaultValidationContext();
|
||||||
|
}
|
||||||
|
} else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
|
||||||
|
rootCertInstance = commonTlsContext.getValidationContextCertificateProviderInstance();
|
||||||
|
} else if (commonTlsContext.hasValidationContext()) {
|
||||||
|
staticCertValidationContext = commonTlsContext.getValidationContext();
|
||||||
|
}
|
||||||
|
CommonTlsContext.CertificateProviderInstance certInstance = null;
|
||||||
|
if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
|
||||||
|
certInstance = commonTlsContext.getTlsCertificateCertificateProviderInstance();
|
||||||
|
}
|
||||||
|
return new CertProviderClientSslContextProvider(
|
||||||
|
node,
|
||||||
|
certProviders,
|
||||||
|
certInstance,
|
||||||
|
rootCertInstance,
|
||||||
|
staticCertValidationContext,
|
||||||
|
upstreamTlsContext,
|
||||||
|
certificateProviderStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal.certprovider;
|
||||||
|
|
||||||
|
import io.envoyproxy.envoy.config.core.v3.Node;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance;
|
||||||
|
import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
|
||||||
|
import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext;
|
||||||
|
import io.grpc.xds.internal.sds.DynamicSslContextProvider;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Base class for {@link CertProviderClientSslContextProvider}. */
|
||||||
|
abstract class CertProviderSslContextProvider extends DynamicSslContextProvider implements
|
||||||
|
CertificateProvider.Watcher {
|
||||||
|
|
||||||
|
@Nullable private final CertificateProviderStore.Handle certHandle;
|
||||||
|
@Nullable private final CertificateProviderStore.Handle rootCertHandle;
|
||||||
|
@Nullable private final CertificateProviderInstance certInstance;
|
||||||
|
@Nullable private final CertificateProviderInstance rootCertInstance;
|
||||||
|
@Nullable protected PrivateKey savedKey;
|
||||||
|
@Nullable protected List<X509Certificate> savedCertChain;
|
||||||
|
@Nullable protected List<X509Certificate> savedTrustedRoots;
|
||||||
|
|
||||||
|
protected CertProviderSslContextProvider(
|
||||||
|
Node node,
|
||||||
|
Map<String, CertificateProviderInfo> certProviders,
|
||||||
|
CertificateProviderInstance certInstance,
|
||||||
|
CertificateProviderInstance rootCertInstance,
|
||||||
|
CertificateValidationContext staticCertValidationContext,
|
||||||
|
BaseTlsContext tlsContext,
|
||||||
|
CertificateProviderStore certificateProviderStore) {
|
||||||
|
super(tlsContext, staticCertValidationContext);
|
||||||
|
this.certInstance = certInstance;
|
||||||
|
this.rootCertInstance = rootCertInstance;
|
||||||
|
String certInstanceName = null;
|
||||||
|
if (certInstance != null && certInstance.isInitialized()) {
|
||||||
|
certInstanceName = certInstance.getInstanceName();
|
||||||
|
CertificateProviderInfo certProviderInstanceConfig =
|
||||||
|
getCertProviderConfig(certProviders, certInstanceName);
|
||||||
|
certHandle =
|
||||||
|
certificateProviderStore.createOrGetProvider(
|
||||||
|
certInstance.getCertificateName(),
|
||||||
|
certProviderInstanceConfig.getPluginName(),
|
||||||
|
certProviderInstanceConfig.getConfig(),
|
||||||
|
this,
|
||||||
|
true);
|
||||||
|
} else {
|
||||||
|
certHandle = null;
|
||||||
|
}
|
||||||
|
if (rootCertInstance != null
|
||||||
|
&& rootCertInstance.isInitialized()
|
||||||
|
&& !rootCertInstance.getInstanceName().equals(certInstanceName)) {
|
||||||
|
CertificateProviderInfo certProviderInstanceConfig =
|
||||||
|
getCertProviderConfig(certProviders, rootCertInstance.getInstanceName());
|
||||||
|
rootCertHandle =
|
||||||
|
certificateProviderStore.createOrGetProvider(
|
||||||
|
rootCertInstance.getCertificateName(),
|
||||||
|
certProviderInstanceConfig.getPluginName(),
|
||||||
|
certProviderInstanceConfig.getConfig(),
|
||||||
|
this,
|
||||||
|
true);
|
||||||
|
} else {
|
||||||
|
rootCertHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CertificateProviderInfo getCertProviderConfig(
|
||||||
|
Map<String, CertificateProviderInfo> certProviders, String pluginInstanceName) {
|
||||||
|
return certProviders.get(pluginInstanceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void updateCertificate(PrivateKey key, List<X509Certificate> certChain) {
|
||||||
|
savedKey = key;
|
||||||
|
savedCertChain = certChain;
|
||||||
|
updateSslContextWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void updateTrustedRoots(List<X509Certificate> trustedRoots) {
|
||||||
|
savedTrustedRoots = trustedRoots;
|
||||||
|
updateSslContextWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSslContextWhenReady() {
|
||||||
|
if (isMtls()) {
|
||||||
|
if (savedKey != null && savedTrustedRoots != null) {
|
||||||
|
updateSslContext();
|
||||||
|
clearKeysAndCerts();
|
||||||
|
}
|
||||||
|
} else if (isClientSideTls()) {
|
||||||
|
if (savedTrustedRoots != null) {
|
||||||
|
updateSslContext();
|
||||||
|
clearKeysAndCerts();
|
||||||
|
}
|
||||||
|
} else if (isServerSideTls()) {
|
||||||
|
if (savedKey != null) {
|
||||||
|
updateSslContext();
|
||||||
|
clearKeysAndCerts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearKeysAndCerts() {
|
||||||
|
savedKey = null;
|
||||||
|
savedTrustedRoots = null;
|
||||||
|
savedCertChain = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isMtls() {
|
||||||
|
return certInstance != null && rootCertInstance != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isClientSideTls() {
|
||||||
|
return rootCertInstance != null && certInstance == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isServerSideTls() {
|
||||||
|
return certInstance != null && rootCertInstance == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final CertificateValidationContext generateCertificateValidationContext() {
|
||||||
|
return staticCertificateValidationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void close() {
|
||||||
|
if (certHandle != null) {
|
||||||
|
certHandle.close();
|
||||||
|
}
|
||||||
|
if (rootCertHandle != null) {
|
||||||
|
rootCertHandle.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ public final class CertificateProviderStore {
|
||||||
CertificateProviderProvider certProviderProvider =
|
CertificateProviderProvider certProviderProvider =
|
||||||
certificateProviderRegistry.getProvider(key.pluginName);
|
certificateProviderRegistry.getProvider(key.pluginName);
|
||||||
if (certProviderProvider == null) {
|
if (certProviderProvider == null) {
|
||||||
throw new IllegalArgumentException("Provider not found.");
|
throw new IllegalArgumentException("Provider not found for " + key.pluginName);
|
||||||
}
|
}
|
||||||
CertificateProvider certProvider = certProviderProvider.createCertificateProvider(
|
CertificateProvider certProvider = certProviderProvider.createCertificateProvider(
|
||||||
key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates);
|
key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal.sds;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext;
|
||||||
|
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.cert.CertStoreException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/** Base class for dynamic {@link SslContextProvider}s. */
|
||||||
|
public abstract class DynamicSslContextProvider extends SslContextProvider {
|
||||||
|
|
||||||
|
protected final List<Callback> pendingCallbacks = new ArrayList<>();
|
||||||
|
@Nullable protected final CertificateValidationContext staticCertificateValidationContext;
|
||||||
|
@Nullable protected SslContext sslContext;
|
||||||
|
|
||||||
|
protected DynamicSslContextProvider(
|
||||||
|
BaseTlsContext tlsContext, CertificateValidationContext staticCertValidationContext) {
|
||||||
|
super(tlsContext);
|
||||||
|
this.staticCertificateValidationContext = staticCertValidationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public SslContext getSslContext() {
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract CertificateValidationContext generateCertificateValidationContext();
|
||||||
|
|
||||||
|
/** Gets a server or client side SslContextBuilder. */
|
||||||
|
protected abstract SslContextBuilder getSslContextBuilder(
|
||||||
|
CertificateValidationContext certificateValidationContext)
|
||||||
|
throws CertificateException, IOException, CertStoreException;
|
||||||
|
|
||||||
|
// this gets called only when requested secrets are ready...
|
||||||
|
protected final void updateSslContext() {
|
||||||
|
try {
|
||||||
|
CertificateValidationContext localCertValidationContext =
|
||||||
|
generateCertificateValidationContext();
|
||||||
|
SslContextBuilder sslContextBuilder = getSslContextBuilder(localCertValidationContext);
|
||||||
|
CommonTlsContext commonTlsContext = getCommonTlsContext();
|
||||||
|
if (commonTlsContext != null && commonTlsContext.getAlpnProtocolsCount() > 0) {
|
||||||
|
List<String> alpnList = commonTlsContext.getAlpnProtocolsList();
|
||||||
|
ApplicationProtocolConfig apn =
|
||||||
|
new ApplicationProtocolConfig(
|
||||||
|
ApplicationProtocolConfig.Protocol.ALPN,
|
||||||
|
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
|
||||||
|
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
|
||||||
|
alpnList);
|
||||||
|
sslContextBuilder.applicationProtocolConfig(apn);
|
||||||
|
}
|
||||||
|
List<Callback> pendingCallbacksCopy = null;
|
||||||
|
SslContext sslContextCopy = null;
|
||||||
|
synchronized (pendingCallbacks) {
|
||||||
|
sslContext = sslContextBuilder.build();
|
||||||
|
sslContextCopy = sslContext;
|
||||||
|
pendingCallbacksCopy = clonePendingCallbacksAndClear();
|
||||||
|
}
|
||||||
|
makePendingCallbacks(sslContextCopy, pendingCallbacksCopy);
|
||||||
|
} catch (Exception e) {
|
||||||
|
onError(Status.fromThrowable(e));
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void callPerformCallback(
|
||||||
|
Callback callback, final SslContext sslContextCopy) {
|
||||||
|
performCallback(
|
||||||
|
new SslContextGetter() {
|
||||||
|
@Override
|
||||||
|
public SslContext get() {
|
||||||
|
return sslContextCopy;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void addCallback(Callback callback) {
|
||||||
|
checkNotNull(callback, "callback");
|
||||||
|
// if there is a computed sslContext just send it
|
||||||
|
SslContext sslContextCopy = null;
|
||||||
|
synchronized (pendingCallbacks) {
|
||||||
|
if (sslContext != null) {
|
||||||
|
sslContextCopy = sslContext;
|
||||||
|
} else {
|
||||||
|
pendingCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sslContextCopy != null) {
|
||||||
|
callPerformCallback(callback, sslContextCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final void makePendingCallbacks(
|
||||||
|
SslContext sslContextCopy, List<Callback> pendingCallbacksCopy) {
|
||||||
|
for (Callback callback : pendingCallbacksCopy) {
|
||||||
|
callPerformCallback(callback, sslContextCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Propagates error to all the callback receivers. */
|
||||||
|
public final void onError(Status error) {
|
||||||
|
for (Callback callback : clonePendingCallbacksAndClear()) {
|
||||||
|
callback.onException(error.asException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Callback> clonePendingCallbacksAndClear() {
|
||||||
|
synchronized (pendingCallbacks) {
|
||||||
|
List<Callback> copy = ImmutableList.copyOf(pendingCallbacks);
|
||||||
|
pendingCallbacks.clear();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -90,7 +90,7 @@ final class SdsClientSslContextProvider extends SdsSslContextProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
SslContextBuilder getSslContextBuilder(
|
protected final SslContextBuilder getSslContextBuilder(
|
||||||
CertificateValidationContext localCertValidationContext)
|
CertificateValidationContext localCertValidationContext)
|
||||||
throws CertificateException, IOException, CertStoreException {
|
throws CertificateException, IOException, CertStoreException {
|
||||||
SslContextBuilder sslContextBuilder =
|
SslContextBuilder sslContextBuilder =
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ public final class SdsProtocolNegotiators {
|
||||||
.findOrCreateClientSslContextProvider(upstreamTlsContext);
|
.findOrCreateClientSslContextProvider(upstreamTlsContext);
|
||||||
|
|
||||||
sslContextProvider.addCallback(
|
sslContextProvider.addCallback(
|
||||||
new SslContextProvider.Callback() {
|
new SslContextProvider.Callback(ctx.executor()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSecret(SslContext sslContext) {
|
public void updateSecret(SslContext sslContext) {
|
||||||
|
|
@ -220,8 +220,8 @@ public final class SdsProtocolNegotiators {
|
||||||
public void onException(Throwable throwable) {
|
public void onException(Throwable throwable) {
|
||||||
ctx.fireExceptionCaught(throwable);
|
ctx.fireExceptionCaught(throwable);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ctx.executor());
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -370,7 +370,7 @@ public final class SdsProtocolNegotiators {
|
||||||
}
|
}
|
||||||
final SslContextProvider sslContextProvider = sslContextProviderTemp;
|
final SslContextProvider sslContextProvider = sslContextProviderTemp;
|
||||||
sslContextProvider.addCallback(
|
sslContextProvider.addCallback(
|
||||||
new SslContextProvider.Callback() {
|
new SslContextProvider.Callback(ctx.executor()) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSecret(SslContext sslContext) {
|
public void updateSecret(SslContext sslContext) {
|
||||||
|
|
@ -389,8 +389,8 @@ public final class SdsProtocolNegotiators {
|
||||||
public void onException(Throwable throwable) {
|
public void onException(Throwable throwable) {
|
||||||
ctx.fireExceptionCaught(throwable);
|
ctx.fireExceptionCaught(throwable);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ctx.executor());
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ final class SdsServerSslContextProvider extends SdsSslContextProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
SslContextBuilder getSslContextBuilder(
|
protected final SslContextBuilder getSslContextBuilder(
|
||||||
CertificateValidationContext localCertValidationContext)
|
CertificateValidationContext localCertValidationContext)
|
||||||
throws CertificateException, IOException, CertStoreException {
|
throws CertificateException, IOException, CertStoreException {
|
||||||
SslContextBuilder sslContextBuilder =
|
SslContextBuilder sslContextBuilder =
|
||||||
|
|
|
||||||
|
|
@ -21,27 +21,18 @@ import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import io.envoyproxy.envoy.api.v2.core.Node;
|
import io.envoyproxy.envoy.api.v2.core.Node;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
|
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.Secret;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.Secret;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
|
||||||
import io.netty.handler.ssl.SslContext;
|
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.cert.CertStoreException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** Base class for SdsClientSslContextProvider and SdsServerSslContextProvider. */
|
/** Base class for SdsClientSslContextProvider and SdsServerSslContextProvider. */
|
||||||
abstract class SdsSslContextProvider extends SslContextProvider implements SdsClient.SecretWatcher {
|
abstract class SdsSslContextProvider extends DynamicSslContextProvider implements
|
||||||
|
SdsClient.SecretWatcher {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(SdsSslContextProvider.class.getName());
|
private static final Logger logger = Logger.getLogger(SdsSslContextProvider.class.getName());
|
||||||
|
|
||||||
|
|
@ -49,13 +40,10 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl
|
||||||
@Nullable private final SdsClient validationContextSdsClient;
|
@Nullable private final SdsClient validationContextSdsClient;
|
||||||
@Nullable private final SdsSecretConfig certSdsConfig;
|
@Nullable private final SdsSecretConfig certSdsConfig;
|
||||||
@Nullable private final SdsSecretConfig validationContextSdsConfig;
|
@Nullable private final SdsSecretConfig validationContextSdsConfig;
|
||||||
@Nullable private final CertificateValidationContext staticCertificateValidationContext;
|
|
||||||
private final List<CallbackPair> pendingCallbacks = new ArrayList<>();
|
|
||||||
@Nullable protected TlsCertificate tlsCertificate;
|
@Nullable protected TlsCertificate tlsCertificate;
|
||||||
@Nullable private CertificateValidationContext certificateValidationContext;
|
@Nullable private CertificateValidationContext certificateValidationContext;
|
||||||
@Nullable private SslContext sslContext;
|
|
||||||
|
|
||||||
SdsSslContextProvider(
|
protected SdsSslContextProvider(
|
||||||
Node node,
|
Node node,
|
||||||
SdsSecretConfig certSdsConfig,
|
SdsSecretConfig certSdsConfig,
|
||||||
SdsSecretConfig validationContextSdsConfig,
|
SdsSecretConfig validationContextSdsConfig,
|
||||||
|
|
@ -63,10 +51,9 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl
|
||||||
Executor watcherExecutor,
|
Executor watcherExecutor,
|
||||||
Executor channelExecutor,
|
Executor channelExecutor,
|
||||||
BaseTlsContext tlsContext) {
|
BaseTlsContext tlsContext) {
|
||||||
super(tlsContext);
|
super(tlsContext, staticCertValidationContext);
|
||||||
this.certSdsConfig = certSdsConfig;
|
this.certSdsConfig = certSdsConfig;
|
||||||
this.validationContextSdsConfig = validationContextSdsConfig;
|
this.validationContextSdsConfig = validationContextSdsConfig;
|
||||||
this.staticCertificateValidationContext = staticCertValidationContext;
|
|
||||||
if (certSdsConfig != null && certSdsConfig.isInitialized()) {
|
if (certSdsConfig != null && certSdsConfig.isInitialized()) {
|
||||||
certSdsClient =
|
certSdsClient =
|
||||||
SdsClient.Factory.createSdsClient(certSdsConfig, node, watcherExecutor, channelExecutor);
|
SdsClient.Factory.createSdsClient(certSdsConfig, node, watcherExecutor, channelExecutor);
|
||||||
|
|
@ -87,35 +74,7 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCallback(Callback callback, Executor executor) {
|
public final synchronized void onSecretChanged(Secret secretUpdate) {
|
||||||
checkNotNull(callback, "callback");
|
|
||||||
checkNotNull(executor, "executor");
|
|
||||||
// if there is a computed sslContext just send it
|
|
||||||
SslContext sslContextCopy = sslContext;
|
|
||||||
if (sslContextCopy != null) {
|
|
||||||
callPerformCallback(callback, executor, sslContextCopy);
|
|
||||||
} else {
|
|
||||||
synchronized (pendingCallbacks) {
|
|
||||||
pendingCallbacks.add(new CallbackPair(callback, executor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void callPerformCallback(
|
|
||||||
Callback callback, Executor executor, final SslContext sslContextCopy) {
|
|
||||||
performCallback(
|
|
||||||
new SslContextGetter() {
|
|
||||||
@Override
|
|
||||||
public SslContext get() {
|
|
||||||
return sslContextCopy;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback,
|
|
||||||
executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onSecretChanged(Secret secretUpdate) {
|
|
||||||
checkNotNull(secretUpdate);
|
checkNotNull(secretUpdate);
|
||||||
if (secretUpdate.hasTlsCertificate()) {
|
if (secretUpdate.hasTlsCertificate()) {
|
||||||
checkState(
|
checkState(
|
||||||
|
|
@ -143,35 +102,8 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a server or client side SslContextBuilder. */
|
@Override
|
||||||
abstract SslContextBuilder getSslContextBuilder(
|
protected final CertificateValidationContext generateCertificateValidationContext() {
|
||||||
CertificateValidationContext localCertValidationContext)
|
|
||||||
throws CertificateException, IOException, CertStoreException;
|
|
||||||
|
|
||||||
// this gets called only when requested secrets are ready...
|
|
||||||
private void updateSslContext() {
|
|
||||||
try {
|
|
||||||
CertificateValidationContext localCertValidationContext = mergeStaticAndDynamicCertContexts();
|
|
||||||
SslContextBuilder sslContextBuilder = getSslContextBuilder(localCertValidationContext);
|
|
||||||
CommonTlsContext commonTlsContext = getCommonTlsContext();
|
|
||||||
if (commonTlsContext != null && commonTlsContext.getAlpnProtocolsCount() > 0) {
|
|
||||||
List<String> alpnList = commonTlsContext.getAlpnProtocolsList();
|
|
||||||
ApplicationProtocolConfig apn = new ApplicationProtocolConfig(
|
|
||||||
ApplicationProtocolConfig.Protocol.ALPN,
|
|
||||||
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
|
|
||||||
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
|
|
||||||
alpnList);
|
|
||||||
sslContextBuilder.applicationProtocolConfig(apn);
|
|
||||||
}
|
|
||||||
SslContext sslContextCopy = sslContextBuilder.build();
|
|
||||||
sslContext = sslContextCopy;
|
|
||||||
makePendingCallbacks(sslContextCopy);
|
|
||||||
} catch (CertificateException | IOException | CertStoreException e) {
|
|
||||||
logger.log(Level.SEVERE, "exception in updateSslContext", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CertificateValidationContext mergeStaticAndDynamicCertContexts() {
|
|
||||||
if (staticCertificateValidationContext == null) {
|
if (staticCertificateValidationContext == null) {
|
||||||
return certificateValidationContext;
|
return certificateValidationContext;
|
||||||
}
|
}
|
||||||
|
|
@ -183,27 +115,8 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl
|
||||||
return localCertContextBuilder.mergeFrom(staticCertificateValidationContext).build();
|
return localCertContextBuilder.mergeFrom(staticCertificateValidationContext).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makePendingCallbacks(SslContext sslContextCopy) {
|
|
||||||
synchronized (pendingCallbacks) {
|
|
||||||
for (CallbackPair pair : pendingCallbacks) {
|
|
||||||
callPerformCallback(pair.callback, pair.executor, sslContextCopy);
|
|
||||||
}
|
|
||||||
pendingCallbacks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Status error) {
|
public final void close() {
|
||||||
synchronized (pendingCallbacks) {
|
|
||||||
for (CallbackPair callbackPair : pendingCallbacks) {
|
|
||||||
callbackPair.callback.onException(error.asException());
|
|
||||||
}
|
|
||||||
pendingCallbacks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (certSdsClient != null) {
|
if (certSdsClient != null) {
|
||||||
certSdsClient.cancelSecretWatch(this);
|
certSdsClient.cancelSecretWatch(this);
|
||||||
certSdsClient.shutdown();
|
certSdsClient.shutdown();
|
||||||
|
|
@ -213,14 +126,4 @@ abstract class SdsSslContextProvider extends SslContextProvider implements SdsCl
|
||||||
validationContextSdsClient.shutdown();
|
validationContextSdsClient.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CallbackPair {
|
|
||||||
private final Callback callback;
|
|
||||||
private final Executor executor;
|
|
||||||
|
|
||||||
private CallbackPair(Callback callback, Executor executor) {
|
|
||||||
this.callback = callback;
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.CertStoreException;
|
import java.security.cert.CertStoreException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** A client SslContext provider that uses file-based secrets (secret volume). */
|
/** A client SslContext provider that uses file-based secrets (secret volume). */
|
||||||
|
|
@ -92,9 +91,8 @@ final class SecretVolumeClientSslContextProvider extends SslContextProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCallback(final Callback callback, Executor executor) {
|
public void addCallback(final Callback callback) {
|
||||||
checkNotNull(callback, "callback");
|
checkNotNull(callback, "callback");
|
||||||
checkNotNull(executor, "executor");
|
|
||||||
// as per the contract we will read the current secrets on disk
|
// as per the contract we will read the current secrets on disk
|
||||||
// this involves I/O which can potentially block the executor
|
// this involves I/O which can potentially block the executor
|
||||||
performCallback(
|
performCallback(
|
||||||
|
|
@ -104,8 +102,8 @@ final class SecretVolumeClientSslContextProvider extends SslContextProvider {
|
||||||
return buildSslContextFromSecrets();
|
return buildSslContextFromSecrets();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
callback,
|
callback
|
||||||
executor);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.CertStoreException;
|
import java.security.cert.CertStoreException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** A server SslContext provider that uses file-based secrets (secret volume). */
|
/** A server SslContext provider that uses file-based secrets (secret volume). */
|
||||||
|
|
@ -85,9 +84,8 @@ final class SecretVolumeServerSslContextProvider extends SslContextProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCallback(final Callback callback, Executor executor) {
|
public void addCallback(final Callback callback) {
|
||||||
checkNotNull(callback, "callback");
|
checkNotNull(callback, "callback");
|
||||||
checkNotNull(executor, "executor");
|
|
||||||
// as per the contract we will read the current secrets on disk
|
// as per the contract we will read the current secrets on disk
|
||||||
// this involves I/O which can potentially block the executor
|
// this involves I/O which can potentially block the executor
|
||||||
performCallback(
|
performCallback(
|
||||||
|
|
@ -97,8 +95,8 @@ final class SecretVolumeServerSslContextProvider extends SslContextProvider {
|
||||||
return buildSslContextFromSecrets();
|
return buildSslContextFromSecrets();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
callback,
|
callback
|
||||||
executor);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -47,19 +47,25 @@ public abstract class SslContextProvider implements Closeable {
|
||||||
|
|
||||||
protected final BaseTlsContext tlsContext;
|
protected final BaseTlsContext tlsContext;
|
||||||
|
|
||||||
public interface Callback {
|
abstract static class Callback {
|
||||||
/** Informs callee of new/updated SslContext. */
|
private final Executor executor;
|
||||||
void updateSecret(SslContext sslContext);
|
|
||||||
|
|
||||||
/** Informs callee of an exception that was generated. */
|
protected Callback(Executor executor) {
|
||||||
void onException(Throwable throwable);
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
SslContextProvider(BaseTlsContext tlsContext) {
|
/** Informs callee of new/updated SslContext. */
|
||||||
|
abstract void updateSecret(SslContext sslContext);
|
||||||
|
|
||||||
|
/** Informs callee of an exception that was generated. */
|
||||||
|
abstract void onException(Throwable throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SslContextProvider(BaseTlsContext tlsContext) {
|
||||||
this.tlsContext = checkNotNull(tlsContext, "tlsContext");
|
this.tlsContext = checkNotNull(tlsContext, "tlsContext");
|
||||||
}
|
}
|
||||||
|
|
||||||
CommonTlsContext getCommonTlsContext() {
|
protected CommonTlsContext getCommonTlsContext() {
|
||||||
return tlsContext.getCommonTlsContext();
|
return tlsContext.getCommonTlsContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,14 +106,13 @@ public abstract class SslContextProvider implements Closeable {
|
||||||
* Registers a callback on the given executor. The callback will run when SslContext becomes
|
* Registers a callback on the given executor. The callback will run when SslContext becomes
|
||||||
* available or immediately if the result is already available.
|
* available or immediately if the result is already available.
|
||||||
*/
|
*/
|
||||||
public abstract void addCallback(Callback callback, Executor executor);
|
public abstract void addCallback(Callback callback);
|
||||||
|
|
||||||
final void performCallback(
|
protected final void performCallback(
|
||||||
final SslContextGetter sslContextGetter, final Callback callback, Executor executor) {
|
final SslContextGetter sslContextGetter, final Callback callback) {
|
||||||
checkNotNull(sslContextGetter, "sslContextGetter");
|
checkNotNull(sslContextGetter, "sslContextGetter");
|
||||||
checkNotNull(callback, "callback");
|
checkNotNull(callback, "callback");
|
||||||
checkNotNull(executor, "executor");
|
callback.executor.execute(
|
||||||
executor.execute(
|
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.grpc.xds.internal.sds.trust;
|
package io.grpc.xds.internal.sds.trust;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
@ -53,9 +53,29 @@ public final class SdsTrustManagerFactory extends SimpleTrustManagerFactory {
|
||||||
/** Constructor constructs from a {@link CertificateValidationContext}. */
|
/** Constructor constructs from a {@link CertificateValidationContext}. */
|
||||||
public SdsTrustManagerFactory(CertificateValidationContext certificateValidationContext)
|
public SdsTrustManagerFactory(CertificateValidationContext certificateValidationContext)
|
||||||
throws CertificateException, IOException, CertStoreException {
|
throws CertificateException, IOException, CertStoreException {
|
||||||
checkNotNull(certificateValidationContext, "certificateValidationContext");
|
this(
|
||||||
sdsX509TrustManager = createSdsX509TrustManager(
|
getTrustedCaFromCertContext(certificateValidationContext),
|
||||||
getTrustedCaFromCertContext(certificateValidationContext), certificateValidationContext);
|
certificateValidationContext,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdsTrustManagerFactory(
|
||||||
|
X509Certificate[] certs, CertificateValidationContext staticCertificateValidationContext)
|
||||||
|
throws CertStoreException {
|
||||||
|
this(certs, staticCertificateValidationContext, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SdsTrustManagerFactory(
|
||||||
|
X509Certificate[] certs,
|
||||||
|
CertificateValidationContext certificateValidationContext,
|
||||||
|
boolean validationContextIsStatic)
|
||||||
|
throws CertStoreException {
|
||||||
|
if (validationContextIsStatic) {
|
||||||
|
checkArgument(
|
||||||
|
certificateValidationContext == null || !certificateValidationContext.hasTrustedCa(),
|
||||||
|
"only static certificateValidationContext expected");
|
||||||
|
}
|
||||||
|
sdsX509TrustManager = createSdsX509TrustManager(certs, certificateValidationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static X509Certificate[] getTrustedCaFromCertContext(
|
private static X509Certificate[] getTrustedCaFromCertContext(
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,8 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from OkHostnameVerifier.verifyHostName().
|
// Copied from OkHostnameVerifier.verifyHostName().
|
||||||
private static boolean verifyDnsNameInPattern(String pattern, String sanToVerify) {
|
private static boolean verifyDnsNameInPattern(String pattern, StringMatcher sanToVerifyMatcher) {
|
||||||
|
String sanToVerify = sanToVerifyMatcher.getExact();
|
||||||
// Basic sanity checks
|
// Basic sanity checks
|
||||||
// Check length == 0 instead of .isEmpty() to support Java 5.
|
// Check length == 0 instead of .isEmpty() to support Java 5.
|
||||||
if (sanToVerify == null
|
if (sanToVerify == null
|
||||||
|
|
@ -150,9 +151,9 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509
|
||||||
// sanToVerify matches pattern
|
// sanToVerify matches pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean verifyDnsNameInSanList(String altNameFromCert,
|
private static boolean verifyDnsNameInSanList(
|
||||||
List<String> verifySanList) {
|
String altNameFromCert, List<StringMatcher> verifySanList) {
|
||||||
for (String verifySan : verifySanList) {
|
for (StringMatcher verifySan : verifySanList) {
|
||||||
if (verifyDnsNameInPattern(altNameFromCert, verifySan)) {
|
if (verifyDnsNameInPattern(altNameFromCert, verifySan)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -168,16 +169,17 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509
|
||||||
* @param verifySanList list of SANs from certificate context
|
* @param verifySanList list of SANs from certificate context
|
||||||
* @return true if there is a match
|
* @return true if there is a match
|
||||||
*/
|
*/
|
||||||
private static boolean verifyStringInSanList(String stringFromCert, List<String> verifySanList) {
|
private static boolean verifyStringInSanList(
|
||||||
for (String sanToVerify : verifySanList) {
|
String stringFromCert, List<StringMatcher> verifySanList) {
|
||||||
if (Ascii.equalsIgnoreCase(sanToVerify, stringFromCert)) {
|
for (StringMatcher sanToVerify : verifySanList) {
|
||||||
|
if (Ascii.equalsIgnoreCase(sanToVerify.getExact(), stringFromCert)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean verifyOneSanInList(List<?> entry, List<String> verifySanList)
|
private static boolean verifyOneSanInList(List<?> entry, List<StringMatcher> verifySanList)
|
||||||
throws CertificateParsingException {
|
throws CertificateParsingException {
|
||||||
// from OkHostnameVerifier.getSubjectAltNames
|
// from OkHostnameVerifier.getSubjectAltNames
|
||||||
if (entry == null || entry.size() < 2) {
|
if (entry == null || entry.size() < 2) {
|
||||||
|
|
@ -200,9 +202,8 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509
|
||||||
}
|
}
|
||||||
|
|
||||||
// logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName
|
// logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName
|
||||||
@SuppressWarnings("UnusedMethod") // TODO(#7166): support StringMatcher list.
|
private static void verifySubjectAltNameInLeaf(
|
||||||
private static void verifySubjectAltNameInLeaf(X509Certificate cert, List<String> verifyList)
|
X509Certificate cert, List<StringMatcher> verifyList) throws CertificateException {
|
||||||
throws CertificateException {
|
|
||||||
Collection<List<?>> names = cert.getSubjectAlternativeNames();
|
Collection<List<?>> names = cert.getSubjectAlternativeNames();
|
||||||
if (names == null || names.isEmpty()) {
|
if (names == null || names.isEmpty()) {
|
||||||
throw new CertificateException("Peer certificate SAN check failed");
|
throw new CertificateException("Peer certificate SAN check failed");
|
||||||
|
|
@ -233,9 +234,7 @@ final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509
|
||||||
throw new CertificateException("Peer certificate(s) missing");
|
throw new CertificateException("Peer certificate(s) missing");
|
||||||
}
|
}
|
||||||
// verify SANs only in the top cert (leaf cert)
|
// verify SANs only in the top cert (leaf cert)
|
||||||
// v2 version: verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);
|
verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);
|
||||||
// TODO(#7166): Implement v3 version.
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal.certprovider;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
||||||
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
|
import io.grpc.xds.Bootstrapper;
|
||||||
|
import io.grpc.xds.EnvoyServerProtoData;
|
||||||
|
import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil;
|
||||||
|
import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link CertProviderClientSslContextProvider}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class CertProviderClientSslContextProviderTest {
|
||||||
|
private static final Logger logger =
|
||||||
|
Logger.getLogger(CertProviderClientSslContextProviderTest.class.getName());
|
||||||
|
|
||||||
|
CertificateProviderRegistry certificateProviderRegistry;
|
||||||
|
CertificateProviderStore certificateProviderStore;
|
||||||
|
private CertProviderClientSslContextProvider.Factory certProviderClientSslContextProviderFactory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
certificateProviderRegistry = new CertificateProviderRegistry();
|
||||||
|
certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry);
|
||||||
|
certProviderClientSslContextProviderFactory =
|
||||||
|
new CertProviderClientSslContextProvider.Factory(certificateProviderStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method to build CertProviderClientSslContextProvider. */
|
||||||
|
private CertProviderClientSslContextProvider getSslContextProvider(
|
||||||
|
String certInstanceName,
|
||||||
|
String rootInstanceName,
|
||||||
|
Bootstrapper.BootstrapInfo bootstrapInfo,
|
||||||
|
Iterable<String> alpnProtocols,
|
||||||
|
CertificateValidationContext staticCertValidationContext) {
|
||||||
|
EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext =
|
||||||
|
CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance(
|
||||||
|
certInstanceName,
|
||||||
|
"cert-default",
|
||||||
|
rootInstanceName,
|
||||||
|
"root-default",
|
||||||
|
alpnProtocols,
|
||||||
|
staticCertValidationContext);
|
||||||
|
return certProviderClientSslContextProviderFactory.getProvider(
|
||||||
|
upstreamTlsContext,
|
||||||
|
bootstrapInfo.getNode().toEnvoyProtoNode(),
|
||||||
|
bootstrapInfo.getCertProviders());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProviderForClient_mtls() throws Exception {
|
||||||
|
final CertificateProvider.DistributorWatcher[] watcherCaptor =
|
||||||
|
new CertificateProvider.DistributorWatcher[1];
|
||||||
|
TestCertificateProvider.createAndRegisterProviderProvider(
|
||||||
|
certificateProviderRegistry, watcherCaptor, "testca", 0);
|
||||||
|
CertProviderClientSslContextProvider provider =
|
||||||
|
getSslContextProvider(
|
||||||
|
"gcp_id",
|
||||||
|
"gcp_id",
|
||||||
|
CommonCertProviderTestUtils.getTestBootstrapInfo(),
|
||||||
|
/* alpnProtocols= */ null,
|
||||||
|
/* staticCertValidationContext= */ null);
|
||||||
|
|
||||||
|
assertThat(provider.savedKey).isNull();
|
||||||
|
assertThat(provider.savedCertChain).isNull();
|
||||||
|
assertThat(provider.savedTrustedRoots).isNull();
|
||||||
|
assertThat(provider.getSslContext()).isNull();
|
||||||
|
|
||||||
|
// now generate cert update
|
||||||
|
watcherCaptor[0].updateCertificate(
|
||||||
|
CommonCertProviderTestUtils.getPrivateKey(CLIENT_KEY_FILE),
|
||||||
|
ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE)));
|
||||||
|
assertThat(provider.savedKey).isNotNull();
|
||||||
|
assertThat(provider.savedCertChain).isNotNull();
|
||||||
|
assertThat(provider.getSslContext()).isNull();
|
||||||
|
|
||||||
|
// now generate root cert update
|
||||||
|
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
|
||||||
|
assertThat(provider.getSslContext()).isNotNull();
|
||||||
|
assertThat(provider.savedKey).isNull();
|
||||||
|
assertThat(provider.savedCertChain).isNull();
|
||||||
|
assertThat(provider.savedTrustedRoots).isNull();
|
||||||
|
|
||||||
|
TestCallback testCallback =
|
||||||
|
CommonTlsContextTestsUtil.getValueThruCallback(provider);
|
||||||
|
|
||||||
|
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
|
TestCallback testCallback1 =
|
||||||
|
CommonTlsContextTestsUtil.getValueThruCallback(provider);
|
||||||
|
assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
|
||||||
|
|
||||||
|
// just do root cert update: sslContext should still be the same
|
||||||
|
watcherCaptor[0].updateTrustedRoots(
|
||||||
|
ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
|
||||||
|
assertThat(provider.savedKey).isNull();
|
||||||
|
assertThat(provider.savedCertChain).isNull();
|
||||||
|
assertThat(provider.savedTrustedRoots).isNotNull();
|
||||||
|
testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
|
||||||
|
assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
|
||||||
|
|
||||||
|
// now update id cert: sslContext should be updated i.e.different from the previous one
|
||||||
|
watcherCaptor[0].updateCertificate(
|
||||||
|
CommonCertProviderTestUtils.getPrivateKey(SERVER_1_KEY_FILE),
|
||||||
|
ImmutableList.of(getCertFromResourceName(SERVER_1_PEM_FILE)));
|
||||||
|
assertThat(provider.savedKey).isNull();
|
||||||
|
assertThat(provider.savedCertChain).isNull();
|
||||||
|
assertThat(provider.savedTrustedRoots).isNull();
|
||||||
|
assertThat(provider.getSslContext()).isNotNull();
|
||||||
|
testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
|
||||||
|
assertThat(testCallback1.updatedSslContext).isNotSameInstanceAs(testCallback.updatedSslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProviderForClient_queueExecutor() throws Exception {
|
||||||
|
final CertificateProvider.DistributorWatcher[] watcherCaptor =
|
||||||
|
new CertificateProvider.DistributorWatcher[1];
|
||||||
|
TestCertificateProvider.createAndRegisterProviderProvider(
|
||||||
|
certificateProviderRegistry, watcherCaptor, "testca", 0);
|
||||||
|
CertProviderClientSslContextProvider provider =
|
||||||
|
getSslContextProvider(
|
||||||
|
"gcp_id",
|
||||||
|
"gcp_id",
|
||||||
|
CommonCertProviderTestUtils.getTestBootstrapInfo(),
|
||||||
|
/* alpnProtocols= */ null,
|
||||||
|
/* staticCertValidationContext= */ null);
|
||||||
|
QueuedExecutor queuedExecutor = new QueuedExecutor();
|
||||||
|
|
||||||
|
TestCallback testCallback =
|
||||||
|
CommonTlsContextTestsUtil.getValueThruCallback(provider, queuedExecutor);
|
||||||
|
assertThat(queuedExecutor.runQueue).isEmpty();
|
||||||
|
|
||||||
|
// now generate cert update
|
||||||
|
watcherCaptor[0].updateCertificate(
|
||||||
|
CommonCertProviderTestUtils.getPrivateKey(CLIENT_KEY_FILE),
|
||||||
|
ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE)));
|
||||||
|
assertThat(queuedExecutor.runQueue).isEmpty(); // still empty
|
||||||
|
|
||||||
|
// now generate root cert update
|
||||||
|
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
|
||||||
|
assertThat(queuedExecutor.runQueue).hasSize(1);
|
||||||
|
queuedExecutor.drain();
|
||||||
|
|
||||||
|
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProviderForClient_tls() throws Exception {
|
||||||
|
final CertificateProvider.DistributorWatcher[] watcherCaptor =
|
||||||
|
new CertificateProvider.DistributorWatcher[1];
|
||||||
|
TestCertificateProvider.createAndRegisterProviderProvider(
|
||||||
|
certificateProviderRegistry, watcherCaptor, "testca", 0);
|
||||||
|
CertProviderClientSslContextProvider provider =
|
||||||
|
getSslContextProvider(
|
||||||
|
/* certInstanceName= */ null,
|
||||||
|
"gcp_id",
|
||||||
|
CommonCertProviderTestUtils.getTestBootstrapInfo(),
|
||||||
|
/* alpnProtocols= */ null,
|
||||||
|
/* staticCertValidationContext= */ null);
|
||||||
|
|
||||||
|
assertThat(provider.savedKey).isNull();
|
||||||
|
assertThat(provider.savedCertChain).isNull();
|
||||||
|
assertThat(provider.savedTrustedRoots).isNull();
|
||||||
|
assertThat(provider.getSslContext()).isNull();
|
||||||
|
|
||||||
|
// now generate root cert update
|
||||||
|
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
|
||||||
|
assertThat(provider.getSslContext()).isNotNull();
|
||||||
|
assertThat(provider.savedKey).isNull();
|
||||||
|
assertThat(provider.savedCertChain).isNull();
|
||||||
|
assertThat(provider.savedTrustedRoots).isNull();
|
||||||
|
|
||||||
|
TestCallback testCallback =
|
||||||
|
CommonTlsContextTestsUtil.getValueThruCallback(provider);
|
||||||
|
|
||||||
|
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProviderForClient_sslContextException_onError() throws Exception {
|
||||||
|
CertificateValidationContext staticCertValidationContext =
|
||||||
|
CertificateValidationContext.newBuilder()
|
||||||
|
.setTrustedCa(DataSource.newBuilder().setInlineString("foo"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final CertificateProvider.DistributorWatcher[] watcherCaptor =
|
||||||
|
new CertificateProvider.DistributorWatcher[1];
|
||||||
|
TestCertificateProvider.createAndRegisterProviderProvider(
|
||||||
|
certificateProviderRegistry, watcherCaptor, "testca", 0);
|
||||||
|
CertProviderClientSslContextProvider provider =
|
||||||
|
getSslContextProvider(
|
||||||
|
/* certInstanceName= */ null,
|
||||||
|
"gcp_id",
|
||||||
|
CommonCertProviderTestUtils.getTestBootstrapInfo(),
|
||||||
|
/* alpnProtocols= */null,
|
||||||
|
staticCertValidationContext);
|
||||||
|
|
||||||
|
TestCallback testCallback = new TestCallback(MoreExecutors.directExecutor());
|
||||||
|
provider.addCallback(testCallback);
|
||||||
|
try {
|
||||||
|
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (RuntimeException expected) {
|
||||||
|
assertThat(expected)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("only static certificateValidationContext expected");
|
||||||
|
}
|
||||||
|
assertThat(testCallback.updatedThrowable).isNotNull();
|
||||||
|
assertThat(testCallback.updatedThrowable)
|
||||||
|
.hasCauseThat()
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("only static certificateValidationContext expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProviderForClient_rootInstanceNull_expectError() throws Exception {
|
||||||
|
final CertificateProvider.DistributorWatcher[] watcherCaptor =
|
||||||
|
new CertificateProvider.DistributorWatcher[1];
|
||||||
|
TestCertificateProvider.createAndRegisterProviderProvider(
|
||||||
|
certificateProviderRegistry, watcherCaptor, "testca", 0);
|
||||||
|
try {
|
||||||
|
getSslContextProvider(
|
||||||
|
/* certInstanceName= */ null,
|
||||||
|
/* rootInstanceName= */ null,
|
||||||
|
CommonCertProviderTestUtils.getTestBootstrapInfo(),
|
||||||
|
/* alpnProtocols= */ null,
|
||||||
|
/* staticCertValidationContext= */ null);
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (NullPointerException expected) {
|
||||||
|
assertThat(expected).hasMessageThat().contains("Client SSL requires rootCertInstance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class QueuedExecutor implements Executor {
|
||||||
|
/** A list of Runnables to be run in order. */
|
||||||
|
private final Queue<Runnable> runQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void execute(Runnable r) {
|
||||||
|
runQueue.add(checkNotNull(r, "'r' must not be null."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void drain() {
|
||||||
|
Runnable r;
|
||||||
|
while ((r = runQueue.poll()) != null) {
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Log it and keep going.
|
||||||
|
logger.log(Level.SEVERE, "Exception while executing runnable " + r, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -49,36 +49,6 @@ public class CertificateProviderStoreTest {
|
||||||
private CertificateProviderStore certificateProviderStore;
|
private CertificateProviderStore certificateProviderStore;
|
||||||
private boolean throwExceptionForCertUpdates;
|
private boolean throwExceptionForCertUpdates;
|
||||||
|
|
||||||
private class TestCertificateProvider extends CertificateProvider {
|
|
||||||
Object config;
|
|
||||||
CertificateProviderProvider certProviderProvider;
|
|
||||||
int closeCalled = 0;
|
|
||||||
int startCalled = 0;
|
|
||||||
|
|
||||||
protected TestCertificateProvider(
|
|
||||||
CertificateProvider.DistributorWatcher watcher,
|
|
||||||
boolean notifyCertUpdates,
|
|
||||||
Object config,
|
|
||||||
CertificateProviderProvider certificateProviderProvider) {
|
|
||||||
super(watcher, notifyCertUpdates);
|
|
||||||
if (throwExceptionForCertUpdates && notifyCertUpdates) {
|
|
||||||
throw new UnsupportedOperationException("Provider does not support Certificate Updates.");
|
|
||||||
}
|
|
||||||
this.config = config;
|
|
||||||
this.certProviderProvider = certificateProviderProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
closeCalled++;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
startCalled++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
certificateProviderRegistry = new CertificateProviderRegistry();
|
certificateProviderRegistry = new CertificateProviderRegistry();
|
||||||
|
|
@ -94,7 +64,7 @@ public class CertificateProviderStoreTest {
|
||||||
"cert-name1", "plugin1", "config", mockWatcher, true);
|
"cert-name1", "plugin1", "config", mockWatcher, true);
|
||||||
fail("exception expected");
|
fail("exception expected");
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("Provider not found.");
|
assertThat(expected).hasMessageThat().isEqualTo("Provider not found for plugin1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +81,7 @@ public class CertificateProviderStoreTest {
|
||||||
"cert-name1", "plugin1", "config", mockWatcher, true);
|
"cert-name1", "plugin1", "config", mockWatcher, true);
|
||||||
fail("exception expected");
|
fail("exception expected");
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo("Provider not found.");
|
assertThat(expected).hasMessageThat().isEqualTo("Provider not found for plugin1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,7 +339,8 @@ public class CertificateProviderStoreTest {
|
||||||
(CertificateProvider.DistributorWatcher) args[1];
|
(CertificateProvider.DistributorWatcher) args[1];
|
||||||
boolean notifyCertUpdates = (Boolean) args[2];
|
boolean notifyCertUpdates = (Boolean) args[2];
|
||||||
return new TestCertificateProvider(
|
return new TestCertificateProvider(
|
||||||
watcher, notifyCertUpdates, config, certProviderProvider);
|
watcher, notifyCertUpdates, config, certProviderProvider,
|
||||||
|
throwExceptionForCertUpdates);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
certificateProviderRegistry.register(certProviderProvider);
|
certificateProviderRegistry.register(certProviderProvider);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal.certprovider;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
import io.grpc.internal.testing.TestUtils;
|
||||||
|
import io.grpc.xds.Bootstrapper;
|
||||||
|
import io.grpc.xds.internal.sds.trust.CertificateUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.base64.Base64;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.security.KeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class CommonCertProviderTestUtils {
|
||||||
|
private static final Logger logger =
|
||||||
|
Logger.getLogger(CommonCertProviderTestUtils.class.getName());
|
||||||
|
|
||||||
|
private static final Pattern KEY_PATTERN = Pattern.compile(
|
||||||
|
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
|
||||||
|
"([a-z0-9+/=\\r\\n]+)" + // Base64 text
|
||||||
|
"-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
static Bootstrapper.BootstrapInfo getTestBootstrapInfo() throws IOException {
|
||||||
|
String rawData =
|
||||||
|
"{\n"
|
||||||
|
+ " \"xds_servers\": [],\n"
|
||||||
|
+ " \"certificate_providers\": {\n"
|
||||||
|
+ " \"gcp_id\": {\n"
|
||||||
|
+ " \"plugin_name\": \"testca\",\n"
|
||||||
|
+ " \"config\": {\n"
|
||||||
|
+ " \"server\": {\n"
|
||||||
|
+ " \"api_type\": \"GRPC\",\n"
|
||||||
|
+ " \"grpc_services\": [{\n"
|
||||||
|
+ " \"google_grpc\": {\n"
|
||||||
|
+ " \"target_uri\": \"meshca.com\",\n"
|
||||||
|
+ " \"channel_credentials\": {\"google_default\": {}},\n"
|
||||||
|
+ " \"call_credentials\": [{\n"
|
||||||
|
+ " \"sts_service\": {\n"
|
||||||
|
+ " \"token_exchange_service\": \"securetoken.googleapis.com\",\n"
|
||||||
|
+ " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " }]\n" // end call_credentials
|
||||||
|
+ " },\n" // end google_grpc
|
||||||
|
+ " \"time_out\": {\"seconds\": 10}\n"
|
||||||
|
+ " }]\n" // end grpc_services
|
||||||
|
+ " },\n" // end server
|
||||||
|
+ " \"certificate_lifetime\": {\"seconds\": 86400},\n"
|
||||||
|
+ " \"renewal_grace_period\": {\"seconds\": 3600},\n"
|
||||||
|
+ " \"key_type\": \"RSA\",\n"
|
||||||
|
+ " \"key_size\": 2048,\n"
|
||||||
|
+ " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n"
|
||||||
|
+ " }\n" // end config
|
||||||
|
+ " },\n" // end gcp_id
|
||||||
|
+ " \"file_provider\": {\n"
|
||||||
|
+ " \"plugin_name\": \"file_watcher\",\n"
|
||||||
|
+ " \"config\": {\"path\": \"/etc/secret/certs\"}\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ " }\n"
|
||||||
|
+ "}";
|
||||||
|
return Bootstrapper.parseConfig(rawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PrivateKey getPrivateKey(String resourceName)
|
||||||
|
throws Exception {
|
||||||
|
InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName);
|
||||||
|
ByteBuf encodedKeyBuf = readPrivateKey(inputStream);
|
||||||
|
|
||||||
|
byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
|
||||||
|
encodedKeyBuf.readBytes(encodedKey).release();
|
||||||
|
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encodedKey);
|
||||||
|
try {
|
||||||
|
return KeyFactory.getInstance("RSA").generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException ignore) {
|
||||||
|
try {
|
||||||
|
return KeyFactory.getInstance("DSA").generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException ignore2) {
|
||||||
|
try {
|
||||||
|
return KeyFactory.getInstance("EC").generatePrivate(spec);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
throw new InvalidKeySpecException("Neither RSA, DSA nor EC worked", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ByteBuf readPrivateKey(InputStream in) throws KeyException {
|
||||||
|
String content;
|
||||||
|
try {
|
||||||
|
content = readContent(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new KeyException("failed to read key input stream", e);
|
||||||
|
}
|
||||||
|
Matcher m = KEY_PATTERN.matcher(content);
|
||||||
|
if (!m.find()) {
|
||||||
|
throw new KeyException("could not find a PKCS #8 private key in input stream");
|
||||||
|
}
|
||||||
|
ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
|
||||||
|
ByteBuf der = Base64.decode(base64);
|
||||||
|
base64.release();
|
||||||
|
return der;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readContent(InputStream in) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
byte[] buf = new byte[8192];
|
||||||
|
for (; ; ) {
|
||||||
|
int ret = in.read(buf);
|
||||||
|
if (ret < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out.write(buf, 0, ret);
|
||||||
|
}
|
||||||
|
return out.toString(CharsetUtil.US_ASCII.name());
|
||||||
|
} finally {
|
||||||
|
safeClose(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void safeClose(OutputStream out) {
|
||||||
|
try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.WARNING, "Failed to close a stream.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate getCertFromResourceName(String resourceName)
|
||||||
|
throws IOException, CertificateException {
|
||||||
|
return CertificateUtils.toX509Certificate(
|
||||||
|
new ByteArrayInputStream(getResourceContents(resourceName).getBytes(UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getResourceContents(String resourceName) throws IOException {
|
||||||
|
InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName);
|
||||||
|
String text = null;
|
||||||
|
try (Reader reader = new InputStreamReader(inputStream, UTF_8)) {
|
||||||
|
text = CharStreams.toString(reader);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The gRPC Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal.certprovider;
|
||||||
|
|
||||||
|
public class TestCertificateProvider extends CertificateProvider {
|
||||||
|
Object config;
|
||||||
|
CertificateProviderProvider certProviderProvider;
|
||||||
|
int closeCalled = 0;
|
||||||
|
int startCalled = 0;
|
||||||
|
|
||||||
|
TestCertificateProvider(
|
||||||
|
DistributorWatcher watcher,
|
||||||
|
boolean notifyCertUpdates,
|
||||||
|
Object config,
|
||||||
|
CertificateProviderProvider certificateProviderProvider,
|
||||||
|
boolean throwExceptionForCertUpdates) {
|
||||||
|
super(watcher, notifyCertUpdates);
|
||||||
|
if (throwExceptionForCertUpdates && notifyCertUpdates) {
|
||||||
|
throw new UnsupportedOperationException("Provider does not support Certificate Updates.");
|
||||||
|
}
|
||||||
|
this.config = config;
|
||||||
|
this.certProviderProvider = certificateProviderProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
closeCalled++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
startCalled++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void createAndRegisterProviderProvider(
|
||||||
|
CertificateProviderRegistry certificateProviderRegistry,
|
||||||
|
final CertificateProvider.DistributorWatcher[] watcherCaptor,
|
||||||
|
String testca,
|
||||||
|
final int index) {
|
||||||
|
final CertificateProviderProvider mockProviderProviderTestCa =
|
||||||
|
new TestCertificateProviderProvider(testca, watcherCaptor, index);
|
||||||
|
certificateProviderRegistry.register(mockProviderProviderTestCa);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestCertificateProviderProvider implements CertificateProviderProvider {
|
||||||
|
|
||||||
|
private final String testCa;
|
||||||
|
private final CertificateProvider.DistributorWatcher[] watcherCaptor;
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
TestCertificateProviderProvider(
|
||||||
|
String testCa, CertificateProvider.DistributorWatcher[] watcherCaptor, int index) {
|
||||||
|
this.testCa = testCa;
|
||||||
|
this.watcherCaptor = watcherCaptor;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return testCa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CertificateProvider createCertificateProvider(
|
||||||
|
Object config, DistributorWatcher watcher, boolean notifyCertUpdates) {
|
||||||
|
watcherCaptor[index] = watcher;
|
||||||
|
return new TestCertificateProvider(watcher, true, config, this, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,10 +16,12 @@
|
||||||
|
|
||||||
package io.grpc.xds.internal.sds;
|
package io.grpc.xds.internal.sds;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.protobuf.BoolValue;
|
import com.google.protobuf.BoolValue;
|
||||||
import com.google.protobuf.Struct;
|
import com.google.protobuf.Struct;
|
||||||
import com.google.protobuf.Value;
|
import com.google.protobuf.Value;
|
||||||
|
|
@ -40,6 +42,7 @@ import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
|
||||||
import io.grpc.internal.testing.TestUtils;
|
import io.grpc.internal.testing.TestUtils;
|
||||||
import io.grpc.xds.EnvoyServerProtoData;
|
import io.grpc.xds.EnvoyServerProtoData;
|
||||||
import io.grpc.xds.internal.sds.trust.CertificateUtils;
|
import io.grpc.xds.internal.sds.trust.CertificateUtils;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -48,6 +51,8 @@ import java.io.Reader;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/** Utility class for client and server ssl provider tests. */
|
/** Utility class for client and server ssl provider tests. */
|
||||||
|
|
@ -461,4 +466,122 @@ public class CommonTlsContextTestsUtil {
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CommonTlsContext buildCommonTlsContextForCertProviderInstance(
|
||||||
|
String certInstanceName,
|
||||||
|
String certName,
|
||||||
|
String rootInstanceName,
|
||||||
|
String rootCertName,
|
||||||
|
Iterable<String> alpnProtocols,
|
||||||
|
CertificateValidationContext staticCertValidationContext) {
|
||||||
|
CommonTlsContext.Builder builder = CommonTlsContext.newBuilder();
|
||||||
|
if (certInstanceName != null) {
|
||||||
|
builder =
|
||||||
|
builder.setTlsCertificateCertificateProviderInstance(
|
||||||
|
CommonTlsContext.CertificateProviderInstance.newBuilder()
|
||||||
|
.setInstanceName(certInstanceName)
|
||||||
|
.setCertificateName(certName));
|
||||||
|
}
|
||||||
|
builder =
|
||||||
|
addCertificateValidationContext(
|
||||||
|
builder, rootInstanceName, rootCertName, staticCertValidationContext);
|
||||||
|
if (alpnProtocols != null) {
|
||||||
|
builder.addAllAlpnProtocols(alpnProtocols);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CommonTlsContext.Builder addCertificateValidationContext(
|
||||||
|
CommonTlsContext.Builder builder,
|
||||||
|
String rootInstanceName,
|
||||||
|
String rootCertName,
|
||||||
|
CertificateValidationContext staticCertValidationContext) {
|
||||||
|
if (rootInstanceName != null) {
|
||||||
|
CommonTlsContext.CertificateProviderInstance.Builder providerInstanceBuilder =
|
||||||
|
CommonTlsContext.CertificateProviderInstance.newBuilder()
|
||||||
|
.setInstanceName(rootInstanceName)
|
||||||
|
.setCertificateName(rootCertName);
|
||||||
|
if (staticCertValidationContext != null) {
|
||||||
|
CombinedCertificateValidationContext combined =
|
||||||
|
CombinedCertificateValidationContext.newBuilder()
|
||||||
|
.setDefaultValidationContext(staticCertValidationContext)
|
||||||
|
.setValidationContextCertificateProviderInstance(providerInstanceBuilder)
|
||||||
|
.build();
|
||||||
|
return builder.setCombinedValidationContext(combined);
|
||||||
|
}
|
||||||
|
builder = builder.setValidationContextCertificateProviderInstance(providerInstanceBuilder);
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method to build UpstreamTlsContext for CertProvider tests. */
|
||||||
|
public static EnvoyServerProtoData.UpstreamTlsContext
|
||||||
|
buildUpstreamTlsContextForCertProviderInstance(
|
||||||
|
@Nullable String certInstanceName,
|
||||||
|
@Nullable String certName,
|
||||||
|
@Nullable String rootInstanceName,
|
||||||
|
@Nullable String rootCertName,
|
||||||
|
Iterable<String> alpnProtocols,
|
||||||
|
CertificateValidationContext staticCertValidationContext) {
|
||||||
|
return buildUpstreamTlsContext(
|
||||||
|
buildCommonTlsContextForCertProviderInstance(
|
||||||
|
certInstanceName,
|
||||||
|
certName,
|
||||||
|
rootInstanceName,
|
||||||
|
rootCertName,
|
||||||
|
alpnProtocols,
|
||||||
|
staticCertValidationContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Perform some simple checks on sslContext. */
|
||||||
|
public static void doChecksOnSslContext(boolean server, SslContext sslContext,
|
||||||
|
List<String> expectedApnProtos) {
|
||||||
|
if (server) {
|
||||||
|
assertThat(sslContext.isServer()).isTrue();
|
||||||
|
} else {
|
||||||
|
assertThat(sslContext.isClient()).isTrue();
|
||||||
|
}
|
||||||
|
List<String> apnProtos = sslContext.applicationProtocolNegotiator().protocols();
|
||||||
|
assertThat(apnProtos).isNotNull();
|
||||||
|
if (expectedApnProtos != null) {
|
||||||
|
assertThat(apnProtos).isEqualTo(expectedApnProtos);
|
||||||
|
} else {
|
||||||
|
assertThat(apnProtos).contains("h2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to get the value thru directExecutor callback. Because of directExecutor this is
|
||||||
|
* a synchronous callback - so need to provide a listener.
|
||||||
|
*/
|
||||||
|
public static TestCallback getValueThruCallback(SslContextProvider provider) {
|
||||||
|
return getValueThruCallback(provider, MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper method to get the value thru callback with a user passed executor. */
|
||||||
|
public static TestCallback getValueThruCallback(SslContextProvider provider, Executor executor) {
|
||||||
|
TestCallback testCallback = new TestCallback(executor);
|
||||||
|
provider.addCallback(testCallback);
|
||||||
|
return testCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestCallback extends SslContextProvider.Callback {
|
||||||
|
|
||||||
|
public SslContext updatedSslContext;
|
||||||
|
public Throwable updatedThrowable;
|
||||||
|
|
||||||
|
public TestCallback(Executor executor) {
|
||||||
|
super(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSecret(SslContext sslContext) {
|
||||||
|
updatedSslContext = sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Throwable throwable) {
|
||||||
|
updatedThrowable = throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE
|
||||||
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
|
||||||
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE;
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE;
|
||||||
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE;
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.getValueThruCallback;
|
||||||
import static io.grpc.xds.internal.sds.SdsClientTest.getOneCertificateValidationContextSecret;
|
import static io.grpc.xds.internal.sds.SdsClientTest.getOneCertificateValidationContextSecret;
|
||||||
import static io.grpc.xds.internal.sds.SdsClientTest.getOneTlsCertSecret;
|
import static io.grpc.xds.internal.sds.SdsClientTest.getOneTlsCertSecret;
|
||||||
import static io.grpc.xds.internal.sds.SecretVolumeSslContextProviderTest.doChecksOnSslContext;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
@ -33,6 +34,7 @@ import io.envoyproxy.envoy.api.v2.core.Node;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
|
||||||
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
|
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
|
||||||
import io.grpc.Status.Code;
|
import io.grpc.Status.Code;
|
||||||
|
import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
|
@ -123,8 +125,7 @@ public class SdsSslContextProviderTest {
|
||||||
|
|
||||||
SdsServerSslContextProvider provider =
|
SdsServerSslContextProvider provider =
|
||||||
getSdsServerSslContextProvider("cert1", "valid1", null, null);
|
getSdsServerSslContextProvider("cert1", "valid1", null, null);
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
}
|
}
|
||||||
|
|
@ -142,8 +143,7 @@ public class SdsSslContextProviderTest {
|
||||||
/* validationContextName= */ "valid1",
|
/* validationContextName= */ "valid1",
|
||||||
/* matchSubjectAltNames= */ null,
|
/* matchSubjectAltNames= */ null,
|
||||||
/* alpnProtocols= */ null);
|
/* alpnProtocols= */ null);
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +159,7 @@ public class SdsSslContextProviderTest {
|
||||||
/* validationContextName= */ null,
|
/* validationContextName= */ null,
|
||||||
/* matchSubjectAltNames= */ null,
|
/* matchSubjectAltNames= */ null,
|
||||||
/* alpnProtocols= */ null);
|
/* alpnProtocols= */ null);
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
}
|
}
|
||||||
|
|
@ -176,8 +175,7 @@ public class SdsSslContextProviderTest {
|
||||||
/* validationContextName= */ "valid1",
|
/* validationContextName= */ "valid1",
|
||||||
/* matchSubjectAltNames= */ null,
|
/* matchSubjectAltNames= */ null,
|
||||||
null);
|
null);
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
}
|
}
|
||||||
|
|
@ -193,8 +191,7 @@ public class SdsSslContextProviderTest {
|
||||||
/* validationContextName= */ "valid1",
|
/* validationContextName= */ "valid1",
|
||||||
/* matchSubjectAltNames= */ null,
|
/* matchSubjectAltNames= */ null,
|
||||||
/* alpnProtocols= */ null);
|
/* alpnProtocols= */ null);
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
assertThat(server.lastNack).isNotNull();
|
assertThat(server.lastNack).isNotNull();
|
||||||
assertThat(server.lastNack.getVersionInfo()).isEmpty();
|
assertThat(server.lastNack.getVersionInfo()).isEmpty();
|
||||||
|
|
@ -222,8 +219,7 @@ public class SdsSslContextProviderTest {
|
||||||
.build()),
|
.build()),
|
||||||
/* alpnProtocols= */ null);
|
/* alpnProtocols= */ null);
|
||||||
|
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
doChecksOnSslContext(false, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,8 +236,7 @@ public class SdsSslContextProviderTest {
|
||||||
/* validationContextName= */ "valid1",
|
/* validationContextName= */ "valid1",
|
||||||
/* matchSubjectAltNames= */ null,
|
/* matchSubjectAltNames= */ null,
|
||||||
/* alpnProtocols= */ Arrays.asList("managed-mtls", "h2"));
|
/* alpnProtocols= */ Arrays.asList("managed-mtls", "h2"));
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
doChecksOnSslContext(
|
doChecksOnSslContext(
|
||||||
false, testCallback.updatedSslContext, Arrays.asList("managed-mtls", "h2"));
|
false, testCallback.updatedSslContext, Arrays.asList("managed-mtls", "h2"));
|
||||||
|
|
@ -260,8 +255,7 @@ public class SdsSslContextProviderTest {
|
||||||
/* validationContextName= */ "valid1",
|
/* validationContextName= */ "valid1",
|
||||||
/* matchSubjectAltNames= */ null,
|
/* matchSubjectAltNames= */ null,
|
||||||
/* alpnProtocols= */ Arrays.asList("managed-mtls", "h2"));
|
/* alpnProtocols= */ Arrays.asList("managed-mtls", "h2"));
|
||||||
SecretVolumeSslContextProviderTest.TestCallback testCallback =
|
TestCallback testCallback = getValueThruCallback(provider);
|
||||||
SecretVolumeSslContextProviderTest.getValueThruCallback(provider);
|
|
||||||
|
|
||||||
doChecksOnSslContext(
|
doChecksOnSslContext(
|
||||||
true, testCallback.updatedSslContext, Arrays.asList("managed-mtls", "h2"));
|
true, testCallback.updatedSslContext, Arrays.asList("managed-mtls", "h2"));
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,17 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_KEY_FILE
|
||||||
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
|
||||||
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE;
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE;
|
||||||
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE;
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext;
|
||||||
|
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.getValueThruCallback;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
|
||||||
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
|
||||||
|
import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.CertStoreException;
|
import java.security.cert.CertStoreException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.List;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -371,22 +372,6 @@ public class SecretVolumeSslContextProviderTest {
|
||||||
doChecksOnSslContext(server, sslContext, /* expectedApnProtos= */ null);
|
doChecksOnSslContext(server, sslContext, /* expectedApnProtos= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void doChecksOnSslContext(boolean server, SslContext sslContext,
|
|
||||||
List<String> expectedApnProtos) {
|
|
||||||
if (server) {
|
|
||||||
assertThat(sslContext.isServer()).isTrue();
|
|
||||||
} else {
|
|
||||||
assertThat(sslContext.isClient()).isTrue();
|
|
||||||
}
|
|
||||||
List<String> apnProtos = sslContext.applicationProtocolNegotiator().protocols();
|
|
||||||
assertThat(apnProtos).isNotNull();
|
|
||||||
if (expectedApnProtos != null) {
|
|
||||||
assertThat(apnProtos).isEqualTo(expectedApnProtos);
|
|
||||||
} else {
|
|
||||||
assertThat(apnProtos).contains("h2");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getProviderForServer() throws IOException, CertificateException, CertStoreException {
|
public void getProviderForServer() throws IOException, CertificateException, CertStoreException {
|
||||||
sslContextForEitherWithBothCertAndTrust(
|
sslContextForEitherWithBothCertAndTrust(
|
||||||
|
|
@ -421,32 +406,6 @@ public class SecretVolumeSslContextProviderTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TestCallback implements SslContextProvider.Callback {
|
|
||||||
|
|
||||||
SslContext updatedSslContext;
|
|
||||||
Throwable updatedThrowable;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateSecret(SslContext sslContext) {
|
|
||||||
updatedSslContext = sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onException(Throwable throwable) {
|
|
||||||
updatedThrowable = throwable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to get the value thru directExecutor callback. Because of directExecutor this is
|
|
||||||
* a synchronous callback - so need to provide a listener.
|
|
||||||
*/
|
|
||||||
static TestCallback getValueThruCallback(SslContextProvider provider) {
|
|
||||||
TestCallback testCallback = new TestCallback();
|
|
||||||
provider.addCallback(testCallback, MoreExecutors.directExecutor());
|
|
||||||
return testCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getProviderForServer_both_callsback() throws IOException {
|
public void getProviderForServer_both_callsback() throws IOException {
|
||||||
SecretVolumeServerSslContextProvider provider =
|
SecretVolumeServerSslContextProvider provider =
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FI
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
||||||
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
|
||||||
|
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
|
||||||
import io.grpc.internal.testing.TestUtils;
|
import io.grpc.internal.testing.TestUtils;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.CertStoreException;
|
import java.security.cert.CertStoreException;
|
||||||
|
|
@ -80,6 +81,100 @@ public class SdsTrustManagerFactoryTest {
|
||||||
.isEqualTo(CertificateUtils.toX509Certificates(TestUtils.loadCert(CA_PEM_FILE))[0]);
|
.isEqualTo(CertificateUtils.toX509Certificates(TestUtils.loadCert(CA_PEM_FILE))[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructor_fromRootCert()
|
||||||
|
throws CertificateException, IOException, CertStoreException {
|
||||||
|
X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE);
|
||||||
|
CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1",
|
||||||
|
"san2");
|
||||||
|
SdsTrustManagerFactory factory =
|
||||||
|
new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext);
|
||||||
|
assertThat(factory).isNotNull();
|
||||||
|
TrustManager[] tms = factory.getTrustManagers();
|
||||||
|
assertThat(tms).isNotNull();
|
||||||
|
assertThat(tms).hasLength(1);
|
||||||
|
TrustManager myTm = tms[0];
|
||||||
|
assertThat(myTm).isInstanceOf(SdsX509TrustManager.class);
|
||||||
|
SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) myTm;
|
||||||
|
X509Certificate[] acceptedIssuers = sdsX509TrustManager.getAcceptedIssuers();
|
||||||
|
assertThat(acceptedIssuers).isNotNull();
|
||||||
|
assertThat(acceptedIssuers).hasLength(1);
|
||||||
|
X509Certificate caCert = acceptedIssuers[0];
|
||||||
|
assertThat(caCert)
|
||||||
|
.isEqualTo(CertificateUtils.toX509Certificates(TestUtils.loadCert(CA_PEM_FILE))[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorRootCert_checkServerTrusted()
|
||||||
|
throws CertificateException, IOException, CertStoreException {
|
||||||
|
X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE);
|
||||||
|
CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1",
|
||||||
|
"waterzooi.test.google.be");
|
||||||
|
SdsTrustManagerFactory factory =
|
||||||
|
new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext);
|
||||||
|
SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0];
|
||||||
|
X509Certificate[] serverChain =
|
||||||
|
CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE));
|
||||||
|
sdsX509TrustManager.checkServerTrusted(serverChain, "RSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorRootCert_nonStaticContext_throwsException()
|
||||||
|
throws CertificateException, IOException, CertStoreException {
|
||||||
|
X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE);
|
||||||
|
try {
|
||||||
|
new SdsTrustManagerFactory(
|
||||||
|
new X509Certificate[] {x509Cert}, getCertContextFromPath(CA_PEM_FILE));
|
||||||
|
Assert.fail("no exception thrown");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertThat(expected)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("only static certificateValidationContext expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorRootCert_checkServerTrusted_throwsException()
|
||||||
|
throws CertificateException, IOException, CertStoreException {
|
||||||
|
X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE);
|
||||||
|
CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1",
|
||||||
|
"san2");
|
||||||
|
SdsTrustManagerFactory factory =
|
||||||
|
new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext);
|
||||||
|
SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0];
|
||||||
|
X509Certificate[] serverChain =
|
||||||
|
CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE));
|
||||||
|
try {
|
||||||
|
sdsX509TrustManager.checkServerTrusted(serverChain, "RSA");
|
||||||
|
Assert.fail("no exception thrown");
|
||||||
|
} catch (CertificateException expected) {
|
||||||
|
assertThat(expected)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Peer certificate SAN check failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorRootCert_checkClientTrusted_throwsException()
|
||||||
|
throws CertificateException, IOException, CertStoreException {
|
||||||
|
X509Certificate x509Cert = TestUtils.loadX509Cert(CA_PEM_FILE);
|
||||||
|
CertificateValidationContext staticValidationContext = buildStaticValidationContext("san1",
|
||||||
|
"san2");
|
||||||
|
SdsTrustManagerFactory factory =
|
||||||
|
new SdsTrustManagerFactory(new X509Certificate[]{x509Cert}, staticValidationContext);
|
||||||
|
SdsX509TrustManager sdsX509TrustManager = (SdsX509TrustManager) factory.getTrustManagers()[0];
|
||||||
|
X509Certificate[] clientChain =
|
||||||
|
CertificateUtils.toX509Certificates(TestUtils.loadCert(SERVER_1_PEM_FILE));
|
||||||
|
try {
|
||||||
|
sdsX509TrustManager.checkClientTrusted(clientChain, "RSA");
|
||||||
|
Assert.fail("no exception thrown");
|
||||||
|
} catch (CertificateException expected) {
|
||||||
|
assertThat(expected)
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("Peer certificate SAN check failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkServerTrusted_goodCert()
|
public void checkServerTrusted_goodCert()
|
||||||
throws CertificateException, IOException, CertStoreException {
|
throws CertificateException, IOException, CertStoreException {
|
||||||
|
|
@ -156,4 +251,13 @@ public class SdsTrustManagerFactoryTest {
|
||||||
DataSource.newBuilder().setInlineBytes(ByteString.copyFrom(x509Cert.getEncoded())))
|
DataSource.newBuilder().setInlineBytes(ByteString.copyFrom(x509Cert.getEncoded())))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final CertificateValidationContext buildStaticValidationContext(
|
||||||
|
String... verifySans) {
|
||||||
|
CertificateValidationContext.Builder builder = CertificateValidationContext.newBuilder();
|
||||||
|
for (String san : verifySans) {
|
||||||
|
builder.addMatchSubjectAltNames(StringMatcher.newBuilder().setExact(san));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue