k8s.io/apiserver@v0.31.1/pkg/storage/value/transformer.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes 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 value contains methods for assisting with transformation of values in storage.
    18  package value
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"fmt"
    24  	"time"
    25  
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"k8s.io/apimachinery/pkg/util/errors"
    28  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    29  	"k8s.io/klog/v2"
    30  )
    31  
    32  func init() {
    33  	RegisterMetrics()
    34  }
    35  
    36  // Context is additional information that a storage transformation may need to verify the data at rest.
    37  type Context interface {
    38  	// AuthenticatedData should return an array of bytes that describes the current value. If the value changes,
    39  	// the transformer may report the value as unreadable or tampered. This may be nil if no such description exists
    40  	// or is needed. For additional verification, set this to data that strongly identifies the value, such as
    41  	// the key and creation version of the stored data.
    42  	AuthenticatedData() []byte
    43  }
    44  
    45  type Read interface {
    46  	// TransformFromStorage may transform the provided data from its underlying storage representation or return an error.
    47  	// Stale is true if the object on disk is stale and a write to etcd should be issued, even if the contents of the object
    48  	// have not changed.
    49  	TransformFromStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, stale bool, err error)
    50  }
    51  
    52  type Write interface {
    53  	// TransformToStorage may transform the provided data into the appropriate form in storage or return an error.
    54  	TransformToStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, err error)
    55  }
    56  
    57  // Transformer allows a value to be transformed before being read from or written to the underlying store. The methods
    58  // must be able to undo the transformation caused by the other.
    59  type Transformer interface {
    60  	Read
    61  	Write
    62  }
    63  
    64  // ResourceTransformers returns a transformer for the provided resource.
    65  type ResourceTransformers interface {
    66  	TransformerForResource(resource schema.GroupResource) Transformer
    67  }
    68  
    69  // DefaultContext is a simple implementation of Context for a slice of bytes.
    70  type DefaultContext []byte
    71  
    72  // AuthenticatedData returns itself.
    73  func (c DefaultContext) AuthenticatedData() []byte { return c }
    74  
    75  // PrefixTransformer holds a transformer interface and the prefix that the transformation is located under.
    76  type PrefixTransformer struct {
    77  	Prefix      []byte
    78  	Transformer Transformer
    79  }
    80  
    81  type prefixTransformers struct {
    82  	transformers []PrefixTransformer
    83  	err          error
    84  }
    85  
    86  var _ Transformer = &prefixTransformers{}
    87  
    88  // NewPrefixTransformers supports the Transformer interface by checking the incoming data against the provided
    89  // prefixes in order. The first matching prefix will be used to transform the value (the prefix is stripped
    90  // before the Transformer interface is invoked). The first provided transformer will be used when writing to
    91  // the store.
    92  func NewPrefixTransformers(err error, transformers ...PrefixTransformer) Transformer {
    93  	if err == nil {
    94  		err = fmt.Errorf("the provided value does not match any of the supported transformers")
    95  	}
    96  	return &prefixTransformers{
    97  		transformers: transformers,
    98  		err:          err,
    99  	}
   100  }
   101  
   102  // TransformFromStorage finds the first transformer with a prefix matching the provided data and returns
   103  // the result of transforming the value. It will always mark any transformation as stale that is not using
   104  // the first transformer.
   105  func (t *prefixTransformers) TransformFromStorage(ctx context.Context, data []byte, dataCtx Context) ([]byte, bool, error) {
   106  	start := time.Now()
   107  	var errs []error
   108  	for i, transformer := range t.transformers {
   109  		if bytes.HasPrefix(data, transformer.Prefix) {
   110  			result, stale, err := transformer.Transformer.TransformFromStorage(ctx, data[len(transformer.Prefix):], dataCtx)
   111  			// To migrate away from encryption, user can specify an identity transformer higher up
   112  			// (in the config file) than the encryption transformer. In that scenario, the identity transformer needs to
   113  			// identify (during reads from disk) whether the data being read is encrypted or not. If the data is encrypted,
   114  			// it shall throw an error, but that error should not prevent the next subsequent transformer from being tried.
   115  			if len(transformer.Prefix) == 0 && err != nil {
   116  				continue
   117  			}
   118  			if len(transformer.Prefix) == 0 {
   119  				RecordTransformation("from_storage", "identity", time.Since(start), err)
   120  			} else {
   121  				RecordTransformation("from_storage", string(transformer.Prefix), time.Since(start), err)
   122  			}
   123  
   124  			// It is valid to have overlapping prefixes when the same encryption provider
   125  			// is specified multiple times but with different keys (the first provider is
   126  			// being rotated to and some later provider is being rotated away from).
   127  			//
   128  			// Example:
   129  			//
   130  			//  {
   131  			//    "aescbc": {
   132  			//      "keys": [
   133  			//        {
   134  			//          "name": "2",
   135  			//          "secret": "some key 2"
   136  			//        }
   137  			//      ]
   138  			//    }
   139  			//  },
   140  			//  {
   141  			//    "aescbc": {
   142  			//      "keys": [
   143  			//        {
   144  			//          "name": "1",
   145  			//          "secret": "some key 1"
   146  			//        }
   147  			//      ]
   148  			//    }
   149  			//  },
   150  			//
   151  			// The transformers for both aescbc configs share the prefix k8s:enc:aescbc:v1:
   152  			// but a failure in the first one should not prevent a later match from being attempted.
   153  			// Thus we never short-circuit on a prefix match that results in an error.
   154  			if err != nil {
   155  				errs = append(errs, err)
   156  				continue
   157  			}
   158  
   159  			return result, stale || i != 0, err
   160  		}
   161  	}
   162  	if err := errors.Reduce(errors.NewAggregate(errs)); err != nil {
   163  		logTransformErr(ctx, err, "failed to decrypt data")
   164  		return nil, false, err
   165  	}
   166  	RecordTransformation("from_storage", "unknown", time.Since(start), t.err)
   167  	return nil, false, t.err
   168  }
   169  
   170  // TransformToStorage uses the first transformer and adds its prefix to the data.
   171  func (t *prefixTransformers) TransformToStorage(ctx context.Context, data []byte, dataCtx Context) ([]byte, error) {
   172  	start := time.Now()
   173  	transformer := t.transformers[0]
   174  	result, err := transformer.Transformer.TransformToStorage(ctx, data, dataCtx)
   175  	RecordTransformation("to_storage", string(transformer.Prefix), time.Since(start), err)
   176  	if err != nil {
   177  		logTransformErr(ctx, err, "failed to encrypt data")
   178  		return nil, err
   179  	}
   180  	prefixedData := make([]byte, len(transformer.Prefix), len(result)+len(transformer.Prefix))
   181  	copy(prefixedData, transformer.Prefix)
   182  	prefixedData = append(prefixedData, result...)
   183  	return prefixedData, nil
   184  }
   185  
   186  func logTransformErr(ctx context.Context, err error, message string) {
   187  	requestInfo := getRequestInfoFromContext(ctx)
   188  	if klogLevel6 := klog.V(6); klogLevel6.Enabled() {
   189  		klogLevel6.InfoSDepth(
   190  			1,
   191  			message,
   192  			"err", err,
   193  			"group", requestInfo.APIGroup,
   194  			"version", requestInfo.APIVersion,
   195  			"resource", requestInfo.Resource,
   196  			"subresource", requestInfo.Subresource,
   197  			"verb", requestInfo.Verb,
   198  			"namespace", requestInfo.Namespace,
   199  			"name", requestInfo.Name,
   200  		)
   201  
   202  		return
   203  	}
   204  
   205  	klog.ErrorSDepth(1, err, message)
   206  }
   207  
   208  func getRequestInfoFromContext(ctx context.Context) *genericapirequest.RequestInfo {
   209  	if reqInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
   210  		return reqInfo
   211  	}
   212  	return &genericapirequest.RequestInfo{}
   213  }