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 }