blob: 3ecbba6cb372c60ad6ba71866be5e70c5e4f0783 [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 static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.Status;
import io.grpc.xds.internal.security.Closeable;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A plug-in that provides certificates required by the xDS security component and created
* using the certificate-provider config from the xDS server.
*
* <p>We may move this out of the internal package and make this an official API in the future.
*
* <p>The plugin fetches certificates - root and optionally identity cert - required by xDS
* security.
*/
public abstract class CertificateProvider implements Closeable {
/** A watcher is registered to receive certificate updates. */
public interface Watcher {
void updateCertificate(PrivateKey key, List<X509Certificate> certChain);
void updateTrustedRoots(List<X509Certificate> trustedRoots);
void onError(Status errorStatus);
}
@VisibleForTesting
public static final class DistributorWatcher implements Watcher {
private PrivateKey privateKey;
private List<X509Certificate> certChain;
private List<X509Certificate> trustedRoots;
@VisibleForTesting
final Set<Watcher> downstreamWatchers = new HashSet<>();
synchronized void addWatcher(Watcher watcher) {
downstreamWatchers.add(watcher);
if (privateKey != null && certChain != null) {
sendLastCertificateUpdate(watcher);
}
if (trustedRoots != null) {
sendLastTrustedRootsUpdate(watcher);
}
}
synchronized void removeWatcher(Watcher watcher) {
downstreamWatchers.remove(watcher);
}
@VisibleForTesting public Set<Watcher> getDownstreamWatchers() {
return Collections.unmodifiableSet(downstreamWatchers);
}
private void sendLastCertificateUpdate(Watcher watcher) {
watcher.updateCertificate(privateKey, certChain);
}
private void sendLastTrustedRootsUpdate(Watcher watcher) {
watcher.updateTrustedRoots(trustedRoots);
}
@Override
public synchronized void updateCertificate(PrivateKey key, List<X509Certificate> certChain) {
checkNotNull(key, "key");
checkNotNull(certChain, "certChain");
privateKey = key;
this.certChain = certChain;
for (Watcher watcher : downstreamWatchers) {
sendLastCertificateUpdate(watcher);
}
}
@Override
public synchronized void updateTrustedRoots(List<X509Certificate> trustedRoots) {
checkNotNull(trustedRoots, "trustedRoots");
this.trustedRoots = trustedRoots;
for (Watcher watcher : downstreamWatchers) {
sendLastTrustedRootsUpdate(watcher);
}
}
@Override
public synchronized void onError(Status errorStatus) {
for (Watcher watcher : downstreamWatchers) {
watcher.onError(errorStatus);
}
}
X509Certificate getLastIdentityCert() {
if (certChain != null && !certChain.isEmpty()) {
return certChain.get(0);
}
return null;
}
void close() {
downstreamWatchers.clear();
clearValues();
}
void clearValues() {
privateKey = null;
certChain = null;
trustedRoots = null;
}
}
/**
* Concrete subclasses will call this to register the {@link Watcher}.
*
* @param watcher to register
* @param notifyCertUpdates if true, the provider is required to call the watcher’s
* updateCertificate method. Implies the Provider is capable of minting certificates.
* Used by server-side and mTLS client-side. Note the Provider is always required
* to call updateTrustedRoots to provide trusted-root updates.
*/
protected CertificateProvider(DistributorWatcher watcher, boolean notifyCertUpdates) {
this.watcher = watcher;
this.notifyCertUpdates = notifyCertUpdates;
}
/** Releases all resources and stop cert refreshes and watcher updates. */
@Override
public abstract void close();
/** Starts the cert refresh and watcher update cycle. */
public abstract void start();
private final DistributorWatcher watcher;
private final boolean notifyCertUpdates;
public DistributorWatcher getWatcher() {
return watcher;
}
public boolean isNotifyCertUpdates() {
return notifyCertUpdates;
}
}