• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.xds.internal.security.certprovider;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import io.grpc.xds.internal.security.ReferenceCountingMap;
21 import io.grpc.xds.internal.security.certprovider.CertificateProvider.Watcher;
22 import java.io.Closeable;
23 import java.util.Objects;
24 import java.util.logging.Level;
25 import java.util.logging.Logger;
26 import javax.annotation.concurrent.ThreadSafe;
27 
28 /**
29  * Global map of all ref-counted {@link CertificateProvider}s that have been instantiated in
30  * the application. Also  propagates updates received from a {@link CertificateProvider} to all
31  * the {@link Watcher}s registered for that CertificateProvider. The Store is meant to be
32  * used internally by gRPC and *not* a public API.
33  */
34 @ThreadSafe
35 public final class CertificateProviderStore {
36   private static final Logger logger = Logger.getLogger(CertificateProviderStore.class.getName());
37 
38   private static CertificateProviderStore instance;
39   private final CertificateProviderRegistry certificateProviderRegistry;
40   private final ReferenceCountingMap<CertProviderKey, CertificateProvider> certProviderMap;
41 
42   /** Opaque Handle returned by {@link #createOrGetProvider}. */
43   @VisibleForTesting
44   final class Handle implements Closeable {
45     private final CertProviderKey key;
46     private final Watcher watcher;
47     @VisibleForTesting
48     final CertificateProvider certProvider;
49 
Handle(CertProviderKey key, Watcher watcher, CertificateProvider certProvider)50     private Handle(CertProviderKey key, Watcher watcher, CertificateProvider certProvider) {
51       this.key = key;
52       this.watcher = watcher;
53       this.certProvider = certProvider;
54     }
55 
56     /**
57      * Removes the associated {@link Watcher} for the {@link CertificateProvider} and
58      * decrements the ref-count. Releases the {@link CertificateProvider} if the ref-count
59      * has reached 0.
60      */
61     @Override
close()62     public synchronized void close() {
63       CertificateProvider.DistributorWatcher distWatcher = certProvider.getWatcher();
64       distWatcher.removeWatcher(watcher);
65       certProviderMap.release(key, certProvider);
66     }
67   }
68 
69   private static final class CertProviderKey {
70     private final String certName;
71     private final String pluginName;
72     private final boolean notifyCertUpdates;
73     private final Object config;
74 
CertProviderKey( String certName, String pluginName, boolean notifyCertUpdates, Object config)75     private CertProviderKey(
76         String certName, String pluginName, boolean notifyCertUpdates, Object config) {
77       this.certName = certName;
78       this.pluginName = pluginName;
79       this.notifyCertUpdates = notifyCertUpdates;
80       this.config = config;
81     }
82 
83     @Override
equals(Object o)84     public boolean equals(Object o) {
85       if (this == o) {
86         return true;
87       }
88       if (!(o instanceof CertProviderKey)) {
89         return false;
90       }
91       CertProviderKey that = (CertProviderKey) o;
92       return notifyCertUpdates == that.notifyCertUpdates
93           && Objects.equals(certName, that.certName)
94           && Objects.equals(pluginName, that.pluginName)
95           && Objects.equals(config, that.config);
96     }
97 
98     @Override
hashCode()99     public int hashCode() {
100       return Objects.hash(certName, pluginName, notifyCertUpdates, config);
101     }
102 
103     @Override
toString()104     public String toString() {
105       return "CertProviderKey{"
106           + "certName='"
107           + certName
108           + '\''
109           + ", pluginName='"
110           + pluginName
111           + '\''
112           + ", notifyCertUpdates="
113           + notifyCertUpdates
114           + ", config="
115           + config
116           + '}';
117     }
118   }
119 
120   private final class CertProviderFactory
121       implements ReferenceCountingMap.ValueFactory<CertProviderKey, CertificateProvider> {
122 
CertProviderFactory()123     private CertProviderFactory() {
124     }
125 
126     @Override
create(CertProviderKey key)127     public CertificateProvider create(CertProviderKey key) {
128       CertificateProviderProvider certProviderProvider =
129           certificateProviderRegistry.getProvider(key.pluginName);
130       if (certProviderProvider == null) {
131         throw new IllegalArgumentException("Provider not found for " + key.pluginName);
132       }
133       CertificateProvider certProvider = certProviderProvider.createCertificateProvider(
134               key.config, new CertificateProvider.DistributorWatcher(), key.notifyCertUpdates);
135       certProvider.start();
136       return certProvider;
137     }
138   }
139 
140   @VisibleForTesting
CertificateProviderStore(CertificateProviderRegistry certificateProviderRegistry)141   public CertificateProviderStore(CertificateProviderRegistry certificateProviderRegistry) {
142     this.certificateProviderRegistry = certificateProviderRegistry;
143     certProviderMap = new ReferenceCountingMap<>(new CertProviderFactory());
144   }
145 
146   /**
147    * Creates or retrieves a {@link CertificateProvider} instance, increments its ref-count and
148    * registers the watcher passed. Returns a {@link Handle} that can be {@link Handle#close()}d when
149    * the instance is no longer needed by the caller.
150    *
151    * @param notifyCertUpdates when true, the caller is interested in identity cert updates. When
152    *     false, the caller cannot depend on receiving the {@link Watcher#updateCertificate}
153    *     callbacks but may still receive these callbacks which should be ignored.
154    * @throws IllegalArgumentException in case of errors in processing config or the plugin is
155    *     incapable of sending cert updates when notifyCertUpdates is true.
156    * @throws UnsupportedOperationException if the plugin is incapable of sending cert updates when
157    *     notifyCertUpdates is true.
158    */
createOrGetProvider( String certName, String pluginName, Object config, Watcher watcher, boolean notifyCertUpdates)159   public synchronized Handle createOrGetProvider(
160       String certName,
161       String pluginName,
162       Object config,
163       Watcher watcher,
164       boolean notifyCertUpdates) {
165     if (!notifyCertUpdates) {
166       // we try to get a provider first for notifyCertUpdates==true always
167       try {
168         return createProviderHelper(certName, pluginName, config, watcher, true);
169       } catch (UnsupportedOperationException uoe) {
170         // ignore & log exception and fall thru to create a provider with actual value
171         logger.log(Level.FINE, "Trying to get provider for notifyCertUpdates==true", uoe);
172       }
173     }
174     return createProviderHelper(certName, pluginName, config, watcher, notifyCertUpdates);
175   }
176 
createProviderHelper( String certName, String pluginName, Object config, Watcher watcher, boolean notifyCertUpdates)177   private synchronized Handle createProviderHelper(
178       String certName,
179       String pluginName,
180       Object config,
181       Watcher watcher,
182       boolean notifyCertUpdates) {
183     CertProviderKey key = new CertProviderKey(certName, pluginName, notifyCertUpdates, config);
184     CertificateProvider provider = certProviderMap.get(key);
185     CertificateProvider.DistributorWatcher distWatcher = provider.getWatcher();
186     distWatcher.addWatcher(watcher);
187     return new Handle(key, watcher, provider);
188   }
189 
190   /** Gets the CertificateProviderStore singleton instance. */
getInstance()191   public static synchronized CertificateProviderStore getInstance() {
192     if (instance == null) {
193       instance = new CertificateProviderStore(CertificateProviderRegistry.getInstance());
194     }
195     return instance;
196   }
197 }
198