add support for SSE-S3 bulk ETag decryption (#14627)

This commit adds support for bulk ETag
decryption for SSE-S3 encrypted objects.

If KES supports a bulk decryption API, then
MinIO will check whether its policy grants
access to this API. If so, MinIO will use
a bulk API call instead of sending encrypted
ETags serially to KES.

Note that MinIO will not use the KES bulk API
if its client certificate is an admin identity.

MinIO will process object listings in batches.
A batch has a configurable size that can be set
via `MINIO_KMS_KES_BULK_API_BATCH_SIZE=N`.
It defaults to `500`.

This env. variable is experimental and may be
renamed / removed in the future.

Signed-off-by: Andreas Auernhammer <hi@aead.dev>
This commit is contained in:
Andreas Auernhammer
2022-03-25 23:01:41 +01:00
committed by GitHub
parent 3970204009
commit 4d2fc530d0
9 changed files with 252 additions and 60 deletions

View File

@@ -22,6 +22,7 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"strings"
"time"
"github.com/minio/kes"
@@ -69,15 +70,30 @@ func NewWithConfig(config Config) (KMS, error) {
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
})
client.Endpoints = endpoints
var bulkAvailable bool
_, policy, err := client.DescribeSelf(context.Background())
if err == nil {
const BulkAPI = "/v1/key/bulk/decrypt/"
for _, allow := range policy.Allow {
if strings.HasPrefix(allow, BulkAPI) {
bulkAvailable = true
break
}
}
}
return &kesClient{
client: client,
defaultKeyID: config.DefaultKeyID,
client: client,
defaultKeyID: config.DefaultKeyID,
bulkAvailable: bulkAvailable,
}, nil
}
type kesClient struct {
defaultKeyID string
client *kes.Client
bulkAvailable bool
}
var _ KMS = (*kesClient)(nil) // compiler check
@@ -145,3 +161,38 @@ func (c *kesClient) DecryptKey(keyID string, ciphertext []byte, ctx Context) ([]
}
return c.client.Decrypt(context.Background(), keyID, ciphertext, ctxBytes)
}
func (c *kesClient) DecryptAll(keyID string, ciphertexts [][]byte, contexts []Context) ([][]byte, error) {
if c.bulkAvailable {
CCPs := make([]kes.CCP, 0, len(ciphertexts))
for i := range ciphertexts {
bCtx, err := contexts[i].MarshalText()
if err != nil {
return nil, err
}
CCPs = append(CCPs, kes.CCP{
Ciphertext: ciphertexts[i],
Context: bCtx,
})
}
PCPs, err := c.client.DecryptAll(context.Background(), keyID, CCPs...)
if err != nil {
return nil, err
}
plaintexts := make([][]byte, 0, len(PCPs))
for _, p := range PCPs {
plaintexts = append(plaintexts, p.Plaintext)
}
return plaintexts, nil
}
plaintexts := make([][]byte, 0, len(ciphertexts))
for i := range ciphertexts {
plaintext, err := c.DecryptKey(keyID, ciphertexts[i], contexts[i])
if err != nil {
return nil, err
}
plaintexts = append(plaintexts, plaintext)
}
return plaintexts, nil
}

View File

@@ -20,8 +20,10 @@ package kms
import (
"encoding"
"encoding/json"
"strconv"
jsoniter "github.com/json-iterator/go"
"github.com/minio/pkg/env"
)
// KMS is the generic interface that abstracts over
@@ -51,6 +53,23 @@ type KMS interface {
// by the key ID. The context must match the context value
// used to generate the ciphertext.
DecryptKey(keyID string, ciphertext []byte, context Context) ([]byte, error)
// DecryptAll decrypts all ciphertexts with the key referenced
// by the key ID. The contexts must match the context value
// used to generate the ciphertexts.
DecryptAll(keyID string, ciphertext [][]byte, context []Context) ([][]byte, error)
}
// BatchSize returns the size of the batches that should be used during
// KES bulk decryption API calls.
func BatchSize() int {
const DefaultBatchSize = 500
v := env.Get("MINIO_KMS_KES_BULK_API_BATCH_SIZE", strconv.Itoa(DefaultBatchSize))
n, err := strconv.Atoi(v)
if err != nil {
return DefaultBatchSize
}
return n
}
// Status describes the current state of a KMS.

View File

@@ -224,6 +224,18 @@ func (kms secretKey) DecryptKey(keyID string, ciphertext []byte, context Context
return plaintext, nil
}
func (kms secretKey) DecryptAll(keyID string, ciphertexts [][]byte, contexts []Context) ([][]byte, error) {
plaintexts := make([][]byte, 0, len(ciphertexts))
for i := range ciphertexts {
plaintext, err := kms.DecryptKey(keyID, ciphertexts[i], contexts[i])
if err != nil {
return nil, err
}
plaintexts = append(plaintexts, plaintext)
}
return plaintexts, nil
}
type encryptedKey struct {
Algorithm string `json:"aead"`
IV []byte `json:"iv"`