/* * * Copyright 2020 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 certprovider import ( "fmt" "sync" ) // provStore is the global singleton certificate provider store. var provStore = &store{ providers: make(map[storeKey]*wrappedProvider), } // storeKey acts as the key to the map of providers maintained by the store. A // combination of provider name and configuration is used to uniquely identify // every provider instance in the store. Go maps need to be indexed by // comparable types, so the provider configuration is converted from // `interface{}` to string using the ParseConfig method while creating this key. type storeKey struct { // name of the certificate provider. name string // configuration of the certificate provider in string form. config string // opts contains the certificate name and other keyMaterial options. opts BuildOptions } // wrappedProvider wraps a provider instance with a reference count. type wrappedProvider struct { Provider refCount int // A reference to the key and store are also kept here to override the // Close method on the provider. storeKey storeKey store *store } // store is a collection of provider instances, safe for concurrent access. type store struct { mu sync.Mutex providers map[storeKey]*wrappedProvider } // Close overrides the Close method of the embedded provider. It releases the // reference held by the caller on the underlying provider and if the // provider's reference count reaches zero, it is removed from the store, and // its Close method is also invoked. func (wp *wrappedProvider) Close() { ps := wp.store ps.mu.Lock() defer ps.mu.Unlock() wp.refCount-- if wp.refCount == 0 { wp.Provider.Close() delete(ps.providers, wp.storeKey) } } // BuildableConfig wraps parsed provider configuration and functionality to // instantiate provider instances. type BuildableConfig struct { name string config []byte starter func(BuildOptions) Provider pStore *store } // NewBuildableConfig creates a new BuildableConfig with the given arguments. // Provider implementations are expected to invoke this function after parsing // the given configuration as part of their ParseConfig() method. // Equivalent configurations are expected to invoke this function with the same // config argument. func NewBuildableConfig(name string, config []byte, starter func(BuildOptions) Provider) *BuildableConfig { return &BuildableConfig{ name: name, config: config, starter: starter, pStore: provStore, } } // Build kicks off a provider instance with the wrapped configuration. Multiple // invocations of this method with the same opts will result in provider // instances being reused. func (bc *BuildableConfig) Build(opts BuildOptions) (Provider, error) { provStore.mu.Lock() defer provStore.mu.Unlock() sk := storeKey{ name: bc.name, config: string(bc.config), opts: opts, } if wp, ok := provStore.providers[sk]; ok { wp.refCount++ return wp, nil } provider := bc.starter(opts) if provider == nil { return nil, fmt.Errorf("provider(%q, %q).Build(%v) failed", sk.name, sk.config, opts) } wp := &wrappedProvider{ Provider: provider, refCount: 1, storeKey: sk, store: provStore, } provStore.providers[sk] = wp return wp, nil } // String returns the provider name and config as a colon separated string. func (bc *BuildableConfig) String() string { return fmt.Sprintf("%s:%s", bc.name, string(bc.config)) } // ParseConfig is a convenience function to create a BuildableConfig given a // provider name and configuration. Returns an error if there is no registered // builder for the given name or if the config parsing fails. func ParseConfig(name string, config interface{}) (*BuildableConfig, error) { parser := getBuilder(name) if parser == nil { return nil, fmt.Errorf("no certificate provider builder found for %q", name) } return parser.ParseConfig(config) } // GetProvider is a convenience function to create a provider given the name, // config and build options. func GetProvider(name string, config interface{}, opts BuildOptions) (Provider, error) { bc, err := ParseConfig(name, config) if err != nil { return nil, err } return bc.Build(opts) }