k8s.io/apiserver@v0.31.1/pkg/storage/value/encrypt/envelope/kmsv2/cache.go (about)

     1  /*
     2  Copyright 2023 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 kmsv2 transforms values for storage at rest using a Envelope v2 provider
    18  package kmsv2
    19  
    20  import (
    21  	"crypto/sha256"
    22  	"hash"
    23  	"sync"
    24  	"time"
    25  	"unsafe"
    26  
    27  	utilcache "k8s.io/apimachinery/pkg/util/cache"
    28  	"k8s.io/apiserver/pkg/storage/value"
    29  	"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/metrics"
    30  	"k8s.io/utils/clock"
    31  )
    32  
    33  // simpleCache stores the decryption subset of value.Transformer (value.Read).
    34  // this statically enforces that transformers placed in the cache are not used for encryption.
    35  // this is relevant in the context of nonce collision since transformers that are created
    36  // from encrypted DEKs retrieved from etcd cannot maintain their nonce counter state.
    37  type simpleCache struct {
    38  	cache *utilcache.Expiring
    39  	ttl   time.Duration
    40  	// hashPool is a per cache pool of hash.Hash (to avoid allocations from building the Hash)
    41  	// SHA-256 is used to prevent collisions
    42  	hashPool        *sync.Pool
    43  	providerName    string
    44  	mu              sync.Mutex                          // guards call to set
    45  	recordCacheSize func(providerName string, size int) // for unit tests
    46  }
    47  
    48  func newSimpleCache(clock clock.Clock, ttl time.Duration, providerName string) *simpleCache {
    49  	cache := utilcache.NewExpiringWithClock(clock)
    50  	cache.AllowExpiredGet = true // for a given key, the value (the decryptTransformer) is always the same
    51  	return &simpleCache{
    52  		cache: cache,
    53  		ttl:   ttl,
    54  		hashPool: &sync.Pool{
    55  			New: func() interface{} {
    56  				return sha256.New()
    57  			},
    58  		},
    59  		providerName:    providerName,
    60  		recordCacheSize: metrics.RecordDekSourceCacheSize,
    61  	}
    62  }
    63  
    64  // given a key, return the transformer, or nil if it does not exist in the cache
    65  func (c *simpleCache) get(key []byte) value.Read {
    66  	record, ok := c.cache.Get(c.keyFunc(key))
    67  	if !ok {
    68  		return nil
    69  	}
    70  	return record.(value.Read)
    71  }
    72  
    73  // set caches the record for the key
    74  func (c *simpleCache) set(key []byte, transformer value.Read) {
    75  	c.mu.Lock()
    76  	defer c.mu.Unlock()
    77  	if len(key) == 0 {
    78  		panic("key must not be empty")
    79  	}
    80  	if transformer == nil {
    81  		panic("transformer must not be nil")
    82  	}
    83  	c.cache.Set(c.keyFunc(key), transformer, c.ttl)
    84  	// Add metrics for cache size
    85  	c.recordCacheSize(c.providerName, c.cache.Len())
    86  }
    87  
    88  // keyFunc generates a string key by hashing the inputs.
    89  // This lowers the memory requirement of the cache.
    90  func (c *simpleCache) keyFunc(s []byte) string {
    91  	h := c.hashPool.Get().(hash.Hash)
    92  	h.Reset()
    93  
    94  	if _, err := h.Write(s); err != nil {
    95  		panic(err) // Write() on hash never fails
    96  	}
    97  	key := toString(h.Sum(nil)) // skip base64 encoding to save an allocation
    98  	c.hashPool.Put(h)
    99  
   100  	return key
   101  }
   102  
   103  // toString performs unholy acts to avoid allocations
   104  func toString(b []byte) string {
   105  	// unsafe.SliceData relies on cap whereas we want to rely on len
   106  	if len(b) == 0 {
   107  		return ""
   108  	}
   109  	// Copied from go 1.20.1 strings.Builder.String
   110  	// https://github.com/golang/go/blob/202a1a57064127c3f19d96df57b9f9586145e21c/src/strings/builder.go#L48
   111  	return unsafe.String(unsafe.SliceData(b), len(b))
   112  }