blob: 08ed25bf9cb374003c0d34767d12e413bc354fff [file] [log] [blame]
/*
* 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 com.google.common.annotations.VisibleForTesting;
import io.grpc.xds.internal.certprovider.CertificateProvider.Watcher;
import io.grpc.xds.internal.security.ReferenceCountingMap;
import java.io.Closeable;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
/**
* Global map of all ref-counted {@link CertificateProvider}s that have been instantiated in
* the application. Also propagates updates received from a {@link CertificateProvider} to all
* the {@link Watcher}s registered for that CertificateProvider. The Store is meant to be
* used internally by gRPC and *not* a public API.
*/
@ThreadSafe
public final class CertificateProviderStore {
private static final Logger logger = Logger.getLogger(CertificateProviderStore.class.getName());
private static CertificateProviderStore instance;
private final CertificateProviderRegistry certificateProviderRegistry;
private final ReferenceCountingMap<CertProviderKey, CertificateProvider> certProviderMap;
/** Opaque Handle returned by {@link #createOrGetProvider}. */
@VisibleForTesting
final class Handle implements Closeable {
private final CertProviderKey key;
private final Watcher watcher;
@VisibleForTesting
final CertificateProvider certProvider;
private Handle(CertProviderKey key, Watcher watcher, CertificateProvider certProvider) {
this.key = key;
this.watcher = watcher;
this.certProvider = certProvider;
}
/**
* Removes the associated {@link Watcher} for the {@link CertificateProvider} and
* decrements the ref-count. Releases the {@link CertificateProvider} if the ref-count
* has reached 0.
*/
@Override
public synchronized void close() {
CertificateProvider.DistributorWatcher distWatcher = certProvider.getWatcher();
distWatcher.removeWatcher(watcher);
certProviderMap.release(key, certProvider);
}
}
private static final class CertProviderKey {
private final String certName;
private final String pluginName;
private final boolean notifyCertUpdates;
private final Object config;
private CertProviderKey(
String certName, String pluginName, boolean notifyCertUpdates, Object config) {
this.certName = certName;
this.pluginName = pluginName;
this.notifyCertUpdates = notifyCertUpdates;
this.config = config;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CertProviderKey)) {
return false;
}
CertProviderKey that = (CertProviderKey) o;
return notifyCertUpdates == that.notifyCertUpdates
&& Objects.equals(certName, that.certName)
&& Objects.equals(pluginName, that.pluginName)
&& Objects.equals(config, that.config);
}
@Override
public int hashCode() {
return Objects.hash(certName, pluginName, notifyCertUpdates, config);
}
@Override
public String toString() {
return "CertProviderKey{"
+ "certName='"
+ certName
+ '\''
+ ", pluginName='"
+ pluginName
+ '\''
+ ", notifyCertUpdates="
+ notifyCertUpdates
+ ", config="
+ config
+ '}';
}
}
private final class CertProviderFactory
implements ReferenceCountingMap.ValueFactory<CertProviderKey, CertificateProvider> {
private CertProviderFactory() {
}
@Override
public CertificateProvider create(CertProviderKey key) {
CertificateProviderProvider certProviderProvider =
certificateProviderRegistry.getProvider(key.pluginName);
if (certProviderProvider == null) {
throw new IllegalArgumentException("Provider not found for " + key.pluginName);
}
CertificateProvider certProvider = certProviderProvider.createCertificateProvider(
key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates);
certProvider.start();
return certProvider;
}
}
@VisibleForTesting
public CertificateProviderStore(CertificateProviderRegistry certificateProviderRegistry) {
this.certificateProviderRegistry = certificateProviderRegistry;
certProviderMap = new ReferenceCountingMap<>(new CertProviderFactory());
}
/**
* Creates or retrieves a {@link CertificateProvider} instance, increments its ref-count and
* registers the watcher passed. Returns a {@link Handle} that can be {@link Handle#close()}d when
* the instance is no longer needed by the caller.
*
* @param notifyCertUpdates when true, the caller is interested in identity cert updates. When
* false, the caller cannot depend on receiving the {@link Watcher#updateCertificate}
* callbacks but may still receive these callbacks which should be ignored.
* @throws IllegalArgumentException in case of errors in processing config or the plugin is
* incapable of sending cert updates when notifyCertUpdates is true.
* @throws UnsupportedOperationException if the plugin is incapable of sending cert updates when
* notifyCertUpdates is true.
*/
public synchronized Handle createOrGetProvider(
String certName,
String pluginName,
Object config,
Watcher watcher,
boolean notifyCertUpdates) {
if (!notifyCertUpdates) {
// we try to get a provider first for notifyCertUpdates==true always
try {
return createProviderHelper(certName, pluginName, config, watcher, true);
} catch (UnsupportedOperationException uoe) {
// ignore & log exception and fall thru to create a provider with actual value
logger.log(Level.FINE, "Trying to get provider for notifyCertUpdates==true", uoe);
}
}
return createProviderHelper(certName, pluginName, config, watcher, notifyCertUpdates);
}
private synchronized Handle createProviderHelper(
String certName,
String pluginName,
Object config,
Watcher watcher,
boolean notifyCertUpdates) {
CertProviderKey key = new CertProviderKey(certName, pluginName, notifyCertUpdates, config);
CertificateProvider provider = certProviderMap.get(key);
CertificateProvider.DistributorWatcher distWatcher = provider.getWatcher();
distWatcher.addWatcher(watcher);
return new Handle(key, watcher, provider);
}
/** Gets the CertificateProviderStore singleton instance. */
public static synchronized CertificateProviderStore getInstance() {
if (instance == null) {
instance = new CertificateProviderStore(CertificateProviderRegistry.getInstance());
}
return instance;
}
}