google.golang.org/grpc@v1.72.2/credentials/tls/certprovider/store.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package certprovider 20 21 import ( 22 "context" 23 "fmt" 24 "sync" 25 "sync/atomic" 26 ) 27 28 // provStore is the global singleton certificate provider store. 29 var provStore = &store{ 30 providers: make(map[storeKey]*wrappedProvider), 31 } 32 33 // storeKey acts as the key to the map of providers maintained by the store. A 34 // combination of provider name and configuration is used to uniquely identify 35 // every provider instance in the store. Go maps need to be indexed by 36 // comparable types, so the provider configuration is converted from `any` to 37 // `string` using the ParseConfig method while creating this key. 38 type storeKey struct { 39 // name of the certificate provider. 40 name string 41 // configuration of the certificate provider in string form. 42 config string 43 // opts contains the certificate name and other keyMaterial options. 44 opts BuildOptions 45 } 46 47 // wrappedProvider wraps a provider instance with a reference count. 48 type wrappedProvider struct { 49 Provider 50 refCount int 51 52 // A reference to the key and store are also kept here to override the 53 // Close method on the provider. 54 storeKey storeKey 55 store *store 56 } 57 58 // closedProvider always returns errProviderClosed error. 59 type closedProvider struct{} 60 61 func (c closedProvider) KeyMaterial(context.Context) (*KeyMaterial, error) { 62 return nil, errProviderClosed 63 } 64 65 func (c closedProvider) Close() { 66 } 67 68 // singleCloseWrappedProvider wraps a provider instance with a reference count 69 // to properly handle multiple calls to Close. 70 type singleCloseWrappedProvider struct { 71 provider atomic.Pointer[Provider] 72 } 73 74 // store is a collection of provider instances, safe for concurrent access. 75 type store struct { 76 mu sync.Mutex 77 providers map[storeKey]*wrappedProvider 78 } 79 80 // Close overrides the Close method of the embedded provider. It releases the 81 // reference held by the caller on the underlying provider and if the 82 // provider's reference count reaches zero, it is removed from the store, and 83 // its Close method is also invoked. 84 func (wp *wrappedProvider) Close() { 85 ps := wp.store 86 ps.mu.Lock() 87 defer ps.mu.Unlock() 88 89 wp.refCount-- 90 if wp.refCount == 0 { 91 wp.Provider.Close() 92 delete(ps.providers, wp.storeKey) 93 } 94 } 95 96 // Close overrides the Close method of the embedded provider to avoid release the 97 // already released reference. 98 func (w *singleCloseWrappedProvider) Close() { 99 newProvider := Provider(closedProvider{}) 100 oldProvider := w.provider.Swap(&newProvider) 101 (*oldProvider).Close() 102 } 103 104 // KeyMaterial returns the key material sourced by the Provider. 105 // Callers are expected to use the returned value as read-only. 106 func (w *singleCloseWrappedProvider) KeyMaterial(ctx context.Context) (*KeyMaterial, error) { 107 return (*w.provider.Load()).KeyMaterial(ctx) 108 } 109 110 // newSingleCloseWrappedProvider create wrapper a provider instance with a reference count 111 // to properly handle multiple calls to Close. 112 func newSingleCloseWrappedProvider(provider Provider) *singleCloseWrappedProvider { 113 w := &singleCloseWrappedProvider{} 114 w.provider.Store(&provider) 115 return w 116 } 117 118 // BuildableConfig wraps parsed provider configuration and functionality to 119 // instantiate provider instances. 120 type BuildableConfig struct { 121 name string 122 config []byte 123 starter func(BuildOptions) Provider 124 pStore *store 125 } 126 127 // NewBuildableConfig creates a new BuildableConfig with the given arguments. 128 // Provider implementations are expected to invoke this function after parsing 129 // the given configuration as part of their ParseConfig() method. 130 // Equivalent configurations are expected to invoke this function with the same 131 // config argument. 132 func NewBuildableConfig(name string, config []byte, starter func(BuildOptions) Provider) *BuildableConfig { 133 return &BuildableConfig{ 134 name: name, 135 config: config, 136 starter: starter, 137 pStore: provStore, 138 } 139 } 140 141 // Build kicks off a provider instance with the wrapped configuration. Multiple 142 // invocations of this method with the same opts will result in provider 143 // instances being reused. 144 func (bc *BuildableConfig) Build(opts BuildOptions) (Provider, error) { 145 provStore.mu.Lock() 146 defer provStore.mu.Unlock() 147 148 sk := storeKey{ 149 name: bc.name, 150 config: string(bc.config), 151 opts: opts, 152 } 153 if wp, ok := provStore.providers[sk]; ok { 154 wp.refCount++ 155 return newSingleCloseWrappedProvider(wp), nil 156 } 157 158 provider := bc.starter(opts) 159 if provider == nil { 160 return nil, fmt.Errorf("provider(%q, %q).Build(%v) failed", sk.name, sk.config, opts) 161 } 162 wp := &wrappedProvider{ 163 Provider: provider, 164 refCount: 1, 165 storeKey: sk, 166 store: provStore, 167 } 168 provStore.providers[sk] = wp 169 return newSingleCloseWrappedProvider(wp), nil 170 } 171 172 // String returns the provider name and config as a colon separated string. 173 func (bc *BuildableConfig) String() string { 174 return fmt.Sprintf("%s:%s", bc.name, string(bc.config)) 175 } 176 177 // ParseConfig is a convenience function to create a BuildableConfig given a 178 // provider name and configuration. Returns an error if there is no registered 179 // builder for the given name or if the config parsing fails. 180 func ParseConfig(name string, config any) (*BuildableConfig, error) { 181 parser := getBuilder(name) 182 if parser == nil { 183 return nil, fmt.Errorf("no certificate provider builder found for %q", name) 184 } 185 return parser.ParseConfig(config) 186 } 187 188 // GetProvider is a convenience function to create a provider given the name, 189 // config and build options. 190 func GetProvider(name string, config any, opts BuildOptions) (Provider, error) { 191 bc, err := ParseConfig(name, config) 192 if err != nil { 193 return nil, err 194 } 195 return bc.Build(opts) 196 }