feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
182
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes.go
generated
vendored
182
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes.go
generated
vendored
@@ -23,14 +23,129 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// gcm implements AEAD encryption of the provided values given a cipher.Block algorithm.
|
||||
// commonSize is the length of various security sensitive byte slices such as encryption keys.
|
||||
// Do not change this value. It would be a backward incompatible change.
|
||||
const commonSize = 32
|
||||
|
||||
const keySizeCounterNonceGCM = commonSize
|
||||
|
||||
// NewGCMTransformerWithUniqueKeyUnsafe is the same as NewGCMTransformer but is unsafe for general
|
||||
// use because it makes assumptions about the key underlying the block cipher. Specifically,
|
||||
// it uses a 96-bit nonce where the first 32 bits are random data and the remaining 64 bits are
|
||||
// a monotonically incrementing atomic counter. This means that the key must be randomly generated
|
||||
// on process startup and must never be used for encryption outside the lifetime of the process.
|
||||
// Unlike NewGCMTransformer, this function is immune to the birthday attack and thus the key can
|
||||
// be used for 2^64-1 writes without rotation. Furthermore, cryptographic wear out of AES-GCM with
|
||||
// a sequential nonce occurs after 2^64 encryptions, which is not a concern for our use cases.
|
||||
// Even if that occurs, the nonce counter would overflow and crash the process. We have no concerns
|
||||
// around plaintext length because all stored items are small (less than 2 MB). To prevent the
|
||||
// chance of the block cipher being accidentally re-used, it is not taken in as input. Instead,
|
||||
// a new random key is generated and returned on every invocation of this function. This key is
|
||||
// used as the input to the block cipher. If the key is stored and retrieved at a later point,
|
||||
// it can be passed to NewGCMTransformer(aes.NewCipher(key)) to construct a transformer capable
|
||||
// of decrypting values encrypted by this transformer (that transformer must not be used for encryption).
|
||||
func NewGCMTransformerWithUniqueKeyUnsafe() (value.Transformer, []byte, error) {
|
||||
key, err := GenerateKey(keySizeCounterNonceGCM)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
nonceGen := &nonceGenerator{
|
||||
// we start the nonce counter at one billion so that we are
|
||||
// guaranteed to detect rollover across different go routines
|
||||
zero: 1_000_000_000,
|
||||
fatal: die,
|
||||
}
|
||||
nonceGen.nonce.Add(nonceGen.zero)
|
||||
|
||||
transformer, err := newGCMTransformerWithUniqueKeyUnsafe(block, nonceGen)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return transformer, key, nil
|
||||
}
|
||||
|
||||
func newGCMTransformerWithUniqueKeyUnsafe(block cipher.Block, nonceGen *nonceGenerator) (value.Transformer, error) {
|
||||
aead, err := newGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonceFunc := func(b []byte) error {
|
||||
// we only need 8 bytes to store our 64 bit incrementing nonce
|
||||
// instead of leaving the unused bytes as zeros, set those to random bits
|
||||
// this mostly protects us from weird edge cases like a VM restore that rewinds our atomic counter
|
||||
randNonceSize := len(b) - 8
|
||||
|
||||
if err := randomNonce(b[:randNonceSize]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonceGen.next(b[randNonceSize:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return &gcm{aead: aead, nonceFunc: nonceFunc}, nil
|
||||
}
|
||||
|
||||
func randomNonce(b []byte) error {
|
||||
_, err := rand.Read(b)
|
||||
return err
|
||||
}
|
||||
|
||||
type nonceGenerator struct {
|
||||
// even at one million encryptions per second, this counter is enough for half a million years
|
||||
// using this struct avoids alignment bugs: https://pkg.go.dev/sync/atomic#pkg-note-BUG
|
||||
nonce atomic.Uint64
|
||||
zero uint64
|
||||
fatal func(msg string)
|
||||
}
|
||||
|
||||
func (n *nonceGenerator) next(b []byte) {
|
||||
incrementingNonce := n.nonce.Add(1)
|
||||
if incrementingNonce <= n.zero {
|
||||
// this should never happen, and is unrecoverable if it does
|
||||
n.fatal("aes-gcm detected nonce overflow - cryptographic wear out has occurred")
|
||||
}
|
||||
binary.LittleEndian.PutUint64(b, incrementingNonce)
|
||||
}
|
||||
|
||||
func die(msg string) {
|
||||
// nolint:logcheck // we want the stack traces, log flushing, and process exiting logic from FatalDepth
|
||||
klog.FatalDepth(1, msg)
|
||||
}
|
||||
|
||||
// GenerateKey generates a random key using system randomness.
|
||||
func GenerateKey(length int) (key []byte, err error) {
|
||||
defer func(start time.Time) {
|
||||
value.RecordDataKeyGeneration(start, err)
|
||||
}(time.Now())
|
||||
key = make([]byte, length)
|
||||
if _, err = rand.Read(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// NewGCMTransformer takes the given block cipher and performs encryption and decryption on the given data.
|
||||
// It implements AEAD encryption of the provided values given a cipher.Block algorithm.
|
||||
// The authenticated data provided as part of the value.Context method must match when the same
|
||||
// value is set to and loaded from storage. In order to ensure that values cannot be copied by
|
||||
// an attacker from a location under their control, use characteristics of the storage location
|
||||
@@ -43,44 +158,49 @@ import (
|
||||
// therefore transformers using this implementation *must* ensure they allow for frequent key
|
||||
// rotation. Future work should include investigation of AES-GCM-SIV as an alternative to
|
||||
// random nonces.
|
||||
type gcm struct {
|
||||
block cipher.Block
|
||||
func NewGCMTransformer(block cipher.Block) (value.Transformer, error) {
|
||||
aead, err := newGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gcm{aead: aead, nonceFunc: randomNonce}, nil
|
||||
}
|
||||
|
||||
// NewGCMTransformer takes the given block cipher and performs encryption and decryption on the given
|
||||
// data.
|
||||
func NewGCMTransformer(block cipher.Block) value.Transformer {
|
||||
return &gcm{block: block}
|
||||
func newGCM(block cipher.Block) (cipher.AEAD, error) {
|
||||
aead, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nonceSize := aead.NonceSize(); nonceSize != 12 { // all data in etcd will be broken if this ever changes
|
||||
return nil, fmt.Errorf("crypto/cipher.NewGCM returned unexpected nonce size: %d", nonceSize)
|
||||
}
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
type gcm struct {
|
||||
aead cipher.AEAD
|
||||
nonceFunc func([]byte) error
|
||||
}
|
||||
|
||||
func (t *gcm) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
|
||||
aead, err := cipher.NewGCM(t.block)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
nonceSize := aead.NonceSize()
|
||||
nonceSize := t.aead.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
|
||||
return nil, false, errors.New("the stored data was shorter than the required size")
|
||||
}
|
||||
result, err := aead.Open(nil, data[:nonceSize], data[nonceSize:], dataCtx.AuthenticatedData())
|
||||
result, err := t.aead.Open(nil, data[:nonceSize], data[nonceSize:], dataCtx.AuthenticatedData())
|
||||
return result, false, err
|
||||
}
|
||||
|
||||
func (t *gcm) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) {
|
||||
aead, err := cipher.NewGCM(t.block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
nonceSize := t.aead.NonceSize()
|
||||
result := make([]byte, nonceSize+t.aead.Overhead()+len(data))
|
||||
|
||||
if err := t.nonceFunc(result[:nonceSize]); err != nil {
|
||||
return nil, fmt.Errorf("failed to write nonce for AES-GCM: %w", err)
|
||||
}
|
||||
nonceSize := aead.NonceSize()
|
||||
result := make([]byte, nonceSize+aead.Overhead()+len(data))
|
||||
n, err := rand.Read(result[:nonceSize])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != nonceSize {
|
||||
return nil, fmt.Errorf("unable to read sufficient random bytes")
|
||||
}
|
||||
cipherText := aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, dataCtx.AuthenticatedData())
|
||||
|
||||
cipherText := t.aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, dataCtx.AuthenticatedData())
|
||||
return result[:nonceSize+len(cipherText)], nil
|
||||
}
|
||||
|
||||
@@ -96,7 +216,7 @@ func NewCBCTransformer(block cipher.Block) value.Transformer {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidBlockSize = fmt.Errorf("the stored data is not a multiple of the block size")
|
||||
errInvalidBlockSize = errors.New("the stored data is not a multiple of the block size")
|
||||
errInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)")
|
||||
errInvalidPKCS7Padding = errors.New("invalid padding on input")
|
||||
)
|
||||
@@ -104,13 +224,13 @@ var (
|
||||
func (t *cbc) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
|
||||
blockSize := aes.BlockSize
|
||||
if len(data) < blockSize {
|
||||
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
|
||||
return nil, false, errors.New("the stored data was shorter than the required size")
|
||||
}
|
||||
iv := data[:blockSize]
|
||||
data = data[blockSize:]
|
||||
|
||||
if len(data)%blockSize != 0 {
|
||||
return nil, false, ErrInvalidBlockSize
|
||||
return nil, false, errInvalidBlockSize
|
||||
}
|
||||
|
||||
result := make([]byte, len(data))
|
||||
@@ -140,7 +260,7 @@ func (t *cbc) TransformToStorage(ctx context.Context, data []byte, dataCtx value
|
||||
result := make([]byte, blockSize+len(data)+paddingSize)
|
||||
iv := result[:blockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, fmt.Errorf("unable to read sufficient random bytes")
|
||||
return nil, errors.New("unable to read sufficient random bytes")
|
||||
}
|
||||
copy(result[blockSize:], data)
|
||||
|
||||
|
||||
186
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes_extended_nonce.go
generated
vendored
Normal file
186
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes_extended_nonce.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Copyright 2023 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 aes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
// cacheTTL is the TTL of KDF cache entries. We assume that the value.Context.AuthenticatedData
|
||||
// for every call is the etcd storage path of the associated resource, and use that as the primary
|
||||
// cache key (with a secondary check that confirms that the info matches). Thus if a client
|
||||
// is constantly creating resources with new names (and thus new paths), they will keep adding new
|
||||
// entries to the cache for up to this TTL before the GC logic starts deleting old entries. Each
|
||||
// entry is ~300 bytes in size, so even a malicious client will be bounded in the overall memory
|
||||
// it can consume.
|
||||
cacheTTL = 10 * time.Minute
|
||||
|
||||
derivedKeySizeExtendedNonceGCM = commonSize
|
||||
infoSizeExtendedNonceGCM
|
||||
MinSeedSizeExtendedNonceGCM
|
||||
)
|
||||
|
||||
// NewHKDFExtendedNonceGCMTransformer is the same as NewGCMTransformer but trades storage,
|
||||
// memory and CPU to work around the limitations of AES-GCM's 12 byte nonce size. The input seed
|
||||
// is assumed to be a cryptographically strong slice of MinSeedSizeExtendedNonceGCM+ random bytes.
|
||||
// Unlike NewGCMTransformer, this function is immune to the birthday attack because a new key is generated
|
||||
// per encryption via a key derivation function: KDF(seed, random_bytes) -> key. The derived key is
|
||||
// only used once as an AES-GCM key with a random 12 byte nonce. This avoids any concerns around
|
||||
// cryptographic wear out (by either number of encryptions or the amount of data being encrypted).
|
||||
// Speaking on the cryptographic safety, the limit on the number of operations that can be preformed
|
||||
// with a single seed with derived keys and randomly generated nonces is not practically reachable.
|
||||
// Thus, the scheme does not impose any specific requirements on the seed rotation schedule.
|
||||
// Reusing the same seed is safe to do over time and across process restarts. Whenever a new
|
||||
// seed is needed, the caller should generate it via GenerateKey(MinSeedSizeExtendedNonceGCM).
|
||||
// In regard to KMSv2, organization standards or compliance policies around rotation may require
|
||||
// that the seed be rotated at some interval. This can be implemented externally by rotating
|
||||
// the key encryption key via a key ID change.
|
||||
func NewHKDFExtendedNonceGCMTransformer(seed []byte) (value.Transformer, error) {
|
||||
if seedLen := len(seed); seedLen < MinSeedSizeExtendedNonceGCM {
|
||||
return nil, fmt.Errorf("invalid seed length %d used for key generation", seedLen)
|
||||
}
|
||||
return &extendedNonceGCM{
|
||||
seed: seed,
|
||||
cache: newSimpleCache(clock.RealClock{}, cacheTTL),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type extendedNonceGCM struct {
|
||||
seed []byte
|
||||
cache *simpleCache
|
||||
}
|
||||
|
||||
func (e *extendedNonceGCM) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
|
||||
if len(data) < infoSizeExtendedNonceGCM {
|
||||
return nil, false, errors.New("the stored data was shorter than the required size")
|
||||
}
|
||||
|
||||
info := data[:infoSizeExtendedNonceGCM]
|
||||
|
||||
transformer, err := e.derivedKeyTransformer(info, dataCtx, false)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to derive read key from KDF: %w", err)
|
||||
}
|
||||
|
||||
return transformer.TransformFromStorage(ctx, data, dataCtx)
|
||||
}
|
||||
|
||||
func (e *extendedNonceGCM) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) {
|
||||
info := make([]byte, infoSizeExtendedNonceGCM)
|
||||
if err := randomNonce(info); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate info for KDF: %w", err)
|
||||
}
|
||||
|
||||
transformer, err := e.derivedKeyTransformer(info, dataCtx, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive write key from KDF: %w", err)
|
||||
}
|
||||
|
||||
return transformer.TransformToStorage(ctx, data, dataCtx)
|
||||
}
|
||||
|
||||
func (e *extendedNonceGCM) derivedKeyTransformer(info []byte, dataCtx value.Context, write bool) (value.Transformer, error) {
|
||||
if !write { // no need to check cache on write since we always generate a new transformer
|
||||
if transformer := e.cache.get(info, dataCtx); transformer != nil {
|
||||
return transformer, nil
|
||||
}
|
||||
|
||||
// on read, this is a subslice of a much larger slice and we do not want to hold onto that larger slice
|
||||
info = bytes.Clone(info)
|
||||
}
|
||||
|
||||
key, err := e.sha256KDFExpandOnly(info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to KDF expand seed with info: %w", err)
|
||||
}
|
||||
|
||||
transformer, err := newGCMTransformerWithInfo(key, info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build transformer with KDF derived key: %w", err)
|
||||
}
|
||||
|
||||
e.cache.set(dataCtx, transformer)
|
||||
|
||||
return transformer, nil
|
||||
}
|
||||
|
||||
func (e *extendedNonceGCM) sha256KDFExpandOnly(info []byte) ([]byte, error) {
|
||||
kdf := hkdf.Expand(sha256.New, e.seed, info)
|
||||
|
||||
derivedKey := make([]byte, derivedKeySizeExtendedNonceGCM)
|
||||
if _, err := io.ReadFull(kdf, derivedKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to read a derived key from KDF: %w", err)
|
||||
}
|
||||
|
||||
return derivedKey, nil
|
||||
}
|
||||
|
||||
func newGCMTransformerWithInfo(key, info []byte) (*transformerWithInfo, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transformer, err := NewGCMTransformer(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &transformerWithInfo{transformer: transformer, info: info}, nil
|
||||
}
|
||||
|
||||
type transformerWithInfo struct {
|
||||
transformer value.Transformer
|
||||
// info are extra opaque bytes prepended to the writes from transformer and stripped from reads.
|
||||
// currently info is used to generate a key via KDF(seed, info) -> key
|
||||
// and transformer is the output of NewGCMTransformer(aes.NewCipher(key))
|
||||
info []byte
|
||||
}
|
||||
|
||||
func (t *transformerWithInfo) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
|
||||
if !bytes.HasPrefix(data, t.info) {
|
||||
return nil, false, errors.New("the stored data is missing the required info prefix")
|
||||
}
|
||||
|
||||
return t.transformer.TransformFromStorage(ctx, data[len(t.info):], dataCtx)
|
||||
}
|
||||
|
||||
func (t *transformerWithInfo) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) {
|
||||
out, err := t.transformer.TransformToStorage(ctx, data, dataCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outWithInfo := make([]byte, 0, len(out)+len(t.info))
|
||||
outWithInfo = append(outWithInfo, t.info...)
|
||||
outWithInfo = append(outWithInfo, out...)
|
||||
|
||||
return outWithInfo, nil
|
||||
}
|
||||
91
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes/cache.go
generated
vendored
Normal file
91
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes/cache.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2023 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 aes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
type simpleCache struct {
|
||||
cache *utilcache.Expiring
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func newSimpleCache(clock clock.Clock, ttl time.Duration) *simpleCache {
|
||||
cache := utilcache.NewExpiringWithClock(clock)
|
||||
// "Stale" entries are always valid for us because the TTL is just used to prevent
|
||||
// unbounded growth on the cache - for a given info the transformer is always the same.
|
||||
// The key always corresponds to the exact same value, with the caveat that
|
||||
// since we use the value.Context.AuthenticatedData to overwrite old keys,
|
||||
// we always have to check that the info matches (to validate the transformer is correct).
|
||||
cache.AllowExpiredGet = true
|
||||
return &simpleCache{
|
||||
cache: cache,
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
// given a key, return the transformer, or nil if it does not exist in the cache
|
||||
func (c *simpleCache) get(info []byte, dataCtx value.Context) *transformerWithInfo {
|
||||
val, ok := c.cache.Get(keyFunc(dataCtx))
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
transformer := val.(*transformerWithInfo)
|
||||
|
||||
if !bytes.Equal(transformer.info, info) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return transformer
|
||||
}
|
||||
|
||||
// set caches the record for the key
|
||||
func (c *simpleCache) set(dataCtx value.Context, transformer *transformerWithInfo) {
|
||||
if dataCtx == nil || len(dataCtx.AuthenticatedData()) == 0 {
|
||||
panic("authenticated data must not be empty")
|
||||
}
|
||||
if transformer == nil {
|
||||
panic("transformer must not be nil")
|
||||
}
|
||||
if len(transformer.info) == 0 {
|
||||
panic("info must not be empty")
|
||||
}
|
||||
c.cache.Set(keyFunc(dataCtx), transformer, c.ttl)
|
||||
}
|
||||
|
||||
func keyFunc(dataCtx value.Context) string {
|
||||
return toString(dataCtx.AuthenticatedData())
|
||||
}
|
||||
|
||||
// toString performs unholy acts to avoid allocations
|
||||
func toString(b []byte) string {
|
||||
// unsafe.SliceData relies on cap whereas we want to rely on len
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
// Copied from go 1.20.1 strings.Builder.String
|
||||
// https://github.com/golang/go/blob/202a1a57064127c3f19d96df57b9f9586145e21c/src/strings/builder.go#L48
|
||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||
}
|
||||
10
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go
generated
vendored
@@ -53,7 +53,7 @@ type envelopeTransformer struct {
|
||||
transformers *lru.Cache
|
||||
|
||||
// baseTransformerFunc creates a new transformer for encrypting the data with the DEK.
|
||||
baseTransformerFunc func(cipher.Block) value.Transformer
|
||||
baseTransformerFunc func(cipher.Block) (value.Transformer, error)
|
||||
|
||||
cacheSize int
|
||||
cacheEnabled bool
|
||||
@@ -63,7 +63,7 @@ type envelopeTransformer struct {
|
||||
// It uses envelopeService to encrypt and decrypt DEKs. Respective DEKs (in encrypted form) are prepended to
|
||||
// the data items they encrypt. A cache (of size cacheSize) is maintained to store the most recently
|
||||
// used decrypted DEKs in memory.
|
||||
func NewEnvelopeTransformer(envelopeService Service, cacheSize int, baseTransformerFunc func(cipher.Block) value.Transformer) value.Transformer {
|
||||
func NewEnvelopeTransformer(envelopeService Service, cacheSize int, baseTransformerFunc func(cipher.Block) (value.Transformer, error)) value.Transformer {
|
||||
var (
|
||||
cache *lru.Cache
|
||||
)
|
||||
@@ -161,7 +161,11 @@ func (t *envelopeTransformer) addTransformer(encKey []byte, key []byte) (value.T
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transformer := t.baseTransformerFunc(block)
|
||||
transformer, err := t.baseTransformerFunc(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use base64 of encKey as the key into the cache because hashicorp/golang-lru
|
||||
// cannot hash []uint8.
|
||||
if t.cacheEnabled {
|
||||
|
||||
10
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/grpc_service.go
generated
vendored
@@ -28,9 +28,9 @@ import (
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/util"
|
||||
"k8s.io/klog/v2"
|
||||
kmsapi "k8s.io/kms/apis/v1beta1"
|
||||
"k8s.io/kms/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,7 +53,7 @@ type gRPCService struct {
|
||||
|
||||
// NewGRPCService returns an envelope.Service which use gRPC to communicate the remote KMS provider.
|
||||
func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Duration) (Service, error) {
|
||||
klog.V(4).Infof("Configure KMS provider with endpoint: %s", endpoint)
|
||||
klog.V(4).InfoS("Configure KMS provider", "endpoint", endpoint)
|
||||
|
||||
addr, err := util.ParseEndpoint(endpoint)
|
||||
if err != nil {
|
||||
@@ -72,9 +72,9 @@ func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Durat
|
||||
// addr - comes from the closure
|
||||
c, err := net.DialUnix(unixProtocol, nil, &net.UnixAddr{Name: addr})
|
||||
if err != nil {
|
||||
klog.Errorf("failed to create connection to unix socket: %s, error: %v", addr, err)
|
||||
klog.ErrorS(err, "failed to create connection to unix socket", "addr", addr)
|
||||
} else {
|
||||
klog.V(4).Infof("Successfully dialed Unix socket %v", addr)
|
||||
klog.V(4).InfoS("Successfully dialed Unix socket", "addr", addr)
|
||||
}
|
||||
return c, err
|
||||
}))
|
||||
@@ -113,7 +113,7 @@ func (g *gRPCService) checkAPIVersion(ctx context.Context) error {
|
||||
}
|
||||
g.versionChecked = true
|
||||
|
||||
klog.V(4).Infof("Version of KMS provider is %s", response.Version)
|
||||
klog.V(4).InfoS("KMS provider api version verified", "version", response.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
112
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/cache.go
generated
vendored
Normal file
112
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/cache.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright 2023 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 kmsv2 transforms values for storage at rest using a Envelope v2 provider
|
||||
package kmsv2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/metrics"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// simpleCache stores the decryption subset of value.Transformer (value.Read).
|
||||
// this statically enforces that transformers placed in the cache are not used for encryption.
|
||||
// this is relevant in the context of nonce collision since transformers that are created
|
||||
// from encrypted DEKs retrieved from etcd cannot maintain their nonce counter state.
|
||||
type simpleCache struct {
|
||||
cache *utilcache.Expiring
|
||||
ttl time.Duration
|
||||
// hashPool is a per cache pool of hash.Hash (to avoid allocations from building the Hash)
|
||||
// SHA-256 is used to prevent collisions
|
||||
hashPool *sync.Pool
|
||||
providerName string
|
||||
mu sync.Mutex // guards call to set
|
||||
recordCacheSize func(providerName string, size int) // for unit tests
|
||||
}
|
||||
|
||||
func newSimpleCache(clock clock.Clock, ttl time.Duration, providerName string) *simpleCache {
|
||||
cache := utilcache.NewExpiringWithClock(clock)
|
||||
cache.AllowExpiredGet = true // for a given key, the value (the decryptTransformer) is always the same
|
||||
return &simpleCache{
|
||||
cache: cache,
|
||||
ttl: ttl,
|
||||
hashPool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return sha256.New()
|
||||
},
|
||||
},
|
||||
providerName: providerName,
|
||||
recordCacheSize: metrics.RecordDekSourceCacheSize,
|
||||
}
|
||||
}
|
||||
|
||||
// given a key, return the transformer, or nil if it does not exist in the cache
|
||||
func (c *simpleCache) get(key []byte) value.Read {
|
||||
record, ok := c.cache.Get(c.keyFunc(key))
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return record.(value.Read)
|
||||
}
|
||||
|
||||
// set caches the record for the key
|
||||
func (c *simpleCache) set(key []byte, transformer value.Read) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if len(key) == 0 {
|
||||
panic("key must not be empty")
|
||||
}
|
||||
if transformer == nil {
|
||||
panic("transformer must not be nil")
|
||||
}
|
||||
c.cache.Set(c.keyFunc(key), transformer, c.ttl)
|
||||
// Add metrics for cache size
|
||||
c.recordCacheSize(c.providerName, c.cache.Len())
|
||||
}
|
||||
|
||||
// keyFunc generates a string key by hashing the inputs.
|
||||
// This lowers the memory requirement of the cache.
|
||||
func (c *simpleCache) keyFunc(s []byte) string {
|
||||
h := c.hashPool.Get().(hash.Hash)
|
||||
h.Reset()
|
||||
|
||||
if _, err := h.Write(s); err != nil {
|
||||
panic(err) // Write() on hash never fails
|
||||
}
|
||||
key := toString(h.Sum(nil)) // skip base64 encoding to save an allocation
|
||||
c.hashPool.Put(h)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// toString performs unholy acts to avoid allocations
|
||||
func toString(b []byte) string {
|
||||
// unsafe.SliceData relies on cap whereas we want to rely on len
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
// Copied from go 1.20.1 strings.Builder.String
|
||||
// https://github.com/golang/go/blob/202a1a57064127c3f19d96df57b9f9586145e21c/src/strings/builder.go#L48
|
||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||
}
|
||||
507
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go
generated
vendored
507
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/envelope.go
generated
vendored
@@ -21,204 +21,296 @@ import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/storage/value"
|
||||
kmstypes "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2alpha1"
|
||||
aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
|
||||
kmstypes "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2"
|
||||
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/metrics"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/lru"
|
||||
kmsservice "k8s.io/kms/pkg/service"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
func init() {
|
||||
value.RegisterMetrics()
|
||||
metrics.RegisterMetrics()
|
||||
}
|
||||
|
||||
const (
|
||||
// KMSAPIVersion is the version of the KMS API.
|
||||
KMSAPIVersion = "v2alpha1"
|
||||
// KMSAPIVersionv2 is a version of the KMS API.
|
||||
KMSAPIVersionv2 = "v2"
|
||||
// KMSAPIVersionv2beta1 is a version of the KMS API.
|
||||
KMSAPIVersionv2beta1 = "v2beta1"
|
||||
// annotationsMaxSize is the maximum size of the annotations.
|
||||
annotationsMaxSize = 32 * 1024 // 32 kB
|
||||
// keyIDMaxSize is the maximum size of the keyID.
|
||||
keyIDMaxSize = 1 * 1024 // 1 kB
|
||||
// encryptedDEKMaxSize is the maximum size of the encrypted DEK.
|
||||
encryptedDEKMaxSize = 1 * 1024 // 1 kB
|
||||
// KeyIDMaxSize is the maximum size of the keyID.
|
||||
KeyIDMaxSize = 1 * 1024 // 1 kB
|
||||
// encryptedDEKSourceMaxSize is the maximum size of the encrypted DEK source.
|
||||
encryptedDEKSourceMaxSize = 1 * 1024 // 1 kB
|
||||
// cacheTTL is the default time-to-live for the cache entry.
|
||||
// this allows the cache to grow to an infinite size for up to a day.
|
||||
// there is unlikely to be any meaningful memory impact on the server
|
||||
// because the cache will likely never have more than a few thousand entries.
|
||||
// each entry can be large due to an internal cache that maps the DEK seed to individual
|
||||
// DEK entries, but that cache has an aggressive TTL to keep the size under control.
|
||||
// with DEK/seed reuse and no storage migration, the number of entries in this cache
|
||||
// would be approximated by unique key IDs used by the KMS plugin
|
||||
// combined with the number of server restarts. If storage migration
|
||||
// is performed after key ID changes, and the number of restarts
|
||||
// is limited, this cache size may be as small as the number of API
|
||||
// servers in use (once old entries expire out from the TTL).
|
||||
cacheTTL = 24 * time.Hour
|
||||
// key ID related error codes for metrics
|
||||
errKeyIDOKCode ErrCodeKeyID = "ok"
|
||||
errKeyIDEmptyCode ErrCodeKeyID = "empty"
|
||||
errKeyIDTooLongCode ErrCodeKeyID = "too_long"
|
||||
)
|
||||
|
||||
// Service allows encrypting and decrypting data using an external Key Management Service.
|
||||
type Service interface {
|
||||
// Decrypt a given bytearray to obtain the original data as bytes.
|
||||
Decrypt(ctx context.Context, uid string, req *DecryptRequest) ([]byte, error)
|
||||
// Encrypt bytes to a ciphertext.
|
||||
Encrypt(ctx context.Context, uid string, data []byte) (*EncryptResponse, error)
|
||||
// Status returns the status of the KMS.
|
||||
Status(ctx context.Context) (*StatusResponse, error)
|
||||
// NowFunc is exported so tests can override it.
|
||||
var NowFunc = time.Now
|
||||
|
||||
type StateFunc func() (State, error)
|
||||
type ErrCodeKeyID string
|
||||
|
||||
type State struct {
|
||||
Transformer value.Transformer
|
||||
|
||||
EncryptedObject kmstypes.EncryptedObject
|
||||
|
||||
UID string
|
||||
|
||||
ExpirationTimestamp time.Time
|
||||
|
||||
// CacheKey is the key used to cache the DEK/seed in envelopeTransformer.cache.
|
||||
CacheKey []byte
|
||||
}
|
||||
|
||||
func (s *State) ValidateEncryptCapability() error {
|
||||
if now := NowFunc(); now.After(s.ExpirationTimestamp) {
|
||||
return fmt.Errorf("encryptedDEKSource with keyID hash %q expired at %s (current time is %s)",
|
||||
GetHashIfNotEmpty(s.EncryptedObject.KeyID), s.ExpirationTimestamp.Format(time.RFC3339), now.Format(time.RFC3339))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type envelopeTransformer struct {
|
||||
envelopeService Service
|
||||
envelopeService kmsservice.Service
|
||||
providerName string
|
||||
stateFunc StateFunc
|
||||
|
||||
// transformers is a thread-safe LRU cache which caches decrypted DEKs indexed by their encrypted form.
|
||||
transformers *lru.Cache
|
||||
|
||||
// baseTransformerFunc creates a new transformer for encrypting the data with the DEK.
|
||||
baseTransformerFunc func(cipher.Block) value.Transformer
|
||||
|
||||
cacheSize int
|
||||
cacheEnabled bool
|
||||
}
|
||||
|
||||
// EncryptResponse is the response from the Envelope service when encrypting data.
|
||||
type EncryptResponse struct {
|
||||
Ciphertext []byte
|
||||
KeyID string
|
||||
Annotations map[string][]byte
|
||||
}
|
||||
|
||||
// DecryptRequest is the request to the Envelope service when decrypting data.
|
||||
type DecryptRequest struct {
|
||||
Ciphertext []byte
|
||||
KeyID string
|
||||
Annotations map[string][]byte
|
||||
}
|
||||
|
||||
// StatusResponse is the response from the Envelope service when getting the status of the service.
|
||||
type StatusResponse struct {
|
||||
Version string
|
||||
Healthz string
|
||||
KeyID string
|
||||
// cache is a thread-safe expiring lru cache which caches decrypted DEKs indexed by their encrypted form.
|
||||
cache *simpleCache
|
||||
apiServerID string
|
||||
}
|
||||
|
||||
// NewEnvelopeTransformer returns a transformer which implements a KEK-DEK based envelope encryption scheme.
|
||||
// It uses envelopeService to encrypt and decrypt DEKs. Respective DEKs (in encrypted form) are prepended to
|
||||
// the data items they encrypt. A cache (of size cacheSize) is maintained to store the most recently
|
||||
// used decrypted DEKs in memory.
|
||||
func NewEnvelopeTransformer(envelopeService Service, cacheSize int, baseTransformerFunc func(cipher.Block) value.Transformer) value.Transformer {
|
||||
var cache *lru.Cache
|
||||
|
||||
if cacheSize > 0 {
|
||||
// TODO(aramase): Switch to using expiring cache: kubernetes/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/cache/expiring.go.
|
||||
// It handles scans a lot better, doesn't have to be right sized, and don't have a global lock on reads.
|
||||
cache = lru.New(cacheSize)
|
||||
}
|
||||
// the data items they encrypt.
|
||||
func NewEnvelopeTransformer(envelopeService kmsservice.Service, providerName string, stateFunc StateFunc, apiServerID string) value.Transformer {
|
||||
return newEnvelopeTransformerWithClock(envelopeService, providerName, stateFunc, apiServerID, cacheTTL, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newEnvelopeTransformerWithClock(envelopeService kmsservice.Service, providerName string, stateFunc StateFunc, apiServerID string, cacheTTL time.Duration, clock clock.Clock) value.Transformer {
|
||||
return &envelopeTransformer{
|
||||
envelopeService: envelopeService,
|
||||
transformers: cache,
|
||||
baseTransformerFunc: baseTransformerFunc,
|
||||
cacheEnabled: cacheSize > 0,
|
||||
cacheSize: cacheSize,
|
||||
envelopeService: envelopeService,
|
||||
providerName: providerName,
|
||||
stateFunc: stateFunc,
|
||||
cache: newSimpleCache(clock, cacheTTL, providerName),
|
||||
apiServerID: apiServerID,
|
||||
}
|
||||
}
|
||||
|
||||
// TransformFromStorage decrypts data encrypted by this transformer using envelope encryption.
|
||||
func (t *envelopeTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
|
||||
metrics.RecordArrival(metrics.FromStorageLabel, time.Now())
|
||||
ctx, span := tracing.Start(ctx, "TransformFromStorage with envelopeTransformer",
|
||||
attribute.String("transformer.provider.name", t.providerName),
|
||||
// The service.instance_id of the apiserver is already available in the trace
|
||||
/*
|
||||
{
|
||||
"key": "service.instance.id",
|
||||
"type": "string",
|
||||
"value": "apiserver-zsteyir5lyrtdcmqqmd5kzze6m"
|
||||
}
|
||||
*/
|
||||
)
|
||||
defer span.End(500 * time.Millisecond)
|
||||
|
||||
span.AddEvent("About to decode encrypted object")
|
||||
// Deserialize the EncryptedObject from the data.
|
||||
encryptedObject, err := t.doDecode(data)
|
||||
if err != nil {
|
||||
span.AddEvent("Decoding encrypted object failed")
|
||||
span.RecordError(err)
|
||||
return nil, false, err
|
||||
}
|
||||
span.AddEvent("Decoded encrypted object")
|
||||
|
||||
useSeed := encryptedObject.EncryptedDEKSourceType == kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED
|
||||
|
||||
// TODO: consider marking state.EncryptedDEK != encryptedObject.EncryptedDEK as a stale read to support DEK defragmentation
|
||||
// at a minimum we should have a metric that helps the user understand if DEK fragmentation is high
|
||||
state, err := t.stateFunc() // no need to call state.ValidateEncryptCapability on reads
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Look up the decrypted DEK from cache or Envelope.
|
||||
transformer := t.getTransformer(encryptedObject.EncryptedDEK)
|
||||
encryptedObjectCacheKey, err := generateCacheKey(encryptedObject.EncryptedDEKSourceType, encryptedObject.EncryptedDEKSource, encryptedObject.KeyID, encryptedObject.Annotations)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Look up the decrypted DEK from cache first
|
||||
transformer := t.cache.get(encryptedObjectCacheKey)
|
||||
|
||||
// fallback to the envelope service if we do not have the transformer locally
|
||||
if transformer == nil {
|
||||
if t.cacheEnabled {
|
||||
value.RecordCacheMiss()
|
||||
}
|
||||
span.AddEvent("About to decrypt DEK using remote service")
|
||||
value.RecordCacheMiss()
|
||||
|
||||
requestInfo := getRequestInfoFromContext(ctx)
|
||||
uid := string(uuid.NewUUID())
|
||||
klog.V(6).InfoS("Decrypting content using envelope service", "uid", uid, "key", string(dataCtx.AuthenticatedData()))
|
||||
key, err := t.envelopeService.Decrypt(ctx, uid, &DecryptRequest{
|
||||
Ciphertext: encryptedObject.EncryptedDEK,
|
||||
klog.V(6).InfoS("decrypting content using envelope service", "uid", uid, "key", string(dataCtx.AuthenticatedData()),
|
||||
"group", requestInfo.APIGroup, "version", requestInfo.APIVersion, "resource", requestInfo.Resource, "subresource", requestInfo.Subresource,
|
||||
"verb", requestInfo.Verb, "namespace", requestInfo.Namespace, "name", requestInfo.Name)
|
||||
|
||||
key, err := t.envelopeService.Decrypt(ctx, uid, &kmsservice.DecryptRequest{
|
||||
Ciphertext: encryptedObject.EncryptedDEKSource,
|
||||
KeyID: encryptedObject.KeyID,
|
||||
Annotations: encryptedObject.Annotations,
|
||||
})
|
||||
if err != nil {
|
||||
span.AddEvent("DEK decryption failed")
|
||||
span.RecordError(err)
|
||||
return nil, false, fmt.Errorf("failed to decrypt DEK, error: %w", err)
|
||||
}
|
||||
span.AddEvent("DEK decryption succeeded")
|
||||
|
||||
transformer, err = t.addTransformer(encryptedObject.EncryptedDEK, key)
|
||||
transformer, err = t.addTransformerForDecryption(encryptedObjectCacheKey, key, useSeed)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
metrics.RecordKeyID(metrics.FromStorageLabel, t.providerName, encryptedObject.KeyID, t.apiServerID)
|
||||
|
||||
return transformer.TransformFromStorage(ctx, encryptedObject.EncryptedData, dataCtx)
|
||||
span.AddEvent("About to decrypt data using DEK")
|
||||
out, stale, err := transformer.TransformFromStorage(ctx, encryptedObject.EncryptedData, dataCtx)
|
||||
if err != nil {
|
||||
span.AddEvent("Data decryption failed")
|
||||
span.RecordError(err)
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
span.AddEvent("Data decryption succeeded")
|
||||
// data is considered stale if the key ID does not match our current write transformer
|
||||
return out,
|
||||
stale ||
|
||||
encryptedObject.KeyID != state.EncryptedObject.KeyID ||
|
||||
encryptedObject.EncryptedDEKSourceType != state.EncryptedObject.EncryptedDEKSourceType,
|
||||
nil
|
||||
}
|
||||
|
||||
// TransformToStorage encrypts data to be written to disk using envelope encryption.
|
||||
func (t *envelopeTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) {
|
||||
metrics.RecordArrival(metrics.ToStorageLabel, time.Now())
|
||||
newKey, err := generateKey(32)
|
||||
ctx, span := tracing.Start(ctx, "TransformToStorage with envelopeTransformer",
|
||||
attribute.String("transformer.provider.name", t.providerName),
|
||||
// The service.instance_id of the apiserver is already available in the trace
|
||||
/*
|
||||
{
|
||||
"key": "service.instance.id",
|
||||
"type": "string",
|
||||
"value": "apiserver-zsteyir5lyrtdcmqqmd5kzze6m"
|
||||
}
|
||||
*/
|
||||
)
|
||||
defer span.End(500 * time.Millisecond)
|
||||
|
||||
state, err := t.stateFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uid := string(uuid.NewUUID())
|
||||
klog.V(6).InfoS("Encrypting content using envelope service", "uid", uid, "key", string(dataCtx.AuthenticatedData()))
|
||||
resp, err := t.envelopeService.Encrypt(ctx, uid, newKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt DEK, error: %w", err)
|
||||
}
|
||||
|
||||
transformer, err := t.addTransformer(resp.Ciphertext, newKey)
|
||||
if err != nil {
|
||||
if err := state.ValidateEncryptCapability(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := transformer.TransformToStorage(ctx, data, dataCtx)
|
||||
// this prevents a cache miss every time the DEK rotates
|
||||
// this has the side benefit of causing the cache to perform a GC
|
||||
// TODO see if we can do this inside the stateFunc control loop
|
||||
t.cache.set(state.CacheKey, state.Transformer)
|
||||
|
||||
requestInfo := getRequestInfoFromContext(ctx)
|
||||
klog.V(6).InfoS("encrypting content using DEK", "uid", state.UID, "key", string(dataCtx.AuthenticatedData()),
|
||||
"group", requestInfo.APIGroup, "version", requestInfo.APIVersion, "resource", requestInfo.Resource, "subresource", requestInfo.Subresource,
|
||||
"verb", requestInfo.Verb, "namespace", requestInfo.Namespace, "name", requestInfo.Name)
|
||||
|
||||
span.AddEvent("About to encrypt data using DEK")
|
||||
result, err := state.Transformer.TransformToStorage(ctx, data, dataCtx)
|
||||
if err != nil {
|
||||
span.AddEvent("Data encryption failed")
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
span.AddEvent("Data encryption succeeded")
|
||||
|
||||
encObject := &kmstypes.EncryptedObject{
|
||||
KeyID: resp.KeyID,
|
||||
EncryptedDEK: resp.Ciphertext,
|
||||
EncryptedData: result,
|
||||
Annotations: resp.Annotations,
|
||||
}
|
||||
metrics.RecordKeyID(metrics.ToStorageLabel, t.providerName, state.EncryptedObject.KeyID, t.apiServerID)
|
||||
|
||||
encObjectCopy := state.EncryptedObject
|
||||
encObjectCopy.EncryptedData = result
|
||||
|
||||
span.AddEvent("About to encode encrypted object")
|
||||
// Serialize the EncryptedObject to a byte array.
|
||||
return t.doEncode(encObject)
|
||||
out, err := t.doEncode(&encObjectCopy)
|
||||
if err != nil {
|
||||
span.AddEvent("Encoding encrypted object failed")
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
span.AddEvent("Encoded encrypted object")
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// addTransformer inserts a new transformer to the Envelope cache of DEKs for future reads.
|
||||
func (t *envelopeTransformer) addTransformer(encKey []byte, key []byte) (value.Transformer, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
// addTransformerForDecryption inserts a new transformer to the Envelope cache of DEKs for future reads.
|
||||
func (t *envelopeTransformer) addTransformerForDecryption(cacheKey []byte, key []byte, useSeed bool) (value.Read, error) {
|
||||
var transformer value.Read
|
||||
var err error
|
||||
if useSeed {
|
||||
// the input key is considered safe to use here because it is coming from the KMS plugin / etcd
|
||||
transformer, err = aestransformer.NewHKDFExtendedNonceGCMTransformer(key)
|
||||
} else {
|
||||
var block cipher.Block
|
||||
block, err = aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// this is compatible with NewGCMTransformerWithUniqueKeyUnsafe for decryption
|
||||
// it would use random nonces for encryption but we never do that
|
||||
transformer, err = aestransformer.NewGCMTransformer(block)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transformer := t.baseTransformerFunc(block)
|
||||
// Use base64 of encKey as the key into the cache because hashicorp/golang-lru
|
||||
// cannot hash []uint8.
|
||||
if t.cacheEnabled {
|
||||
t.transformers.Add(base64.StdEncoding.EncodeToString(encKey), transformer)
|
||||
metrics.RecordDekCacheFillPercent(float64(t.transformers.Len()) / float64(t.cacheSize))
|
||||
}
|
||||
t.cache.set(cacheKey, transformer)
|
||||
return transformer, nil
|
||||
}
|
||||
|
||||
// getTransformer fetches the transformer corresponding to encKey from cache, if it exists.
|
||||
func (t *envelopeTransformer) getTransformer(encKey []byte) value.Transformer {
|
||||
if !t.cacheEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
_transformer, found := t.transformers.Get(base64.StdEncoding.EncodeToString(encKey))
|
||||
if found {
|
||||
return _transformer.(value.Transformer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// doEncode encodes the EncryptedObject to a byte array.
|
||||
func (t *envelopeTransformer) doEncode(request *kmstypes.EncryptedObject) ([]byte, error) {
|
||||
if err := validateEncryptedObject(request); err != nil {
|
||||
if err := ValidateEncryptedObject(request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto.Marshal(request)
|
||||
@@ -230,38 +322,86 @@ func (t *envelopeTransformer) doDecode(originalData []byte) (*kmstypes.Encrypted
|
||||
if err := proto.Unmarshal(originalData, o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate the EncryptedObject
|
||||
if err := validateEncryptedObject(o); err != nil {
|
||||
if err := ValidateEncryptedObject(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// generateKey generates a random key using system randomness.
|
||||
func generateKey(length int) (key []byte, err error) {
|
||||
defer func(start time.Time) {
|
||||
value.RecordDataKeyGeneration(start, err)
|
||||
}(time.Now())
|
||||
key = make([]byte, length)
|
||||
if _, err = rand.Read(key); err != nil {
|
||||
return nil, err
|
||||
// GenerateTransformer generates a new transformer and encrypts the DEK/seed using the envelope service.
|
||||
// It returns the transformer, the encrypted DEK/seed, cache key and error.
|
||||
func GenerateTransformer(ctx context.Context, uid string, envelopeService kmsservice.Service, useSeed bool) (value.Transformer, *kmstypes.EncryptedObject, []byte, error) {
|
||||
newTransformerFunc := func() (value.Transformer, []byte, error) {
|
||||
seed, err := aestransformer.GenerateKey(aestransformer.MinSeedSizeExtendedNonceGCM)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
transformer, err := aestransformer.NewHKDFExtendedNonceGCMTransformer(seed)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return transformer, seed, nil
|
||||
}
|
||||
if !useSeed {
|
||||
newTransformerFunc = aestransformer.NewGCMTransformerWithUniqueKeyUnsafe
|
||||
}
|
||||
transformer, newKey, err := newTransformerFunc()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
klog.V(6).InfoS("encrypting content using envelope service", "uid", uid)
|
||||
|
||||
resp, err := envelopeService.Encrypt(ctx, uid, newKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to encrypt DEK, error: %w", err)
|
||||
}
|
||||
|
||||
o := &kmstypes.EncryptedObject{
|
||||
KeyID: resp.KeyID,
|
||||
EncryptedDEKSource: resp.Ciphertext,
|
||||
EncryptedData: []byte{0}, // any non-empty value to pass validation
|
||||
Annotations: resp.Annotations,
|
||||
}
|
||||
|
||||
if useSeed {
|
||||
o.EncryptedDEKSourceType = kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED
|
||||
} else {
|
||||
o.EncryptedDEKSourceType = kmstypes.EncryptedDEKSourceType_AES_GCM_KEY
|
||||
}
|
||||
|
||||
if err := ValidateEncryptedObject(o); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
cacheKey, err := generateCacheKey(o.EncryptedDEKSourceType, resp.Ciphertext, resp.KeyID, resp.Annotations)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
o.EncryptedData = nil // make sure that later code that uses this encrypted object sets this field
|
||||
|
||||
return transformer, o, cacheKey, nil
|
||||
}
|
||||
|
||||
func validateEncryptedObject(o *kmstypes.EncryptedObject) error {
|
||||
func ValidateEncryptedObject(o *kmstypes.EncryptedObject) error {
|
||||
if o == nil {
|
||||
return fmt.Errorf("encrypted object is nil")
|
||||
}
|
||||
switch t := o.EncryptedDEKSourceType; t {
|
||||
case kmstypes.EncryptedDEKSourceType_AES_GCM_KEY:
|
||||
case kmstypes.EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED:
|
||||
default:
|
||||
return fmt.Errorf("unknown encryptedDEKSourceType: %d", t)
|
||||
}
|
||||
if len(o.EncryptedData) == 0 {
|
||||
return fmt.Errorf("encrypted data is empty")
|
||||
}
|
||||
if err := validateEncryptedDEK(o.EncryptedDEK); err != nil {
|
||||
return fmt.Errorf("failed to validate encrypted DEK: %w", err)
|
||||
if err := validateEncryptedDEKSource(o.EncryptedDEKSource); err != nil {
|
||||
return fmt.Errorf("failed to validate encrypted DEK source: %w", err)
|
||||
}
|
||||
if err := validateKeyID(o.KeyID); err != nil {
|
||||
if _, err := ValidateKeyID(o.KeyID); err != nil {
|
||||
return fmt.Errorf("failed to validate key id: %w", err)
|
||||
}
|
||||
if err := validateAnnotations(o.Annotations); err != nil {
|
||||
@@ -270,15 +410,15 @@ func validateEncryptedObject(o *kmstypes.EncryptedObject) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEncryptedDEK tests the following:
|
||||
// 1. The encrypted DEK is not empty.
|
||||
// 2. The size of encrypted DEK is less than 1 kB.
|
||||
func validateEncryptedDEK(encryptedDEK []byte) error {
|
||||
if len(encryptedDEK) == 0 {
|
||||
return fmt.Errorf("encrypted DEK is empty")
|
||||
// validateEncryptedDEKSource tests the following:
|
||||
// 1. The encrypted DEK source is not empty.
|
||||
// 2. The size of encrypted DEK source is less than 1 kB.
|
||||
func validateEncryptedDEKSource(encryptedDEKSource []byte) error {
|
||||
if len(encryptedDEKSource) == 0 {
|
||||
return fmt.Errorf("encrypted DEK source is empty")
|
||||
}
|
||||
if len(encryptedDEK) > encryptedDEKMaxSize {
|
||||
return fmt.Errorf("encrypted DEK is %d bytes, which exceeds the max size of %d", len(encryptedDEK), encryptedDEKMaxSize)
|
||||
if len(encryptedDEKSource) > encryptedDEKSourceMaxSize {
|
||||
return fmt.Errorf("encrypted DEK source is %d bytes, which exceeds the max size of %d", len(encryptedDEKSource), encryptedDEKSourceMaxSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -301,15 +441,88 @@ func validateAnnotations(annotations map[string][]byte) error {
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// validateKeyID tests the following:
|
||||
// ValidateKeyID tests the following:
|
||||
// 1. The keyID is not empty.
|
||||
// 2. The size of keyID is less than 1 kB.
|
||||
func validateKeyID(keyID string) error {
|
||||
func ValidateKeyID(keyID string) (ErrCodeKeyID, error) {
|
||||
if len(keyID) == 0 {
|
||||
return fmt.Errorf("keyID is empty")
|
||||
return errKeyIDEmptyCode, fmt.Errorf("keyID is empty")
|
||||
}
|
||||
if len(keyID) > keyIDMaxSize {
|
||||
return fmt.Errorf("keyID is %d bytes, which exceeds the max size of %d", len(keyID), keyIDMaxSize)
|
||||
if len(keyID) > KeyIDMaxSize {
|
||||
return errKeyIDTooLongCode, fmt.Errorf("keyID is %d bytes, which exceeds the max size of %d", len(keyID), KeyIDMaxSize)
|
||||
}
|
||||
return nil
|
||||
return errKeyIDOKCode, nil
|
||||
}
|
||||
|
||||
func getRequestInfoFromContext(ctx context.Context) *genericapirequest.RequestInfo {
|
||||
if reqInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
|
||||
return reqInfo
|
||||
}
|
||||
return &genericapirequest.RequestInfo{}
|
||||
}
|
||||
|
||||
// generateCacheKey returns a key for the cache.
|
||||
// The key is a concatenation of:
|
||||
// 0. encryptedDEKSourceType
|
||||
// 1. encryptedDEKSource
|
||||
// 2. keyID
|
||||
// 3. length of annotations
|
||||
// 4. annotations (sorted by key) - each annotation is a concatenation of:
|
||||
// a. annotation key
|
||||
// b. annotation value
|
||||
func generateCacheKey(encryptedDEKSourceType kmstypes.EncryptedDEKSourceType, encryptedDEKSource []byte, keyID string, annotations map[string][]byte) ([]byte, error) {
|
||||
// TODO(aramase): use sync pool buffer to avoid allocations
|
||||
b := cryptobyte.NewBuilder(nil)
|
||||
b.AddUint32(uint32(encryptedDEKSourceType))
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(encryptedDEKSource)
|
||||
})
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(toBytes(keyID))
|
||||
})
|
||||
if len(annotations) == 0 {
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// add the length of annotations to the cache key
|
||||
b.AddUint32(uint32(len(annotations)))
|
||||
|
||||
// Sort the annotations by key.
|
||||
keys := make([]string, 0, len(annotations))
|
||||
for k := range annotations {
|
||||
k := k
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
// The maximum size of annotations is annotationsMaxSize (32 kB) so we can safely
|
||||
// assume that the length of the key and value will fit in a uint16.
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(toBytes(k))
|
||||
})
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(annotations[k])
|
||||
})
|
||||
}
|
||||
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// toBytes performs unholy acts to avoid allocations
|
||||
func toBytes(s string) []byte {
|
||||
// unsafe.StringData is unspecified for the empty string, so we provide a strict interpretation
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Copied from go 1.20.1 os.File.WriteString
|
||||
// https://github.com/golang/go/blob/202a1a57064127c3f19d96df57b9f9586145e21c/src/os/file.go#L246
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
||||
|
||||
// GetHashIfNotEmpty returns the sha256 hash of the data if it is not empty.
|
||||
func GetHashIfNotEmpty(data string) string {
|
||||
if len(data) > 0 {
|
||||
return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(data)))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
38
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service.go
generated
vendored
38
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/grpc_service.go
generated
vendored
@@ -27,9 +27,11 @@ import (
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/util"
|
||||
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope/metrics"
|
||||
"k8s.io/klog/v2"
|
||||
kmsapi "k8s.io/kms/apis/v2alpha1"
|
||||
kmsapi "k8s.io/kms/apis/v2"
|
||||
kmsservice "k8s.io/kms/pkg/service"
|
||||
"k8s.io/kms/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,8 +47,8 @@ type gRPCService struct {
|
||||
}
|
||||
|
||||
// NewGRPCService returns an envelope.Service which use gRPC to communicate the remote KMS provider.
|
||||
func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Duration) (Service, error) {
|
||||
klog.V(4).Infof("Configure KMS provider with endpoint: %s", endpoint)
|
||||
func NewGRPCService(ctx context.Context, endpoint, providerName string, callTimeout time.Duration) (kmsservice.Service, error) {
|
||||
klog.V(4).InfoS("Configure KMS provider", "endpoint", endpoint)
|
||||
|
||||
addr, err := util.ParseEndpoint(endpoint)
|
||||
if err != nil {
|
||||
@@ -64,12 +66,14 @@ func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Durat
|
||||
// addr - comes from the closure
|
||||
c, err := net.DialUnix(unixProtocol, nil, &net.UnixAddr{Name: addr})
|
||||
if err != nil {
|
||||
klog.Errorf("failed to create connection to unix socket: %s, error: %v", addr, err)
|
||||
klog.ErrorS(err, "failed to create connection to unix socket", "addr", addr)
|
||||
} else {
|
||||
klog.V(4).Infof("Successfully dialed Unix socket %v", addr)
|
||||
klog.V(4).InfoS("Successfully dialed Unix socket", "addr", addr)
|
||||
}
|
||||
return c, err
|
||||
}))
|
||||
}),
|
||||
grpc.WithChainUnaryInterceptor(recordMetricsInterceptor(providerName)),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create connection to %s, error: %v", endpoint, err)
|
||||
@@ -88,7 +92,7 @@ func NewGRPCService(ctx context.Context, endpoint string, callTimeout time.Durat
|
||||
}
|
||||
|
||||
// Decrypt a given data string to obtain the original byte data.
|
||||
func (g *gRPCService) Decrypt(ctx context.Context, uid string, req *DecryptRequest) ([]byte, error) {
|
||||
func (g *gRPCService) Decrypt(ctx context.Context, uid string, req *kmsservice.DecryptRequest) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, g.callTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -106,7 +110,7 @@ func (g *gRPCService) Decrypt(ctx context.Context, uid string, req *DecryptReque
|
||||
}
|
||||
|
||||
// Encrypt bytes to a string ciphertext.
|
||||
func (g *gRPCService) Encrypt(ctx context.Context, uid string, plaintext []byte) (*EncryptResponse, error) {
|
||||
func (g *gRPCService) Encrypt(ctx context.Context, uid string, plaintext []byte) (*kmsservice.EncryptResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, g.callTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -118,7 +122,7 @@ func (g *gRPCService) Encrypt(ctx context.Context, uid string, plaintext []byte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EncryptResponse{
|
||||
return &kmsservice.EncryptResponse{
|
||||
Ciphertext: response.Ciphertext,
|
||||
KeyID: response.KeyId,
|
||||
Annotations: response.Annotations,
|
||||
@@ -126,7 +130,7 @@ func (g *gRPCService) Encrypt(ctx context.Context, uid string, plaintext []byte)
|
||||
}
|
||||
|
||||
// Status returns the status of the KMSv2 provider.
|
||||
func (g *gRPCService) Status(ctx context.Context) (*StatusResponse, error) {
|
||||
func (g *gRPCService) Status(ctx context.Context) (*kmsservice.StatusResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, g.callTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -135,5 +139,15 @@ func (g *gRPCService) Status(ctx context.Context) (*StatusResponse, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StatusResponse{Version: response.Version, Healthz: response.Healthz, KeyID: response.KeyId}, nil
|
||||
return &kmsservice.StatusResponse{Version: response.Version, Healthz: response.Healthz, KeyID: response.KeyId}, nil
|
||||
}
|
||||
|
||||
func recordMetricsInterceptor(providerName string) grpc.UnaryClientInterceptor {
|
||||
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
start := NowFunc()
|
||||
respErr := invoker(ctx, method, req, reply, cc, opts...)
|
||||
elapsed := NowFunc().Sub(start)
|
||||
metrics.RecordKMSOperationLatency(providerName, method, elapsed, respErr)
|
||||
return respErr
|
||||
}
|
||||
}
|
||||
|
||||
186
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2/api.pb.go
generated
vendored
Normal file
186
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2/api.pb.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: api.proto
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type EncryptedDEKSourceType int32
|
||||
|
||||
const (
|
||||
// AES_GCM_KEY means that the plaintext of encryptedDEKSource is the DEK itself, with AES-GCM as the encryption algorithm.
|
||||
EncryptedDEKSourceType_AES_GCM_KEY EncryptedDEKSourceType = 0
|
||||
// HKDF_SHA256_XNONCE_AES_GCM_SEED means that the plaintext of encryptedDEKSource is the pseudo random key
|
||||
// (referred to as the seed throughout the code) that is fed into HKDF expand. SHA256 is the hash algorithm
|
||||
// and first 32 bytes of encryptedData are the info param. The first 32 bytes from the HKDF stream are used
|
||||
// as the DEK with AES-GCM as the encryption algorithm.
|
||||
EncryptedDEKSourceType_HKDF_SHA256_XNONCE_AES_GCM_SEED EncryptedDEKSourceType = 1
|
||||
)
|
||||
|
||||
var EncryptedDEKSourceType_name = map[int32]string{
|
||||
0: "AES_GCM_KEY",
|
||||
1: "HKDF_SHA256_XNONCE_AES_GCM_SEED",
|
||||
}
|
||||
|
||||
var EncryptedDEKSourceType_value = map[string]int32{
|
||||
"AES_GCM_KEY": 0,
|
||||
"HKDF_SHA256_XNONCE_AES_GCM_SEED": 1,
|
||||
}
|
||||
|
||||
func (x EncryptedDEKSourceType) String() string {
|
||||
return proto.EnumName(EncryptedDEKSourceType_name, int32(x))
|
||||
}
|
||||
|
||||
func (EncryptedDEKSourceType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_00212fb1f9d3bf1c, []int{0}
|
||||
}
|
||||
|
||||
// EncryptedObject is the representation of data stored in etcd after envelope encryption.
|
||||
type EncryptedObject struct {
|
||||
// EncryptedData is the encrypted data.
|
||||
EncryptedData []byte `protobuf:"bytes,1,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"`
|
||||
// KeyID is the KMS key ID used for encryption operations.
|
||||
// keyID must satisfy the following constraints:
|
||||
// 1. The keyID is not empty.
|
||||
// 2. The size of keyID is less than 1 kB.
|
||||
KeyID string `protobuf:"bytes,2,opt,name=keyID,proto3" json:"keyID,omitempty"`
|
||||
// EncryptedDEKSource is the ciphertext of the source of the DEK used to encrypt the data stored in encryptedData.
|
||||
// encryptedDEKSourceType defines the process of using the plaintext of this field to determine the aforementioned DEK.
|
||||
// encryptedDEKSource must satisfy the following constraints:
|
||||
// 1. The encrypted DEK source is not empty.
|
||||
// 2. The size of encrypted DEK source is less than 1 kB.
|
||||
EncryptedDEKSource []byte `protobuf:"bytes,3,opt,name=encryptedDEKSource,proto3" json:"encryptedDEKSource,omitempty"`
|
||||
// Annotations is additional metadata that was provided by the KMS plugin.
|
||||
// Annotations must satisfy the following constraints:
|
||||
// 1. Annotation key must be a fully qualified domain name that conforms to the definition in DNS (RFC 1123).
|
||||
// 2. The size of annotations keys + values is less than 32 kB.
|
||||
Annotations map[string][]byte `protobuf:"bytes,4,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
// encryptedDEKSourceType defines the process of using the plaintext of encryptedDEKSource to determine the DEK.
|
||||
EncryptedDEKSourceType EncryptedDEKSourceType `protobuf:"varint,5,opt,name=encryptedDEKSourceType,proto3,enum=v2.EncryptedDEKSourceType" json:"encryptedDEKSourceType,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) Reset() { *m = EncryptedObject{} }
|
||||
func (m *EncryptedObject) String() string { return proto.CompactTextString(m) }
|
||||
func (*EncryptedObject) ProtoMessage() {}
|
||||
func (*EncryptedObject) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_00212fb1f9d3bf1c, []int{0}
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EncryptedObject.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EncryptedObject.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EncryptedObject.Merge(m, src)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Size() int {
|
||||
return xxx_messageInfo_EncryptedObject.Size(m)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EncryptedObject.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EncryptedObject proto.InternalMessageInfo
|
||||
|
||||
func (m *EncryptedObject) GetEncryptedData() []byte {
|
||||
if m != nil {
|
||||
return m.EncryptedData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetKeyID() string {
|
||||
if m != nil {
|
||||
return m.KeyID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetEncryptedDEKSource() []byte {
|
||||
if m != nil {
|
||||
return m.EncryptedDEKSource
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetAnnotations() map[string][]byte {
|
||||
if m != nil {
|
||||
return m.Annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetEncryptedDEKSourceType() EncryptedDEKSourceType {
|
||||
if m != nil {
|
||||
return m.EncryptedDEKSourceType
|
||||
}
|
||||
return EncryptedDEKSourceType_AES_GCM_KEY
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("v2.EncryptedDEKSourceType", EncryptedDEKSourceType_name, EncryptedDEKSourceType_value)
|
||||
proto.RegisterType((*EncryptedObject)(nil), "v2.EncryptedObject")
|
||||
proto.RegisterMapType((map[string][]byte)(nil), "v2.EncryptedObject.AnnotationsEntry")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) }
|
||||
|
||||
var fileDescriptor_00212fb1f9d3bf1c = []byte{
|
||||
// 329 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xe1, 0x4b, 0xc2, 0x40,
|
||||
0x18, 0xc6, 0xdb, 0xcc, 0xc0, 0xd3, 0x72, 0x1c, 0x21, 0xc3, 0x2f, 0x8d, 0xf2, 0xc3, 0xe8, 0xc3,
|
||||
0x0e, 0x16, 0x85, 0x44, 0x08, 0xe6, 0xce, 0x0c, 0x49, 0x61, 0xeb, 0x43, 0xf5, 0x65, 0x9c, 0xf6,
|
||||
0x22, 0x6b, 0xb6, 0x1b, 0xb7, 0xf3, 0x60, 0x7f, 0x6a, 0xff, 0x4d, 0x38, 0x13, 0xd3, 0xec, 0xdb,
|
||||
0xbd, 0xef, 0xfd, 0xde, 0xe7, 0xb9, 0x7b, 0x5e, 0x54, 0x61, 0x69, 0xe4, 0xa4, 0x82, 0x4b, 0x8e,
|
||||
0x75, 0xe5, 0x9e, 0x7f, 0xe9, 0xa8, 0x4e, 0x93, 0xa9, 0xc8, 0x53, 0x09, 0xef, 0xe3, 0xc9, 0x07,
|
||||
0x4c, 0x25, 0x6e, 0xa1, 0x63, 0x58, 0xb7, 0x3c, 0x26, 0x99, 0xa9, 0x59, 0x9a, 0x5d, 0xf3, 0xb7,
|
||||
0x9b, 0xf8, 0x14, 0x95, 0x63, 0xc8, 0x1f, 0x3d, 0x53, 0xb7, 0x34, 0xbb, 0xe2, 0xaf, 0x0a, 0xec,
|
||||
0x20, 0xbc, 0xc1, 0xe8, 0x30, 0xe0, 0x0b, 0x31, 0x05, 0xb3, 0x54, 0x08, 0xec, 0xb9, 0xc1, 0x7d,
|
||||
0x54, 0x65, 0x49, 0xc2, 0x25, 0x93, 0x11, 0x4f, 0x32, 0xf3, 0xd0, 0x2a, 0xd9, 0x55, 0xb7, 0xe5,
|
||||
0x28, 0xd7, 0xd9, 0x79, 0x95, 0xd3, 0xdd, 0x60, 0x34, 0x91, 0x22, 0xf7, 0x7f, 0x0f, 0x62, 0x1f,
|
||||
0x35, 0xfe, 0xaa, 0x3f, 0xe7, 0x29, 0x98, 0x65, 0x4b, 0xb3, 0x4f, 0xdc, 0xe6, 0x96, 0xe4, 0x16,
|
||||
0xe1, 0xff, 0x33, 0xd9, 0xec, 0x20, 0x63, 0xd7, 0x14, 0x1b, 0xa8, 0x14, 0x43, 0x5e, 0x24, 0x52,
|
||||
0xf1, 0x97, 0xc7, 0x65, 0x0e, 0x8a, 0xcd, 0x17, 0x50, 0xe4, 0x50, 0xf3, 0x57, 0xc5, 0xad, 0xde,
|
||||
0xd6, 0x2e, 0x47, 0xa8, 0xb1, 0xdf, 0x11, 0xd7, 0x51, 0xb5, 0x4b, 0x83, 0xf0, 0xa1, 0xf7, 0x14,
|
||||
0x0e, 0xe9, 0xab, 0x71, 0x80, 0x2f, 0xd0, 0xd9, 0x60, 0xe8, 0xf5, 0xc3, 0x60, 0xd0, 0x75, 0xaf,
|
||||
0x6f, 0xc2, 0x97, 0xd1, 0x78, 0xd4, 0xa3, 0xe1, 0x9a, 0x09, 0x28, 0xf5, 0x0c, 0xed, 0xbe, 0xf3,
|
||||
0x76, 0x17, 0xb7, 0x33, 0x27, 0xe2, 0x84, 0xa5, 0x51, 0x06, 0x42, 0x81, 0x20, 0x69, 0x3c, 0x23,
|
||||
0x99, 0xe4, 0x82, 0xcd, 0x80, 0x14, 0xce, 0xe4, 0xe7, 0x33, 0x04, 0x12, 0x05, 0x73, 0x9e, 0x02,
|
||||
0x89, 0x3f, 0x33, 0xe5, 0x12, 0xe5, 0x4e, 0x8e, 0x8a, 0xb5, 0x5f, 0x7d, 0x07, 0x00, 0x00, 0xff,
|
||||
0xff, 0xcc, 0x0f, 0x2b, 0x2e, 0x03, 0x02, 0x00, 0x00,
|
||||
}
|
||||
60
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2/api.proto
generated
vendored
Normal file
60
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2/api.proto
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2022 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.
|
||||
*/
|
||||
|
||||
// To regenerate api.pb.go run `hack/update-codegen.sh protobindings`
|
||||
syntax = "proto3";
|
||||
|
||||
package v2;
|
||||
option go_package = "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2";
|
||||
|
||||
// EncryptedObject is the representation of data stored in etcd after envelope encryption.
|
||||
message EncryptedObject {
|
||||
// EncryptedData is the encrypted data.
|
||||
bytes encryptedData = 1;
|
||||
|
||||
// KeyID is the KMS key ID used for encryption operations.
|
||||
// keyID must satisfy the following constraints:
|
||||
// 1. The keyID is not empty.
|
||||
// 2. The size of keyID is less than 1 kB.
|
||||
string keyID = 2;
|
||||
|
||||
// EncryptedDEKSource is the ciphertext of the source of the DEK used to encrypt the data stored in encryptedData.
|
||||
// encryptedDEKSourceType defines the process of using the plaintext of this field to determine the aforementioned DEK.
|
||||
// encryptedDEKSource must satisfy the following constraints:
|
||||
// 1. The encrypted DEK source is not empty.
|
||||
// 2. The size of encrypted DEK source is less than 1 kB.
|
||||
bytes encryptedDEKSource = 3;
|
||||
|
||||
// Annotations is additional metadata that was provided by the KMS plugin.
|
||||
// Annotations must satisfy the following constraints:
|
||||
// 1. Annotation key must be a fully qualified domain name that conforms to the definition in DNS (RFC 1123).
|
||||
// 2. The size of annotations keys + values is less than 32 kB.
|
||||
map<string, bytes> annotations = 4;
|
||||
|
||||
// encryptedDEKSourceType defines the process of using the plaintext of encryptedDEKSource to determine the DEK.
|
||||
EncryptedDEKSourceType encryptedDEKSourceType = 5;
|
||||
}
|
||||
|
||||
enum EncryptedDEKSourceType {
|
||||
// AES_GCM_KEY means that the plaintext of encryptedDEKSource is the DEK itself, with AES-GCM as the encryption algorithm.
|
||||
AES_GCM_KEY = 0;
|
||||
|
||||
// HKDF_SHA256_XNONCE_AES_GCM_SEED means that the plaintext of encryptedDEKSource is the pseudo random key
|
||||
// (referred to as the seed throughout the code) that is fed into HKDF expand. SHA256 is the hash algorithm
|
||||
// and first 32 bytes of encryptedData are the info param. The first 32 bytes from the HKDF stream are used
|
||||
// as the DEK with AES-GCM as the encryption algorithm.
|
||||
HKDF_SHA256_XNONCE_AES_GCM_SEED = 1;
|
||||
}
|
||||
@@ -14,5 +14,5 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v2alpha1 contains definition of kms-plugin's serialized types.
|
||||
package v2alpha1
|
||||
// Package v2 contains definition of kms-plugin's serialized types.
|
||||
package v2
|
||||
128
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2alpha1/api.pb.go
generated
vendored
128
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2alpha1/api.pb.go
generated
vendored
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: api.proto
|
||||
|
||||
package v2alpha1
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// EncryptedObject is the representation of data stored in etcd after envelope encryption.
|
||||
type EncryptedObject struct {
|
||||
// EncryptedData is the encrypted data.
|
||||
EncryptedData []byte `protobuf:"bytes,1,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"`
|
||||
// KeyID is the KMS key ID used for encryption operations.
|
||||
KeyID string `protobuf:"bytes,2,opt,name=keyID,proto3" json:"keyID,omitempty"`
|
||||
// EncryptedDEK is the encrypted DEK.
|
||||
EncryptedDEK []byte `protobuf:"bytes,3,opt,name=encryptedDEK,proto3" json:"encryptedDEK,omitempty"`
|
||||
// Annotations is additional metadata that was provided by the KMS plugin.
|
||||
Annotations map[string][]byte `protobuf:"bytes,4,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) Reset() { *m = EncryptedObject{} }
|
||||
func (m *EncryptedObject) String() string { return proto.CompactTextString(m) }
|
||||
func (*EncryptedObject) ProtoMessage() {}
|
||||
func (*EncryptedObject) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_00212fb1f9d3bf1c, []int{0}
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EncryptedObject.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EncryptedObject.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EncryptedObject.Merge(m, src)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_Size() int {
|
||||
return xxx_messageInfo_EncryptedObject.Size(m)
|
||||
}
|
||||
func (m *EncryptedObject) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EncryptedObject.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EncryptedObject proto.InternalMessageInfo
|
||||
|
||||
func (m *EncryptedObject) GetEncryptedData() []byte {
|
||||
if m != nil {
|
||||
return m.EncryptedData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetKeyID() string {
|
||||
if m != nil {
|
||||
return m.KeyID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetEncryptedDEK() []byte {
|
||||
if m != nil {
|
||||
return m.EncryptedDEK
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EncryptedObject) GetAnnotations() map[string][]byte {
|
||||
if m != nil {
|
||||
return m.Annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*EncryptedObject)(nil), "v2alpha1.EncryptedObject")
|
||||
proto.RegisterMapType((map[string][]byte)(nil), "v2alpha1.EncryptedObject.AnnotationsEntry")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) }
|
||||
|
||||
var fileDescriptor_00212fb1f9d3bf1c = []byte{
|
||||
// 200 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4c, 0x2c, 0xc8, 0xd4,
|
||||
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x28, 0x33, 0x4a, 0xcc, 0x29, 0xc8, 0x48, 0x34, 0x54,
|
||||
0xfa, 0xcf, 0xc8, 0xc5, 0xef, 0x9a, 0x97, 0x5c, 0x54, 0x59, 0x50, 0x92, 0x9a, 0xe2, 0x9f, 0x94,
|
||||
0x95, 0x9a, 0x5c, 0x22, 0xa4, 0xc2, 0xc5, 0x9b, 0x0a, 0x13, 0x72, 0x49, 0x2c, 0x49, 0x94, 0x60,
|
||||
0x54, 0x60, 0xd4, 0xe0, 0x09, 0x42, 0x15, 0x14, 0x12, 0xe1, 0x62, 0xcd, 0x4e, 0xad, 0xf4, 0x74,
|
||||
0x91, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x84, 0x94, 0xb8, 0x78, 0x10, 0xca, 0x5c,
|
||||
0xbd, 0x25, 0x98, 0xc1, 0x5a, 0x51, 0xc4, 0x84, 0x7c, 0xb8, 0xb8, 0x13, 0xf3, 0xf2, 0xf2, 0x4b,
|
||||
0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x8a, 0x25, 0x58, 0x14, 0x98, 0x35, 0xb8, 0x8d, 0xb4, 0xf4, 0x60,
|
||||
0x6e, 0xd2, 0x43, 0x73, 0x8f, 0x9e, 0x23, 0x42, 0xb1, 0x6b, 0x5e, 0x49, 0x51, 0x65, 0x10, 0xb2,
|
||||
0x76, 0x29, 0x3b, 0x2e, 0x01, 0x74, 0x05, 0x42, 0x02, 0x5c, 0xcc, 0xd9, 0xa9, 0x95, 0x60, 0x77,
|
||||
0x73, 0x06, 0x81, 0x98, 0x20, 0xd7, 0x96, 0x25, 0xe6, 0x94, 0xa6, 0x82, 0x5d, 0xcb, 0x13, 0x04,
|
||||
0xe1, 0x58, 0x31, 0x59, 0x30, 0x26, 0xb1, 0x81, 0x83, 0xc4, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff,
|
||||
0x88, 0x8c, 0xbb, 0x4e, 0x1f, 0x01, 0x00, 0x00,
|
||||
}
|
||||
35
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2alpha1/api.proto
generated
vendored
35
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/kmsv2/v2alpha1/api.proto
generated
vendored
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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.
|
||||
*/
|
||||
|
||||
// To regenerate api.pb.go run hack/update-generated-kms.sh
|
||||
syntax = "proto3";
|
||||
|
||||
package v2alpha1;
|
||||
|
||||
// EncryptedObject is the representation of data stored in etcd after envelope encryption.
|
||||
message EncryptedObject {
|
||||
// EncryptedData is the encrypted data.
|
||||
bytes encryptedData = 1;
|
||||
|
||||
// KeyID is the KMS key ID used for encryption operations.
|
||||
string keyID = 2;
|
||||
|
||||
// EncryptedDEK is the encrypted DEK.
|
||||
bytes encryptedDEK = 3;
|
||||
|
||||
// Annotations is additional metadata that was provided by the KMS plugin.
|
||||
map<string, bytes> annotations = 4;
|
||||
}
|
||||
224
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/metrics/metrics.go
generated
vendored
224
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/metrics/metrics.go
generated
vendored
@@ -17,11 +17,20 @@ limitations under the License.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/lru"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,6 +40,13 @@ const (
|
||||
ToStorageLabel = "to_storage"
|
||||
)
|
||||
|
||||
type metricLabels struct {
|
||||
transformationType string
|
||||
providerName string
|
||||
keyIDHash string
|
||||
apiServerIDHash string
|
||||
}
|
||||
|
||||
/*
|
||||
* By default, all the following metrics are defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes)
|
||||
@@ -40,12 +56,18 @@ const (
|
||||
* the metric stability policy.
|
||||
*/
|
||||
var (
|
||||
lockLastFromStorage sync.Mutex
|
||||
lockLastToStorage sync.Mutex
|
||||
lockLastFromStorage sync.Mutex
|
||||
lockLastToStorage sync.Mutex
|
||||
lockRecordKeyID sync.Mutex
|
||||
lockRecordKeyIDStatus sync.Mutex
|
||||
|
||||
lastFromStorage time.Time
|
||||
lastToStorage time.Time
|
||||
lastFromStorage time.Time
|
||||
lastToStorage time.Time
|
||||
keyIDHashTotalMetricLabels *lru.Cache
|
||||
keyIDHashStatusLastTimestampSecondsMetricLabels *lru.Cache
|
||||
cacheSize = 100
|
||||
|
||||
// This metric is only used for KMS v1 API.
|
||||
dekCacheFillPercent = metrics.NewGauge(
|
||||
&metrics.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
@@ -56,6 +78,7 @@ var (
|
||||
},
|
||||
)
|
||||
|
||||
// This metric is only used for KMS v1 API.
|
||||
dekCacheInterArrivals = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
@@ -67,17 +90,157 @@ var (
|
||||
},
|
||||
[]string{"transformation_type"},
|
||||
)
|
||||
|
||||
// These metrics are made public to be used by unit tests.
|
||||
KMSOperationsLatencyMetric = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "kms_operations_latency_seconds",
|
||||
Help: "KMS operation duration with gRPC error code status total.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
// Use custom buckets to avoid the default buckets which are too small for KMS operations.
|
||||
// Start 0.1ms with the last bucket being [~52s, +Inf)
|
||||
Buckets: metrics.ExponentialBuckets(0.0001, 2, 20),
|
||||
},
|
||||
[]string{"provider_name", "method_name", "grpc_status_code"},
|
||||
)
|
||||
|
||||
// keyIDHashTotal is the number of times a keyID is used
|
||||
// e.g. apiserver_envelope_encryption_key_id_hash_total counter
|
||||
// apiserver_envelope_encryption_key_id_hash_total{apiserver_id_hash="sha256",key_id_hash="sha256",
|
||||
// provider_name="providerName",transformation_type="from_storage"} 1
|
||||
KeyIDHashTotal = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "key_id_hash_total",
|
||||
Help: "Number of times a keyID is used split by transformation type, provider, and apiserver identity.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"transformation_type", "provider_name", "key_id_hash", "apiserver_id_hash"},
|
||||
)
|
||||
|
||||
// keyIDHashLastTimestampSeconds is the last time in seconds when a keyID was used
|
||||
// e.g. apiserver_envelope_encryption_key_id_hash_last_timestamp_seconds{apiserver_id_hash="sha256",key_id_hash="sha256", provider_name="providerName",transformation_type="from_storage"} 1.674865558833728e+09
|
||||
KeyIDHashLastTimestampSeconds = metrics.NewGaugeVec(
|
||||
&metrics.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "key_id_hash_last_timestamp_seconds",
|
||||
Help: "The last time in seconds when a keyID was used.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"transformation_type", "provider_name", "key_id_hash", "apiserver_id_hash"},
|
||||
)
|
||||
|
||||
// keyIDHashStatusLastTimestampSeconds is the last time in seconds when a keyID was returned by the Status RPC call.
|
||||
// e.g. apiserver_envelope_encryption_key_id_hash_status_last_timestamp_seconds{apiserver_id_hash="sha256",key_id_hash="sha256", provider_name="providerName"} 1.674865558833728e+09
|
||||
KeyIDHashStatusLastTimestampSeconds = metrics.NewGaugeVec(
|
||||
&metrics.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "key_id_hash_status_last_timestamp_seconds",
|
||||
Help: "The last time in seconds when a keyID was returned by the Status RPC call.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"provider_name", "key_id_hash", "apiserver_id_hash"},
|
||||
)
|
||||
|
||||
InvalidKeyIDFromStatusTotal = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "invalid_key_id_from_status_total",
|
||||
Help: "Number of times an invalid keyID is returned by the Status RPC call split by error.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"provider_name", "error"},
|
||||
)
|
||||
|
||||
DekSourceCacheSize = metrics.NewGaugeVec(
|
||||
&metrics.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "dek_source_cache_size",
|
||||
Help: "Number of records in data encryption key (DEK) source cache. On a restart, this value is an approximation of the number of decrypt RPC calls the server will make to the KMS plugin.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"provider_name"},
|
||||
)
|
||||
)
|
||||
|
||||
var registerMetricsFunc sync.Once
|
||||
var hashPool *sync.Pool
|
||||
|
||||
func registerLRUMetrics() {
|
||||
if keyIDHashTotalMetricLabels != nil {
|
||||
keyIDHashTotalMetricLabels.Clear()
|
||||
}
|
||||
if keyIDHashStatusLastTimestampSecondsMetricLabels != nil {
|
||||
keyIDHashStatusLastTimestampSecondsMetricLabels.Clear()
|
||||
}
|
||||
|
||||
keyIDHashTotalMetricLabels = lru.NewWithEvictionFunc(cacheSize, func(key lru.Key, _ interface{}) {
|
||||
item := key.(metricLabels)
|
||||
if deleted := KeyIDHashTotal.DeleteLabelValues(item.transformationType, item.providerName, item.keyIDHash, item.apiServerIDHash); deleted {
|
||||
klog.InfoS("Deleted keyIDHashTotalMetricLabels", "transformationType", item.transformationType,
|
||||
"providerName", item.providerName, "keyIDHash", item.keyIDHash, "apiServerIDHash", item.apiServerIDHash)
|
||||
}
|
||||
if deleted := KeyIDHashLastTimestampSeconds.DeleteLabelValues(item.transformationType, item.providerName, item.keyIDHash, item.apiServerIDHash); deleted {
|
||||
klog.InfoS("Deleted keyIDHashLastTimestampSecondsMetricLabels", "transformationType", item.transformationType,
|
||||
"providerName", item.providerName, "keyIDHash", item.keyIDHash, "apiServerIDHash", item.apiServerIDHash)
|
||||
}
|
||||
})
|
||||
keyIDHashStatusLastTimestampSecondsMetricLabels = lru.NewWithEvictionFunc(cacheSize, func(key lru.Key, _ interface{}) {
|
||||
item := key.(metricLabels)
|
||||
if deleted := KeyIDHashStatusLastTimestampSeconds.DeleteLabelValues(item.providerName, item.keyIDHash, item.apiServerIDHash); deleted {
|
||||
klog.InfoS("Deleted keyIDHashStatusLastTimestampSecondsMetricLabels", "providerName", item.providerName, "keyIDHash", item.keyIDHash, "apiServerIDHash", item.apiServerIDHash)
|
||||
}
|
||||
})
|
||||
}
|
||||
func RegisterMetrics() {
|
||||
registerMetricsFunc.Do(func() {
|
||||
registerLRUMetrics()
|
||||
hashPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return sha256.New()
|
||||
},
|
||||
}
|
||||
legacyregistry.MustRegister(dekCacheFillPercent)
|
||||
legacyregistry.MustRegister(dekCacheInterArrivals)
|
||||
legacyregistry.MustRegister(DekSourceCacheSize)
|
||||
legacyregistry.MustRegister(KeyIDHashTotal)
|
||||
legacyregistry.MustRegister(KeyIDHashLastTimestampSeconds)
|
||||
legacyregistry.MustRegister(KeyIDHashStatusLastTimestampSeconds)
|
||||
legacyregistry.MustRegister(InvalidKeyIDFromStatusTotal)
|
||||
legacyregistry.MustRegister(KMSOperationsLatencyMetric)
|
||||
})
|
||||
}
|
||||
|
||||
// RecordKeyID records total count and last time in seconds when a KeyID was used for TransformFromStorage and TransformToStorage operations
|
||||
func RecordKeyID(transformationType, providerName, keyID, apiServerID string) {
|
||||
lockRecordKeyID.Lock()
|
||||
defer lockRecordKeyID.Unlock()
|
||||
|
||||
keyIDHash, apiServerIDHash := addLabelToCache(keyIDHashTotalMetricLabels, transformationType, providerName, keyID, apiServerID)
|
||||
KeyIDHashTotal.WithLabelValues(transformationType, providerName, keyIDHash, apiServerIDHash).Inc()
|
||||
KeyIDHashLastTimestampSeconds.WithLabelValues(transformationType, providerName, keyIDHash, apiServerIDHash).SetToCurrentTime()
|
||||
}
|
||||
|
||||
// RecordKeyIDFromStatus records last time in seconds when a KeyID was returned by the Status RPC call.
|
||||
func RecordKeyIDFromStatus(providerName, keyID, apiServerID string) {
|
||||
lockRecordKeyIDStatus.Lock()
|
||||
defer lockRecordKeyIDStatus.Unlock()
|
||||
|
||||
keyIDHash, apiServerIDHash := addLabelToCache(keyIDHashStatusLastTimestampSecondsMetricLabels, "", providerName, keyID, apiServerID)
|
||||
KeyIDHashStatusLastTimestampSeconds.WithLabelValues(providerName, keyIDHash, apiServerIDHash).SetToCurrentTime()
|
||||
}
|
||||
|
||||
func RecordInvalidKeyIDFromStatus(providerName, errCode string) {
|
||||
InvalidKeyIDFromStatusTotal.WithLabelValues(providerName, errCode).Inc()
|
||||
}
|
||||
|
||||
func RecordArrival(transformationType string, start time.Time) {
|
||||
switch transformationType {
|
||||
case FromStorageLabel:
|
||||
@@ -104,3 +267,56 @@ func RecordArrival(transformationType string, start time.Time) {
|
||||
func RecordDekCacheFillPercent(percent float64) {
|
||||
dekCacheFillPercent.Set(percent)
|
||||
}
|
||||
|
||||
func RecordDekSourceCacheSize(providerName string, size int) {
|
||||
DekSourceCacheSize.WithLabelValues(providerName).Set(float64(size))
|
||||
}
|
||||
|
||||
// RecordKMSOperationLatency records the latency of KMS operation.
|
||||
func RecordKMSOperationLatency(providerName, methodName string, duration time.Duration, err error) {
|
||||
KMSOperationsLatencyMetric.WithLabelValues(providerName, methodName, getErrorCode(err)).Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
type gRPCError interface {
|
||||
GRPCStatus() *status.Status
|
||||
}
|
||||
|
||||
func getErrorCode(err error) string {
|
||||
if err == nil {
|
||||
return codes.OK.String()
|
||||
}
|
||||
|
||||
// handle errors wrapped with fmt.Errorf and similar
|
||||
var s gRPCError
|
||||
if errors.As(err, &s) {
|
||||
return s.GRPCStatus().Code().String()
|
||||
}
|
||||
|
||||
// This is not gRPC error. The operation must have failed before gRPC
|
||||
// method was called, otherwise we would get gRPC error.
|
||||
return "unknown-non-grpc"
|
||||
}
|
||||
|
||||
func getHash(data string) string {
|
||||
if len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
h := hashPool.Get().(hash.Hash)
|
||||
h.Reset()
|
||||
h.Write([]byte(data))
|
||||
dataHash := fmt.Sprintf("sha256:%x", h.Sum(nil))
|
||||
hashPool.Put(h)
|
||||
return dataHash
|
||||
}
|
||||
|
||||
func addLabelToCache(c *lru.Cache, transformationType, providerName, keyID, apiServerID string) (string, string) {
|
||||
keyIDHash := getHash(keyID)
|
||||
apiServerIDHash := getHash(apiServerID)
|
||||
c.Add(metricLabels{
|
||||
transformationType: transformationType,
|
||||
providerName: providerName,
|
||||
keyIDHash: keyIDHash,
|
||||
apiServerIDHash: apiServerIDHash,
|
||||
}, nil) // value is irrelevant, this is a set and not a map
|
||||
return keyIDHash, apiServerIDHash
|
||||
}
|
||||
|
||||
54
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/util/util.go
generated
vendored
54
vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/util/util.go
generated
vendored
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// unixProtocol is the only supported protocol for remote KMS provider.
|
||||
unixProtocol = "unix"
|
||||
)
|
||||
|
||||
// Parse the endpoint to extract schema, host or path.
|
||||
func ParseEndpoint(endpoint string) (string, error) {
|
||||
if len(endpoint) == 0 {
|
||||
return "", fmt.Errorf("remote KMS provider can't use empty string as endpoint")
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid endpoint %q for remote KMS provider, error: %v", endpoint, err)
|
||||
}
|
||||
|
||||
if u.Scheme != unixProtocol {
|
||||
return "", fmt.Errorf("unsupported scheme %q for remote KMS provider", u.Scheme)
|
||||
}
|
||||
|
||||
// Linux abstract namespace socket - no physical file required
|
||||
// Warning: Linux Abstract sockets have not concept of ACL (unlike traditional file based sockets).
|
||||
// However, Linux Abstract sockets are subject to Linux networking namespace, so will only be accessible to
|
||||
// containers within the same pod (unless host networking is used).
|
||||
if strings.HasPrefix(u.Path, "/@") {
|
||||
return strings.TrimPrefix(u.Path, "/"), nil
|
||||
}
|
||||
|
||||
return u.Path, nil
|
||||
}
|
||||
35
vendor/k8s.io/apiserver/pkg/storage/value/metrics.go
generated
vendored
35
vendor/k8s.io/apiserver/pkg/storage/value/metrics.go
generated
vendored
@@ -17,9 +17,11 @@ limitations under the License.
|
||||
package value
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
@@ -51,7 +53,7 @@ var (
|
||||
Buckets: metrics.ExponentialBuckets(5e-6, 2, 25),
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"transformation_type"},
|
||||
[]string{"transformation_type", "transformer_prefix"},
|
||||
)
|
||||
|
||||
transformerOperationsTotal = metrics.NewCounterVec(
|
||||
@@ -59,7 +61,7 @@ var (
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "transformation_operations_total",
|
||||
Help: "Total number of transformations.",
|
||||
Help: "Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"transformation_type", "transformer_prefix", "status"},
|
||||
@@ -111,12 +113,11 @@ func RegisterMetrics() {
|
||||
|
||||
// RecordTransformation records latencies and count of TransformFromStorage and TransformToStorage operations.
|
||||
// Note that transformation_failures_total metric is deprecated, use transformation_operations_total instead.
|
||||
func RecordTransformation(transformationType, transformerPrefix string, start time.Time, err error) {
|
||||
transformerOperationsTotal.WithLabelValues(transformationType, transformerPrefix, status.Code(err).String()).Inc()
|
||||
func RecordTransformation(transformationType, transformerPrefix string, elapsed time.Duration, err error) {
|
||||
transformerOperationsTotal.WithLabelValues(transformationType, transformerPrefix, getErrorCode(err)).Inc()
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
transformerLatencies.WithLabelValues(transformationType).Observe(sinceInSeconds(start))
|
||||
if err == nil {
|
||||
transformerLatencies.WithLabelValues(transformationType, transformerPrefix).Observe(elapsed.Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,3 +140,23 @@ func RecordDataKeyGeneration(start time.Time, err error) {
|
||||
func sinceInSeconds(start time.Time) float64 {
|
||||
return time.Since(start).Seconds()
|
||||
}
|
||||
|
||||
type gRPCError interface {
|
||||
GRPCStatus() *status.Status
|
||||
}
|
||||
|
||||
func getErrorCode(err error) string {
|
||||
if err == nil {
|
||||
return codes.OK.String()
|
||||
}
|
||||
|
||||
// handle errors wrapped with fmt.Errorf and similar
|
||||
var s gRPCError
|
||||
if errors.As(err, &s) {
|
||||
return s.GRPCStatus().Code().String()
|
||||
}
|
||||
|
||||
// This is not gRPC error. The operation must have failed before gRPC
|
||||
// method was called, otherwise we would get gRPC error.
|
||||
return "unknown-non-grpc"
|
||||
}
|
||||
|
||||
61
vendor/k8s.io/apiserver/pkg/storage/value/transformer.go
generated
vendored
61
vendor/k8s.io/apiserver/pkg/storage/value/transformer.go
generated
vendored
@@ -23,7 +23,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -39,17 +42,30 @@ type Context interface {
|
||||
AuthenticatedData() []byte
|
||||
}
|
||||
|
||||
// Transformer allows a value to be transformed before being read from or written to the underlying store. The methods
|
||||
// must be able to undo the transformation caused by the other.
|
||||
type Transformer interface {
|
||||
type Read interface {
|
||||
// TransformFromStorage may transform the provided data from its underlying storage representation or return an error.
|
||||
// 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
|
||||
// have not changed.
|
||||
TransformFromStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, stale bool, err error)
|
||||
}
|
||||
|
||||
type Write interface {
|
||||
// TransformToStorage may transform the provided data into the appropriate form in storage or return an error.
|
||||
TransformToStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, err error)
|
||||
}
|
||||
|
||||
// Transformer allows a value to be transformed before being read from or written to the underlying store. The methods
|
||||
// must be able to undo the transformation caused by the other.
|
||||
type Transformer interface {
|
||||
Read
|
||||
Write
|
||||
}
|
||||
|
||||
// ResourceTransformers returns a transformer for the provided resource.
|
||||
type ResourceTransformers interface {
|
||||
TransformerForResource(resource schema.GroupResource) Transformer
|
||||
}
|
||||
|
||||
// DefaultContext is a simple implementation of Context for a slice of bytes.
|
||||
type DefaultContext []byte
|
||||
|
||||
@@ -100,9 +116,9 @@ func (t *prefixTransformers) TransformFromStorage(ctx context.Context, data []by
|
||||
continue
|
||||
}
|
||||
if len(transformer.Prefix) == 0 {
|
||||
RecordTransformation("from_storage", "identity", start, err)
|
||||
RecordTransformation("from_storage", "identity", time.Since(start), err)
|
||||
} else {
|
||||
RecordTransformation("from_storage", string(transformer.Prefix), start, err)
|
||||
RecordTransformation("from_storage", string(transformer.Prefix), time.Since(start), err)
|
||||
}
|
||||
|
||||
// It is valid to have overlapping prefixes when the same encryption provider
|
||||
@@ -144,9 +160,10 @@ func (t *prefixTransformers) TransformFromStorage(ctx context.Context, data []by
|
||||
}
|
||||
}
|
||||
if err := errors.Reduce(errors.NewAggregate(errs)); err != nil {
|
||||
logTransformErr(ctx, err, "failed to decrypt data")
|
||||
return nil, false, err
|
||||
}
|
||||
RecordTransformation("from_storage", "unknown", start, t.err)
|
||||
RecordTransformation("from_storage", "unknown", time.Since(start), t.err)
|
||||
return nil, false, t.err
|
||||
}
|
||||
|
||||
@@ -155,8 +172,9 @@ func (t *prefixTransformers) TransformToStorage(ctx context.Context, data []byte
|
||||
start := time.Now()
|
||||
transformer := t.transformers[0]
|
||||
result, err := transformer.Transformer.TransformToStorage(ctx, data, dataCtx)
|
||||
RecordTransformation("to_storage", string(transformer.Prefix), start, err)
|
||||
RecordTransformation("to_storage", string(transformer.Prefix), time.Since(start), err)
|
||||
if err != nil {
|
||||
logTransformErr(ctx, err, "failed to encrypt data")
|
||||
return nil, err
|
||||
}
|
||||
prefixedData := make([]byte, len(transformer.Prefix), len(result)+len(transformer.Prefix))
|
||||
@@ -164,3 +182,32 @@ func (t *prefixTransformers) TransformToStorage(ctx context.Context, data []byte
|
||||
prefixedData = append(prefixedData, result...)
|
||||
return prefixedData, nil
|
||||
}
|
||||
|
||||
func logTransformErr(ctx context.Context, err error, message string) {
|
||||
requestInfo := getRequestInfoFromContext(ctx)
|
||||
if klogLevel6 := klog.V(6); klogLevel6.Enabled() {
|
||||
klogLevel6.InfoSDepth(
|
||||
1,
|
||||
message,
|
||||
"err", err,
|
||||
"group", requestInfo.APIGroup,
|
||||
"version", requestInfo.APIVersion,
|
||||
"resource", requestInfo.Resource,
|
||||
"subresource", requestInfo.Subresource,
|
||||
"verb", requestInfo.Verb,
|
||||
"namespace", requestInfo.Namespace,
|
||||
"name", requestInfo.Name,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
klog.ErrorSDepth(1, err, message)
|
||||
}
|
||||
|
||||
func getRequestInfoFromContext(ctx context.Context) *genericapirequest.RequestInfo {
|
||||
if reqInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
|
||||
return reqInfo
|
||||
}
|
||||
return &genericapirequest.RequestInfo{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user