diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/BUILD b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/BUILD index ce6df23f97a..ba39e251e0d 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/BUILD @@ -12,14 +12,17 @@ go_library( name = "go_default_library", srcs = [ "config.go", + "plugins.go", "types.go", ], tags = ["automanaged"], deps = [ "//vendor/github.com/ghodss/yaml:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library", + "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/identity:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/secretbox:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go index 6e11d617583..8d86339b09b 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/config.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/storage/value" aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes" + "k8s.io/apiserver/pkg/storage/value/encrypt/envelope" "k8s.io/apiserver/pkg/storage/value/encrypt/identity" "k8s.io/apiserver/pkg/storage/value/encrypt/secretbox" ) @@ -38,6 +39,7 @@ const ( aesCBCTransformerPrefixV1 = "k8s:enc:aescbc:v1:" aesGCMTransformerPrefixV1 = "k8s:enc:aesgcm:v1:" secretboxTransformerPrefixV1 = "k8s:enc:secretbox:v1:" + kmsTransformerPrefixV1 = "k8s:enc:kms:v1:" ) // GetTransformerOverrides returns the transformer overrides by reading and parsing the encryption provider configuration file @@ -144,6 +146,27 @@ func GetPrefixTransformers(config *ResourceConfig) ([]value.PrefixTransformer, e found = true } + if provider.KMS != nil { + if found == true { + return nil, fmt.Errorf("more than one provider specified in a single element, should split into different list elements") + } + + f, err := os.Open(provider.KMS.ConfigFile) + if err != nil { + return nil, fmt.Errorf("error opening KMS provider configuration file %q: %v", provider.KMS.ConfigFile, err) + } + defer f.Close() + envelopeService, pluginFound, err := KMSPluginRegistry.getPlugin(provider.KMS.Name, f) + if err != nil { + return nil, fmt.Errorf("could not configure KMS plugin %q, %v", provider.KMS.Name, err) + } + if pluginFound == false { + return nil, fmt.Errorf("KMS plugin %q not found", provider.KMS.Name) + } + transformer, err = getEnvelopePrefixTransformer(provider.KMS, envelopeService) + found = true + } + if err != nil { return result, err } @@ -258,3 +281,16 @@ func GetSecretboxPrefixTransformer(config *SecretboxConfig) (value.PrefixTransfo } return result, nil } + +// getEnvelopePrefixTransformer returns a prefix transformer from the provided config. +// envelopeService is used as the root of trust. +func getEnvelopePrefixTransformer(config *KMSConfig, envelopeService envelope.Service) (value.PrefixTransformer, error) { + envelopeTransformer, err := envelope.NewEnvelopeTransformer(envelopeService, config.CacheSize, aestransformer.NewCBCTransformer) + if err != nil { + return value.PrefixTransformer{}, err + } + return value.PrefixTransformer{ + Transformer: envelopeTransformer, + Prefix: []byte(kmsTransformerPrefixV1 + config.Name + ":"), + }, nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go new file mode 100644 index 00000000000..c55e6a20ddd --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/plugins.go @@ -0,0 +1,130 @@ +/* +Copyright 2017 The Kubernetes 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 encryptionconfig + +import ( + "bytes" + "io" + "io/ioutil" + "reflect" + "sync" + + "github.com/golang/glog" + + "k8s.io/apiserver/pkg/storage/value/encrypt/envelope" +) + +// Factory is a function that returns an envelope Service for encryption providers. +// The config parameter provides an io.Reader handler to the factory in +// order to load specific configurations. If no configuration is provided +// the parameter is nil. +type Factory func(config io.Reader) (envelope.Service, error) + +type cloudKMSFactory func(name string) (envelope.Service, error) + +// KMSPlugins contains all registered KMS options. +type KMSPlugins struct { + lock sync.RWMutex + registry map[string]Factory + cloudKMS cloudKMSFactory +} + +var ( + // PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled. + PluginEnabledFn = func(name string, config io.Reader) bool { + return true + } + + // KMSPluginRegistry contains the registered KMS plugins which can be used for configuring + // encryption providers. + KMSPluginRegistry = KMSPlugins{} +) + +// PluginEnabledFunc is a function type that can provide an external check on whether an admission plugin may be enabled +type PluginEnabledFunc func(name string, config io.Reader) bool + +// Register registers a plugin Factory by name. This +// is expected to happen during app startup. +func (ps *KMSPlugins) Register(name string, plugin Factory) { + ps.lock.Lock() + defer ps.lock.Unlock() + _, found := ps.registry[name] + if ps.registry == nil { + ps.registry = map[string]Factory{} + } + if found { + glog.Fatalf("KMS plugin %q was registered twice", name) + } + glog.V(1).Infof("Registered KMS plugin %q", name) + ps.registry[name] = plugin +} + +// RegisterCloudProvidedKMSPlugin registers the cloud's KMS provider as +// an envelope.Service. This service is provided by the cloudprovider interface. +func (ps *KMSPlugins) RegisterCloudProvidedKMSPlugin(cloudKMSGetter cloudKMSFactory) { + ps.cloudKMS = cloudKMSGetter +} + +// getPlugin creates an instance of the named plugin. It returns `false` if the +// the name is not known. The error is returned only when the named provider was +// known but failed to initialize. The config parameter specifies the io.Reader +// handler of the configuration file for the cloud provider, or nil for no configuration. +func (ps *KMSPlugins) getPlugin(name string, config io.Reader) (envelope.Service, bool, error) { + f, found := ps.fetchPluginFromRegistry(name) + if !found { + return nil, false, nil + } + + config1, config2, err := splitStream(config) + if err != nil { + return nil, true, err + } + if !PluginEnabledFn(name, config1) { + return nil, true, nil + } + + ret, err := f(config2) + return ret, true, err +} + +// fetchPluginFromRegistry tries to get a registered plugin with the requested name. +func (ps *KMSPlugins) fetchPluginFromRegistry(name string) (Factory, bool) { + ps.lock.RLock() + defer ps.lock.RUnlock() + // Map lookup defaults to single value context + f, found := ps.registry[name] + return f, found +} + +// getCloudProvidedPlugin creates an instance of the named cloud provided KMS plugin. +func (ps *KMSPlugins) getCloudProvidedPlugin(name string) (envelope.Service, error) { + return ps.cloudKMS(name) +} + +// splitStream reads the stream bytes and constructs two copies of it. +func splitStream(config io.Reader) (io.Reader, io.Reader, error) { + if config == nil || reflect.ValueOf(config).IsNil() { + return nil, nil, nil + } + + configBytes, err := ioutil.ReadAll(config) + if err != nil { + return nil, nil, err + } + + return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go index 1a88392c78a..c983e942dae 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig/types.go @@ -45,6 +45,8 @@ type ProviderConfig struct { Secretbox *SecretboxConfig `json:"secretbox,omitempty"` // identity is the (empty) configuration for the identity transformer. Identity *IdentityConfig `json:"identity,omitempty"` + // kms contains the name and path to configuration file for a KMS based envelope transformer. + KMS *KMSConfig `json:"kms,omitempty"` } // AESConfig contains the API configuration for an AES transformer. @@ -71,3 +73,14 @@ type Key struct { // IdentityConfig is an empty struct to allow identity transformer in provider configuration. type IdentityConfig struct{} + +// KMS contains the name and path to configuration file for a KMS based envelope transformer. +type KMSConfig struct { + // name is the name of the KMS plugin to be used. + Name string `json:"name"` + // cacheSize is the maximum number of secrets which are cached in memory. The default value is 1000. + // +optional + CacheSize int `json:"cachesize,omitempty"` + // configfile is the path to the configuration file for the named KMS provider. + ConfigFile string `json:"configfile"` +}