mirror of https://github.com/k3s-io/k3s
Add configuration options for encryption providers
Add location transformer, config for transformers Location transformer helps choose the most specific transformer for read/write operations depending on the path of resource being accessed. Configuration allows use of --experimental-encryption-provider-config to set up encryption providers. Only AEAD is supported at the moment. Add new files to BUILD, AEAD => k8s-aes-gcm Use group resources to select encryption provider Update tests for configuration parsing Remove location transformer Allow specifying providers per resource group in configuration Add IdentityTransformer configuration option Fix minor issues with initial AEAD implementation Unified parsing of all configurations Parse configuration using a union struct Run configuration parsing in APIserver, refactor parsing More gdoc, fix minor bugs Add test coverage for combined transformers Use table driven tests for encryptionconfigpull/6/head
parent
68dd748ba1
commit
9760d00d08
|
@ -87,6 +87,7 @@ go_library(
|
|||
"//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/options/encryptionconfig:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
||||
|
|
|
@ -50,6 +50,7 @@ import (
|
|||
genericregistry "k8s.io/apiserver/pkg/registry/generic"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/filters"
|
||||
"k8s.io/apiserver/pkg/server/options/encryptionconfig"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||
|
@ -496,6 +497,16 @@ func BuildStorageFactory(s *options.ServerRunOptions) (*serverstorage.DefaultSto
|
|||
storageFactory.SetEtcdLocation(groupResource, servers)
|
||||
}
|
||||
|
||||
if s.Etcd.EncryptionProviderConfigFilepath != "" {
|
||||
transformerOverrides, err := encryptionconfig.GetTransformerOverrides(s.Etcd.EncryptionProviderConfigFilepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for groupResource, transformer := range transformerOverrides {
|
||||
storageFactory.SetTransformer(groupResource, transformer)
|
||||
}
|
||||
}
|
||||
|
||||
return storageFactory, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -357,6 +357,7 @@ staging/src/k8s.io/apiserver/pkg/storage/names
|
|||
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
|
||||
staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory
|
||||
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes
|
||||
staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/identity
|
||||
staging/src/k8s.io/apiserver/pkg/util/flushwriter
|
||||
staging/src/k8s.io/apiserver/pkg/util/logs
|
||||
staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"config.go",
|
||||
"types.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/github.com/ghodss/yaml: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/identity:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["encryptionconfig_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
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 (
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
yaml "github.com/ghodss/yaml"
|
||||
|
||||
"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/identity"
|
||||
)
|
||||
|
||||
const (
|
||||
aesTransformerPrefixV1 = "k8s:enc:aes:v1:"
|
||||
)
|
||||
|
||||
// GetTransformerOverrides returns the transformer overrides by reading and parsing the encryption provider configuration file
|
||||
func GetTransformerOverrides(filepath string) (map[schema.GroupResource]value.Transformer, error) {
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening encryption provider configuration file %q: %v", filepath, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
result, err := ParseEncryptionConfiguration(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while parsing encryption provider configuration file %q: %v", filepath, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseEncryptionConfiguration parses configuration data and returns the transformer overrides
|
||||
func ParseEncryptionConfiguration(f io.Reader) (map[schema.GroupResource]value.Transformer, error) {
|
||||
configFileContents, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read contents: %v", err)
|
||||
}
|
||||
|
||||
var config EncryptionConfig
|
||||
err = yaml.Unmarshal(configFileContents, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while parsing file: %v", err)
|
||||
}
|
||||
|
||||
if config.Kind != "EncryptionConfig" && config.Kind != "" {
|
||||
return nil, fmt.Errorf("invalid configuration kind %q provided", config.Kind)
|
||||
}
|
||||
if config.Kind == "" {
|
||||
return nil, fmt.Errorf("invalid configuration file, missing Kind")
|
||||
}
|
||||
// TODO config.APIVersion is unchecked
|
||||
|
||||
resourceToPrefixTransformer := map[schema.GroupResource][]value.PrefixTransformer{}
|
||||
|
||||
// For each entry in the configuration
|
||||
for _, resourceConfig := range config.Resources {
|
||||
transformers, err := GetPrefixTransformers(&resourceConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For each resource, create a list of providers to use
|
||||
for _, resource := range resourceConfig.Resources {
|
||||
gr := schema.ParseGroupResource(resource)
|
||||
resourceToPrefixTransformer[gr] = append(
|
||||
resourceToPrefixTransformer[gr], transformers...)
|
||||
}
|
||||
}
|
||||
|
||||
result := map[schema.GroupResource]value.Transformer{}
|
||||
for gr, transList := range resourceToPrefixTransformer {
|
||||
result[gr] = value.NewMutableTransformer(value.NewPrefixTransformers(fmt.Errorf("no matching prefix found"), transList...))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetPrefixTransformer constructs and returns the appropriate prefix transformers for the passed resource using its configuration
|
||||
func GetPrefixTransformers(config *ResourceConfig) ([]value.PrefixTransformer, error) {
|
||||
var result []value.PrefixTransformer
|
||||
for _, provider := range config.Providers {
|
||||
found := false
|
||||
|
||||
if provider.AES != nil {
|
||||
transformer, err := GetAESPrefixTransformer(provider.AES)
|
||||
found = true
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = append(result, transformer)
|
||||
}
|
||||
|
||||
if provider.Identity != nil {
|
||||
if found == true {
|
||||
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
|
||||
}
|
||||
found = true
|
||||
result = append(result, value.PrefixTransformer{
|
||||
Transformer: identity.NewEncryptCheckTransformer(),
|
||||
Prefix: []byte{},
|
||||
})
|
||||
}
|
||||
|
||||
if found == false {
|
||||
return result, fmt.Errorf("invalid provider configuration provided")
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetAESPrefixTransformer returns a prefix transformer from the provided configuration
|
||||
func GetAESPrefixTransformer(config *AESConfig) (value.PrefixTransformer, error) {
|
||||
var result value.PrefixTransformer
|
||||
|
||||
if len(config.Keys) == 0 {
|
||||
return result, fmt.Errorf("aes provider has no valid keys")
|
||||
}
|
||||
for _, key := range config.Keys {
|
||||
if key.Name == "" {
|
||||
return result, fmt.Errorf("key with invalid name provided")
|
||||
}
|
||||
if key.Secret == "" {
|
||||
return result, fmt.Errorf("key %v has no provided secret", key.Name)
|
||||
}
|
||||
}
|
||||
|
||||
keyTransformers := []value.PrefixTransformer{}
|
||||
|
||||
for _, keyData := range config.Keys {
|
||||
key, err := base64.StdEncoding.DecodeString(keyData.Secret)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("could not obtain secret for named key %s: %s", keyData.Name, err)
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("error while creating cipher for named key %s: %s", keyData.Name, err)
|
||||
}
|
||||
|
||||
// Create a new PrefixTransformer for this key
|
||||
keyTransformers = append(keyTransformers,
|
||||
value.PrefixTransformer{
|
||||
Transformer: aestransformer.NewGCMTransformer(block),
|
||||
Prefix: []byte(keyData.Name + ":"),
|
||||
})
|
||||
}
|
||||
|
||||
// Create a prefixTransformer which can choose between these keys
|
||||
keyTransformer := value.NewPrefixTransformers(
|
||||
fmt.Errorf("no matching key was found for the provided AES transformer"), keyTransformers...)
|
||||
|
||||
// Create a PrefixTransformer which shall later be put in a list with other providers
|
||||
result = value.PrefixTransformer{
|
||||
Transformer: keyTransformer,
|
||||
Prefix: []byte(aesTransformerPrefixV1),
|
||||
}
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
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"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
)
|
||||
|
||||
const (
|
||||
sampleText = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
sampleContextText = "0123456789"
|
||||
|
||||
correctConfigWithIdentityFirst = `
|
||||
kind: EncryptionConfig
|
||||
apiVersion: v1
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
- namespaces
|
||||
providers:
|
||||
- identity: {}
|
||||
- aes:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: c2VjcmV0IGlzIHNlY3VyZQ==
|
||||
- name: key2
|
||||
secret: dGhpcyBpcyBwYXNzd29yZA==
|
||||
`
|
||||
|
||||
correctConfigWithAesFirst = `
|
||||
kind: EncryptionConfig
|
||||
apiVersion: v1
|
||||
resources:
|
||||
- resources:
|
||||
- secrets
|
||||
providers:
|
||||
- aes:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: c2VjcmV0IGlzIHNlY3VyZQ==
|
||||
- name: key2
|
||||
secret: dGhpcyBpcyBwYXNzd29yZA==
|
||||
- identity: {}
|
||||
`
|
||||
|
||||
incorrectConfigNoSecretForKey = `
|
||||
kind: EncryptionConfig
|
||||
apiVersion: v1
|
||||
resources:
|
||||
- resources:
|
||||
- namespaces
|
||||
- secrets
|
||||
providers:
|
||||
- aes:
|
||||
keys:
|
||||
- name: key1
|
||||
`
|
||||
|
||||
incorrectConfigInvalidKey = `
|
||||
kind: EncryptionConfig
|
||||
apiVersion: v1
|
||||
resources:
|
||||
- resources:
|
||||
- namespaces
|
||||
- secrets
|
||||
providers:
|
||||
- aes:
|
||||
keys:
|
||||
- name: key1
|
||||
secret: c2VjcmV0IGlzIHNlY3VyZQ==
|
||||
- name: key2
|
||||
secret: YSBzZWNyZXQgYSBzZWNyZXQ=
|
||||
`
|
||||
)
|
||||
|
||||
func TestEncryptionProviderConfigCorrect(t *testing.T) {
|
||||
// Creates two transformers with different ordering of identity and AES transformers.
|
||||
// Transforms data using one of them, and tries to untransform using both of them.
|
||||
// Repeats this for both the possible combinations.
|
||||
|
||||
identityFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithIdentityFirst))
|
||||
if err != nil {
|
||||
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithIdentityFirst)
|
||||
}
|
||||
|
||||
aesFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithAesFirst))
|
||||
if err != nil {
|
||||
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithAesFirst)
|
||||
}
|
||||
|
||||
// Pick the transformer for any of the returned resources.
|
||||
identityFirstTransformer := identityFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
|
||||
aesFirstTransformer := aesFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
|
||||
|
||||
context := value.DefaultContext([]byte(sampleContextText))
|
||||
originalText := []byte(sampleText)
|
||||
|
||||
testCases := []struct {
|
||||
WritingTransformer value.Transformer
|
||||
Name string
|
||||
AesStale bool
|
||||
IdentityStale bool
|
||||
}{
|
||||
{aesFirstTransformer, "aesFirst", false, true},
|
||||
{identityFirstTransformer, "identityFirst", true, false},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
transformedData, err := testCase.WritingTransformer.TransformToStorage(originalText, context)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error while transforming data to storage: %s", testCase.Name, err)
|
||||
}
|
||||
|
||||
aesUntransformedData, stale, err := aesFirstTransformer.TransformFromStorage(transformedData, context)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error while reading using aesFirst transformer: %s", testCase.Name, err)
|
||||
}
|
||||
if stale != testCase.AesStale {
|
||||
t.Fatalf("%s: wrong stale information on reading using aesFirst transformer, should be %v", testCase.Name, testCase.AesStale)
|
||||
}
|
||||
|
||||
identityUntransformedData, stale, err := identityFirstTransformer.TransformFromStorage(transformedData, context)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error while reading using identityFirst transformer: %s", testCase.Name, err)
|
||||
}
|
||||
if stale != testCase.IdentityStale {
|
||||
t.Fatalf("%s: wrong stale information on reading using identityFirst transformer, should be %v", testCase.Name, testCase.IdentityStale)
|
||||
}
|
||||
|
||||
if bytes.Compare(aesUntransformedData, originalText) != 0 {
|
||||
t.Fatalf("%s: aesFirst transformer transformed data incorrectly. Expected: %v, got %v", testCase.Name, originalText, aesUntransformedData)
|
||||
}
|
||||
|
||||
if bytes.Compare(identityUntransformedData, originalText) != 0 {
|
||||
t.Fatalf("%s: identityFirst transformer transformed data incorrectly. Expected: %v, got %v", testCase.Name, originalText, aesUntransformedData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throw error if key has no secret
|
||||
func TestEncryptionProviderConfigNoSecretForKey(t *testing.T) {
|
||||
if _, err := ParseEncryptionConfiguration(strings.NewReader(incorrectConfigNoSecretForKey)); err == nil {
|
||||
t.Fatalf("invalid configuration file (one key has no secret) got parsed:\n%s", incorrectConfigNoSecretForKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Throw error if invalid key for AES
|
||||
func TestEncryptionProviderConfigInvalidKey(t *testing.T) {
|
||||
if _, err := ParseEncryptionConfiguration(strings.NewReader(incorrectConfigInvalidKey)); err == nil {
|
||||
t.Fatalf("invalid configuration file (bad AES key) got parsed:\n%s", incorrectConfigInvalidKey)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
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
|
||||
|
||||
// EncryptionConfig stores the complete configuration for encryption providers.
|
||||
type EncryptionConfig struct {
|
||||
// kind is the type of configuration file.
|
||||
Kind string `json:"kind"`
|
||||
// apiVersion is the API version this file has to be parsed as.
|
||||
APIVersion string `json:"apiVersion"`
|
||||
// resources is a list containing resources, and their corresponding encryption providers.
|
||||
Resources []ResourceConfig `json:"resources"`
|
||||
}
|
||||
|
||||
// ResourceConfig stores per resource configuration.
|
||||
type ResourceConfig struct {
|
||||
// resources is a list of kubernetes resources which have to be encrypted.
|
||||
Resources []string `json:"resources"`
|
||||
// providers is a list of transformers to be used for reading and writing the resources to disk.
|
||||
// eg: aes, identity.
|
||||
Providers []ProviderConfig `json:"providers"`
|
||||
}
|
||||
|
||||
// ProviderConfig stores the provided configuration for an encryption provider.
|
||||
type ProviderConfig struct {
|
||||
// aes is the configuration for the AEAD-GCM transformer.
|
||||
AES *AESConfig `json:"aes,omitempty"`
|
||||
// identity is the (empty) configuration for the identity transformer.
|
||||
Identity *IdentityConfig `json:"identity,omitempty"`
|
||||
}
|
||||
|
||||
// AESConfig contains the API configuration for an AES transformer.
|
||||
type AESConfig struct {
|
||||
// keys is a list of keys to be used for creating the AES transformer.
|
||||
Keys []Key `json:"keys"`
|
||||
}
|
||||
|
||||
// Key contains name and secret of the provided key for AES transformer.
|
||||
type Key struct {
|
||||
// name is the name of the key to be used while storing data to disk.
|
||||
Name string `json:"name"`
|
||||
// secret is the actual AES key, encoded in base64. It has to be 16, 24 or 32 bytes long.
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
// IdentityConfig is an empty struct to allow identity transformer in provider configuration.
|
||||
type IdentityConfig struct{}
|
|
@ -30,7 +30,8 @@ import (
|
|||
)
|
||||
|
||||
type EtcdOptions struct {
|
||||
StorageConfig storagebackend.Config
|
||||
StorageConfig storagebackend.Config
|
||||
EncryptionProviderConfigFilepath string
|
||||
|
||||
EtcdServersOverrides []string
|
||||
|
||||
|
@ -109,6 +110,9 @@ func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
|
|||
|
||||
fs.BoolVar(&s.StorageConfig.Quorum, "etcd-quorum-read", s.StorageConfig.Quorum,
|
||||
"If true, enable quorum read.")
|
||||
|
||||
fs.StringVar(&s.EncryptionProviderConfigFilepath, "experimental-encryption-provider-config", s.EncryptionProviderConfigFilepath,
|
||||
"The file containing configuration for encryption providers to be used for storing secrets in etcd")
|
||||
}
|
||||
|
||||
func (s *EtcdOptions) ApplyTo(c *server.Config) error {
|
||||
|
|
|
@ -48,5 +48,6 @@ go_library(
|
|||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/recognizer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
)
|
||||
|
||||
// Backend describes the storage servers, the information here should be enough
|
||||
|
@ -109,6 +110,8 @@ type groupResourceOverrides struct {
|
|||
// decoderDecoratorFn is optional and may wrap the provided decoders (can add new decoders). The order of
|
||||
// returned decoders will be priority for attempt to decode.
|
||||
decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder
|
||||
// transformer is optional and shall encrypt that resource at rest.
|
||||
transformer value.Transformer
|
||||
}
|
||||
|
||||
// Apply overrides the provided config and options if the override has a value in that position
|
||||
|
@ -132,6 +135,9 @@ func (o groupResourceOverrides) Apply(config *storagebackend.Config, options *St
|
|||
if o.decoderDecoratorFn != nil {
|
||||
options.DecoderDecoratorFn = o.decoderDecoratorFn
|
||||
}
|
||||
if o.transformer != nil {
|
||||
config.Transformer = o.transformer
|
||||
}
|
||||
}
|
||||
|
||||
var _ StorageFactory = &DefaultStorageFactory{}
|
||||
|
@ -193,6 +199,12 @@ func (s *DefaultStorageFactory) SetSerializer(groupResource schema.GroupResource
|
|||
s.Overrides[groupResource] = overrides
|
||||
}
|
||||
|
||||
func (s *DefaultStorageFactory) SetTransformer(groupResource schema.GroupResource, transformer value.Transformer) {
|
||||
overrides := s.Overrides[groupResource]
|
||||
overrides.transformer = transformer
|
||||
s.Overrides[groupResource] = overrides
|
||||
}
|
||||
|
||||
// AddCohabitatingResources links resources together the order of the slice matters! its the priority order of lookup for finding a storage location
|
||||
func (s *DefaultStorageFactory) AddCohabitatingResources(groupResources ...schema.GroupResource) {
|
||||
for _, groupResource := range groupResources {
|
||||
|
|
|
@ -388,7 +388,7 @@ func (s *store) List(ctx context.Context, key, resourceVersion string, pred stor
|
|||
|
||||
elems := make([]*elemForDecode, 0, len(getResp.Kvs))
|
||||
for _, kv := range getResp.Kvs {
|
||||
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key))
|
||||
data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(kv.Key))
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to transform key %q: %v", key, err))
|
||||
continue
|
||||
|
|
|
@ -58,7 +58,7 @@ func newETCD3Storage(c storagebackend.Config) (storage.Interface, DestroyFunc, e
|
|||
}
|
||||
transformer := c.Transformer
|
||||
if transformer == nil {
|
||||
transformer = value.IdentityTransformer
|
||||
transformer = value.NewMutableTransformer(value.IdentityTransformer)
|
||||
}
|
||||
if c.Quorum {
|
||||
return etcd3.New(client, c.Codec, c.Prefix, transformer), destroyFunc, nil
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["identity.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library"],
|
||||
)
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 identity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
)
|
||||
|
||||
// encryptIdentityTransformer performs no transformation on provided data, but validates
|
||||
// that the data is not encrypted data during TransformFromStorage
|
||||
type identityTransformer struct{}
|
||||
|
||||
// NewEncryptCheckTransformer returns an identityTransformer which returns an error
|
||||
// on attempts to read encrypted data
|
||||
func NewEncryptCheckTransformer() value.Transformer {
|
||||
return identityTransformer{}
|
||||
}
|
||||
|
||||
// TransformFromStorage returns the input bytes if the data is not encrypted
|
||||
func (identityTransformer) TransformFromStorage(b []byte, context value.Context) ([]byte, bool, error) {
|
||||
// EncryptIdentityTransformer has to return an error if the data is encoded using another transformer.
|
||||
// JSON data starts with '{'. Protobuf data has a prefix 'k8s[\x00-\xFF]'.
|
||||
// Prefix 'k8s:enc:' is reserved for encrypted data on disk.
|
||||
if bytes.HasPrefix(b, []byte("k8s:enc:")) {
|
||||
return []byte{}, false, fmt.Errorf("identity transformer tried to read encrypted data")
|
||||
}
|
||||
return b, false, nil
|
||||
}
|
||||
|
||||
// TransformToStorage implements the Transformer interface for encryptIdentityTransformer
|
||||
func (identityTransformer) TransformToStorage(b []byte, context value.Context) ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
|
@ -126,6 +126,13 @@ func (t *prefixTransformers) TransformFromStorage(data []byte, context Context)
|
|||
for i, transformer := range t.transformers {
|
||||
if bytes.HasPrefix(data, transformer.Prefix) {
|
||||
result, stale, err := transformer.Transformer.TransformFromStorage(data[len(transformer.Prefix):], context)
|
||||
// To migrate away from encryption, user can specify an identity transformer higher up
|
||||
// (in the config file) than the encryption transformer. In that scenario, the identity transformer needs to
|
||||
// identify (during reads from disk) whether the data being read is encrypted or not. If the data is encrypted,
|
||||
// it shall throw an error, but that error should not prevent subsequent transformers from being tried.
|
||||
if len(transformer.Prefix) == 0 && err != nil {
|
||||
continue
|
||||
}
|
||||
return result, stale || i != 0, err
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue