feat(s3): support WEED_S3_SSE_KEY env var for SSE-S3 KEK (#8904)
* feat(s3): support WEED_S3_SSE_KEY env var for SSE-S3 KEK Add support for providing the SSE-S3 Key Encryption Key (KEK) via the WEED_S3_SSE_KEY environment variable (hex-encoded 256-bit key). This avoids storing the master key in plaintext on the filer at /etc/s3/sse_kek. Key source priority: 1. WEED_S3_SSE_KEY environment variable (recommended) 2. Existing filer KEK at /etc/s3/sse_kek (backward compatible) 3. Auto-generate and save to filer (deprecated for new deployments) Existing deployments with a filer-stored KEK continue to work unchanged. A deprecation warning is logged when auto-generating a new filer KEK. * refactor(s3): derive KEK from any string via HKDF instead of requiring hex Accept any secret string in WEED_S3_SSE_KEY and derive a 256-bit key using HKDF-SHA256 instead of requiring a hex-encoded key. This is simpler for users — no need to generate hex, just set a passphrase. * feat(s3): add WEED_S3_SSE_KEK and WEED_S3_SSE_KEY env vars for KEK Two env vars for providing the SSE-S3 Key Encryption Key: - WEED_S3_SSE_KEK: hex-encoded, same format as /etc/s3/sse_kek. If the filer file also exists, they must match. - WEED_S3_SSE_KEY: any string, 256-bit key derived via HKDF-SHA256. Refuses to start if /etc/s3/sse_kek exists (must delete first). Only one may be set. Existing filer-stored KEKs continue to work. Auto-generating and storing new KEKs on filer is deprecated. * fix(s3): stop auto-generating KEK, fail only when SSE-S3 is used Instead of auto-generating a KEK and storing it on the filer when no key source is configured, simply leave SSE-S3 disabled. Encrypt and decrypt operations return a clear error directing the user to set WEED_S3_SSE_KEK or WEED_S3_SSE_KEY. * refactor(s3): move SSE-S3 KEK config to security.toml Move KEK configuration from standalone env vars to security.toml's new [sse_s3] section, following the same pattern as JWT keys and TLS certs. [sse_s3] kek = "" # hex-encoded 256-bit key (same format as /etc/s3/sse_kek) key = "" # any string, HKDF-derived Viper's WEED_ prefix auto-mapping provides env var support: WEED_SSE_S3_KEK and WEED_SSE_S3_KEY. All existing behavior is preserved: filer KEK fallback, mismatch detection, and HKDF derivation. * refactor(s3): rename SSE-S3 config keys to s3.sse.kek / s3.sse.key Use [s3.sse] section in security.toml, matching the existing naming convention (e.g. [s3.*]). Env vars: WEED_S3_SSE_KEK, WEED_S3_SSE_KEY. * fix(s3): address code review findings for SSE-S3 KEK - Don't hold mutex during filer retry loop (up to 20s of sleep). Lock only to write filerClient and superKey. - Remove dead generateAndSaveSuperKeyToFiler and unused constants. - Return error from deriveKeyFromSecret instead of ignoring it. - Fix outdated doc comment on InitializeWithFiler. - Use t.Setenv in tests instead of manual os.Setenv/Unsetenv. * fix(s3): don't block startup on filer errors when KEK is configured - When s3.sse.kek is set, a temporarily unreachable filer no longer prevents startup. The filer consistency check becomes best-effort with a warning. - Same treatment for s3.sse.key: filer unreachable logs a warning instead of failing. - Rewrite error messages to suggest migration instead of file deletion, avoiding the risk of orphaning encrypted data. Finding 3 (restore auto-generation) intentionally skipped — auto-gen was removed by design to avoid storing plaintext KEK on filer. * fix(test): set WEED_S3_SSE_KEY in SSE integration test server startup SSE-S3 no longer auto-generates a KEK, so integration tests must provide one. Set WEED_S3_SSE_KEY=test-sse-s3-key in all weed mini invocations in the test Makefile.
This commit is contained in:
@@ -179,6 +179,19 @@ password = ""
|
||||
user = ""
|
||||
password = ""
|
||||
|
||||
# SSE-S3 server-side encryption key management
|
||||
# These settings configure the Key Encryption Key (KEK) for S3 SSE-S3 encryption.
|
||||
# Set exactly one of kek or key. If neither is set, SSE-S3 is disabled.
|
||||
# Can also be set via env vars: WEED_S3_SSE_KEK, WEED_S3_SSE_KEY
|
||||
[s3.sse]
|
||||
# hex-encoded 256-bit key, same format as the legacy /etc/s3/sse_kek filer file.
|
||||
# Use this to migrate from a filer-stored KEK: copy the value from /etc/s3/sse_kek.
|
||||
# Generate a new one with: openssl rand -hex 32
|
||||
kek = ""
|
||||
# any secret string; a 256-bit key is derived automatically via HKDF-SHA256.
|
||||
# Cannot be used while /etc/s3/sse_kek exists on the filer — delete it first.
|
||||
key = ""
|
||||
|
||||
# white list. It's checking request ip address.
|
||||
[guard]
|
||||
white_list = ""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"io"
|
||||
mathrand "math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -258,14 +258,20 @@ type SSES3KeyManager struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// KEK storage directory and file name in filer
|
||||
SSES3KEKDirectory = "/etc/s3"
|
||||
SSES3KEKParentDir = "/etc"
|
||||
SSES3KEKDirName = "s3"
|
||||
SSES3KEKFileName = "sse_kek"
|
||||
// Legacy KEK path on the filer (backward compatibility)
|
||||
defaultKEKPath = "/etc/s3/sse_kek"
|
||||
|
||||
// Full KEK path in filer
|
||||
defaultKEKPath = SSES3KEKDirectory + "/" + SSES3KEKFileName
|
||||
// security.toml keys (also settable via env vars WEED_S3_SSE_KEK / WEED_S3_SSE_KEY):
|
||||
//
|
||||
// s3.sse.kek: hex-encoded 256-bit key, same format as /etc/s3/sse_kek.
|
||||
// Drop-in replacement for the filer-stored KEK. If /etc/s3/sse_kek also
|
||||
// exists, the values must match or the server refuses to start.
|
||||
//
|
||||
// s3.sse.key: any secret string; a 256-bit key is derived via HKDF-SHA256.
|
||||
// Cannot be used while /etc/s3/sse_kek exists — the filer file must be
|
||||
// deleted first (to avoid silently orphaning old data).
|
||||
sseS3KEKConfigKey = "s3.sse.kek"
|
||||
sseS3KeyConfigKey = "s3.sse.key"
|
||||
)
|
||||
|
||||
// NewSSES3KeyManager creates a new SSE-S3 key manager with envelope encryption
|
||||
@@ -276,34 +282,145 @@ func NewSSES3KeyManager() *SSES3KeyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeWithFiler initializes the key manager with a filer client
|
||||
func (km *SSES3KeyManager) InitializeWithFiler(filerClient filer_pb.FilerClient) error {
|
||||
km.mu.Lock()
|
||||
defer km.mu.Unlock()
|
||||
// deriveKeyFromSecret derives a 256-bit key from an arbitrary secret string
|
||||
// using HKDF-SHA256. The derivation is deterministic: the same secret always
|
||||
// produces the same key.
|
||||
func deriveKeyFromSecret(secret string) ([]byte, error) {
|
||||
hkdfReader := hkdf.New(sha256.New, []byte(secret), nil, []byte("seaweedfs-sse-s3-kek"))
|
||||
key := make([]byte, SSES3KeySize)
|
||||
if _, err := io.ReadFull(hkdfReader, key); err != nil {
|
||||
return nil, fmt.Errorf("failed to derive key: %w", err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
km.filerClient = filerClient
|
||||
|
||||
// Try to load existing KEK from filer with retries to handle transient connectivity issues during startup
|
||||
var err error
|
||||
// loadFilerKEK tries to load the KEK from /etc/s3/sse_kek on the filer.
|
||||
// Returns the key bytes on success, nil if the file does not exist or filer
|
||||
// is not configured, or an error on transient failures (retries internally).
|
||||
func (km *SSES3KeyManager) loadFilerKEK() ([]byte, error) {
|
||||
if km.filerClient == nil {
|
||||
return nil, nil // no filer configured
|
||||
}
|
||||
var lastErr error
|
||||
for i := 0; i < 10; i++ {
|
||||
err = km.loadSuperKeyFromFiler()
|
||||
err := km.loadSuperKeyFromFiler()
|
||||
if err == nil {
|
||||
glog.V(1).Infof("SSE-S3 KeyManager: Loaded KEK from filer %s", km.kekPath)
|
||||
return nil
|
||||
// loadSuperKeyFromFiler sets km.superKey; grab a copy
|
||||
key := make([]byte, len(km.superKey))
|
||||
copy(key, km.superKey)
|
||||
km.superKey = nil // will be set by caller
|
||||
return key, nil
|
||||
}
|
||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||
glog.V(1).Infof("SSE-S3 KeyManager: KEK not found, generating new KEK (load from filer %s: %v)", km.kekPath, err)
|
||||
if genErr := km.generateAndSaveSuperKeyToFiler(); genErr != nil {
|
||||
return fmt.Errorf("failed to generate and save SSE-S3 super key: %w", genErr)
|
||||
}
|
||||
return nil
|
||||
return nil, nil // file does not exist
|
||||
}
|
||||
lastErr = err
|
||||
glog.Warningf("SSE-S3 KeyManager: failed to load KEK (attempt %d/10): %v", i+1, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to load KEK from %s after 10 attempts: %w", km.kekPath, lastErr)
|
||||
}
|
||||
|
||||
// If we're here, all retries failed
|
||||
return fmt.Errorf("failed to load SSE-S3 super key from %s after 10 attempts: %w", km.kekPath, err)
|
||||
// InitializeWithFiler initializes the key manager with a filer client.
|
||||
//
|
||||
// Key source priority (via security.toml or WEED_ env vars):
|
||||
// 1. s3.sse.kek (env: WEED_S3_SSE_KEK) — hex-encoded, same format as /etc/s3/sse_kek.
|
||||
// If the filer file also exists, they must match.
|
||||
// 2. s3.sse.key (env: WEED_S3_SSE_KEY) — any string; 256-bit key derived via HKDF.
|
||||
// Refused if /etc/s3/sse_kek exists — delete the filer file first.
|
||||
// 3. Existing /etc/s3/sse_kek on the filer (backward compat).
|
||||
// 4. SSE-S3 disabled (fail on first encrypt/decrypt attempt).
|
||||
func (km *SSES3KeyManager) InitializeWithFiler(filerClient filer_pb.FilerClient) error {
|
||||
// Set filerClient under lock, then release — the rest may do slow I/O
|
||||
// (filer retries with sleep) and must not block encrypt/decrypt callers.
|
||||
km.mu.Lock()
|
||||
km.filerClient = filerClient
|
||||
km.mu.Unlock()
|
||||
|
||||
v := util.GetViper()
|
||||
cfgKEK := v.GetString(sseS3KEKConfigKey) // hex-encoded, drop-in for filer file
|
||||
cfgKey := v.GetString(sseS3KeyConfigKey) // any string, HKDF-derived
|
||||
|
||||
if cfgKEK != "" && cfgKey != "" {
|
||||
return fmt.Errorf("only one of %s and %s may be set, not both", sseS3KEKConfigKey, sseS3KeyConfigKey)
|
||||
}
|
||||
|
||||
var resolvedKey []byte
|
||||
|
||||
switch {
|
||||
// --- Case 1: s3.sse.kek (hex, same format as filer file) ---
|
||||
case cfgKEK != "":
|
||||
key, err := hex.DecodeString(cfgKEK)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s: must be hex-encoded: %w", sseS3KEKConfigKey, err)
|
||||
}
|
||||
if len(key) != SSES3KeySize {
|
||||
return fmt.Errorf("invalid %s: must be %d bytes (%d hex chars), got %d bytes",
|
||||
sseS3KEKConfigKey, SSES3KeySize, SSES3KeySize*2, len(key))
|
||||
}
|
||||
|
||||
// Best-effort consistency check: if the filer file exists, warn on
|
||||
// mismatch. A temporarily unreachable filer must not block startup
|
||||
// when the operator has explicitly provided a KEK.
|
||||
filerKey, err := km.loadFilerKEK()
|
||||
if err != nil {
|
||||
glog.Warningf("SSE-S3 KeyManager: could not reach filer to verify %s against %s: %v (proceeding with configured KEK)",
|
||||
sseS3KEKConfigKey, km.kekPath, err)
|
||||
} else if filerKey != nil && !bytes.Equal(filerKey, key) {
|
||||
return fmt.Errorf("%s does not match existing %s — "+
|
||||
"use the same key value as the filer file, or migrate existing data to the new key. "+
|
||||
"See the Server-Side-Encryption wiki for migration steps",
|
||||
sseS3KEKConfigKey, km.kekPath)
|
||||
}
|
||||
|
||||
resolvedKey = key
|
||||
glog.V(0).Infof("SSE-S3 KeyManager: Loaded KEK from %s config", sseS3KEKConfigKey)
|
||||
|
||||
// --- Case 2: s3.sse.key (any string, HKDF-derived) ---
|
||||
case cfgKey != "":
|
||||
// If the filer still has a legacy KEK file, the operator must migrate
|
||||
// existing data first — using a derived key would silently orphan
|
||||
// objects encrypted with the old KEK.
|
||||
filerKey, err := km.loadFilerKEK()
|
||||
if err != nil {
|
||||
glog.Warningf("SSE-S3 KeyManager: could not reach filer to check for legacy %s: %v (proceeding with configured key)",
|
||||
km.kekPath, err)
|
||||
} else if filerKey != nil {
|
||||
return fmt.Errorf("%s cannot be used while %s exists on the filer — "+
|
||||
"existing objects are encrypted with the filer KEK. "+
|
||||
"Migrate to %s first (copy the filer KEK value) or follow the key-rotation steps in the Server-Side-Encryption wiki",
|
||||
sseS3KeyConfigKey, km.kekPath, sseS3KEKConfigKey)
|
||||
}
|
||||
|
||||
derived, err := deriveKeyFromSecret(cfgKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resolvedKey = derived
|
||||
glog.V(0).Infof("SSE-S3 KeyManager: Derived KEK from %s config", sseS3KeyConfigKey)
|
||||
|
||||
// --- Case 3: Load existing filer KEK (backward compatibility) ---
|
||||
default:
|
||||
filerKey, err := km.loadFilerKEK()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if filerKey != nil {
|
||||
resolvedKey = filerKey
|
||||
glog.V(1).Infof("SSE-S3 KeyManager: Loaded KEK from filer %s", km.kekPath)
|
||||
glog.V(0).Infof("SSE-S3 KeyManager: Consider setting %s in security.toml instead of storing KEK on filer", sseS3KEKConfigKey)
|
||||
} else {
|
||||
// --- Case 4: Nothing configured — SSE-S3 disabled ---
|
||||
glog.V(0).Infof("SSE-S3 KeyManager: No KEK configured. SSE-S3 encryption is disabled. "+
|
||||
"Set %s or %s in security.toml to enable it.", sseS3KEKConfigKey, sseS3KeyConfigKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Only hold the lock to write the final state.
|
||||
km.mu.Lock()
|
||||
km.superKey = resolvedKey
|
||||
km.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadSuperKeyFromFiler loads the KEK from the filer
|
||||
@@ -337,47 +454,6 @@ func (km *SSES3KeyManager) loadSuperKeyFromFiler() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateAndSaveSuperKeyToFiler generates a new KEK and saves it to the filer
|
||||
func (km *SSES3KeyManager) generateAndSaveSuperKeyToFiler() error {
|
||||
if km.filerClient == nil {
|
||||
return fmt.Errorf("filer client not initialized")
|
||||
}
|
||||
|
||||
// Generate a random 256-bit super key (KEK)
|
||||
superKey := make([]byte, SSES3KeySize)
|
||||
if _, err := io.ReadFull(rand.Reader, superKey); err != nil {
|
||||
return fmt.Errorf("failed to generate KEK: %w", err)
|
||||
}
|
||||
|
||||
// Encode as hex for storage
|
||||
encodedKey := []byte(hex.EncodeToString(superKey))
|
||||
|
||||
// Create the entry in filer
|
||||
// First ensure the parent directory exists
|
||||
if err := filer_pb.Mkdir(context.Background(), km.filerClient, SSES3KEKParentDir, SSES3KEKDirName, func(entry *filer_pb.Entry) {
|
||||
// Set appropriate permissions for the directory
|
||||
entry.Attributes.FileMode = uint32(0700 | os.ModeDir)
|
||||
}); err != nil {
|
||||
// Only ignore "already exists" errors.
|
||||
if !errors.Is(err, filer_pb.ErrEntryAlreadyExists) {
|
||||
return fmt.Errorf("failed to create KEK directory %s: %w", SSES3KEKDirectory, err)
|
||||
}
|
||||
glog.V(3).Infof("Parent directory %s already exists, continuing.", SSES3KEKDirectory)
|
||||
}
|
||||
|
||||
// Create the KEK file
|
||||
if err := filer_pb.MkFile(context.Background(), km.filerClient, SSES3KEKDirectory, SSES3KEKFileName, nil, func(entry *filer_pb.Entry) {
|
||||
entry.Content = encodedKey
|
||||
entry.Attributes.FileMode = 0600 // Read/write for owner only
|
||||
entry.Attributes.FileSize = uint64(len(encodedKey))
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to create KEK file in filer: %w", err)
|
||||
}
|
||||
|
||||
km.superKey = superKey
|
||||
glog.Infof("SSE-S3 KeyManager: Generated and saved new KEK to filer %s", km.kekPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrCreateKey gets an existing key or creates a new one
|
||||
// With envelope encryption, we always generate a new DEK since we don't store them
|
||||
@@ -391,6 +467,10 @@ func (km *SSES3KeyManager) encryptKeyWithSuperKey(dek []byte) ([]byte, []byte, e
|
||||
km.mu.RLock()
|
||||
defer km.mu.RUnlock()
|
||||
|
||||
if len(km.superKey) == 0 {
|
||||
return nil, nil, fmt.Errorf("SSE-S3 encryption is not configured — set %s or %s in security.toml", sseS3KEKConfigKey, sseS3KeyConfigKey)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(km.superKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create cipher: %w", err)
|
||||
@@ -418,6 +498,10 @@ func (km *SSES3KeyManager) decryptKeyWithSuperKey(encryptedDEK, nonce []byte) ([
|
||||
km.mu.RLock()
|
||||
defer km.mu.RUnlock()
|
||||
|
||||
if len(km.superKey) == 0 {
|
||||
return nil, fmt.Errorf("SSE-S3 decryption is not configured — set %s or %s in security.toml", sseS3KEKConfigKey, sseS3KeyConfigKey)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(km.superKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cipher: %w", err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package s3api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
)
|
||||
|
||||
// TestSSES3EncryptionDecryption tests basic SSE-S3 encryption and decryption
|
||||
@@ -681,6 +683,125 @@ func TestSSES3InvalidMetadataDeserialization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// setViperKey is a test helper that sets a config key via its WEED_ env var.
|
||||
func setViperKey(t *testing.T, key, value string) {
|
||||
t.Helper()
|
||||
util.GetViper().SetDefault(key, "")
|
||||
t.Setenv("WEED_"+strings.ReplaceAll(strings.ToUpper(key), ".", "_"), value)
|
||||
}
|
||||
|
||||
// TestSSES3KEKConfig tests that sse_s3.kek (hex format) is used as KEK
|
||||
func TestSSES3KEKConfig(t *testing.T) {
|
||||
testKey := make([]byte, 32)
|
||||
for i := range testKey {
|
||||
testKey[i] = byte(i + 50)
|
||||
}
|
||||
setViperKey(t, sseS3KEKConfigKey, hex.EncodeToString(testKey))
|
||||
|
||||
km := NewSSES3KeyManager()
|
||||
err := km.InitializeWithFiler(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("InitializeWithFiler failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(km.superKey, testKey) {
|
||||
t.Errorf("superKey mismatch: expected %x, got %x", testKey, km.superKey)
|
||||
}
|
||||
|
||||
// Round-trip DEK encryption
|
||||
dek := make([]byte, 32)
|
||||
for i := range dek {
|
||||
dek[i] = byte(i)
|
||||
}
|
||||
encrypted, nonce, err := km.encryptKeyWithSuperKey(dek)
|
||||
if err != nil {
|
||||
t.Fatalf("encryptKeyWithSuperKey failed: %v", err)
|
||||
}
|
||||
decrypted, err := km.decryptKeyWithSuperKey(encrypted, nonce)
|
||||
if err != nil {
|
||||
t.Fatalf("decryptKeyWithSuperKey failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(decrypted, dek) {
|
||||
t.Error("round-trip DEK mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSES3KEKConfigInvalidHex tests rejection of bad hex
|
||||
func TestSSES3KEKConfigInvalidHex(t *testing.T) {
|
||||
setViperKey(t, sseS3KEKConfigKey, "not-valid-hex")
|
||||
|
||||
km := NewSSES3KeyManager()
|
||||
err := km.InitializeWithFiler(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid hex, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "hex-encoded") {
|
||||
t.Errorf("expected hex error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSES3KEKConfigWrongSize tests rejection of wrong-size hex key
|
||||
func TestSSES3KEKConfigWrongSize(t *testing.T) {
|
||||
setViperKey(t, sseS3KEKConfigKey, hex.EncodeToString(make([]byte, 16)))
|
||||
|
||||
km := NewSSES3KeyManager()
|
||||
err := km.InitializeWithFiler(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for wrong key size, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "32 bytes") {
|
||||
t.Errorf("expected size error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSES3KeyConfig tests that sse_s3.key (any string, HKDF) works
|
||||
func TestSSES3KeyConfig(t *testing.T) {
|
||||
setViperKey(t, sseS3KeyConfigKey, "my-secret-passphrase")
|
||||
|
||||
km := NewSSES3KeyManager()
|
||||
err := km.InitializeWithFiler(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("InitializeWithFiler failed: %v", err)
|
||||
}
|
||||
|
||||
if len(km.superKey) != SSES3KeySize {
|
||||
t.Fatalf("expected %d-byte superKey, got %d", SSES3KeySize, len(km.superKey))
|
||||
}
|
||||
|
||||
// Deterministic: same input → same output
|
||||
expected, err := deriveKeyFromSecret("my-secret-passphrase")
|
||||
if err != nil {
|
||||
t.Fatalf("deriveKeyFromSecret failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(km.superKey, expected) {
|
||||
t.Errorf("superKey mismatch: expected %x, got %x", expected, km.superKey)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSES3KeyConfigDifferentSecrets tests different strings produce different keys
|
||||
func TestSSES3KeyConfigDifferentSecrets(t *testing.T) {
|
||||
k1, _ := deriveKeyFromSecret("secret-one")
|
||||
k2, _ := deriveKeyFromSecret("secret-two")
|
||||
if bytes.Equal(k1, k2) {
|
||||
t.Error("different secrets should produce different keys")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSSES3BothConfigsReject tests that setting both config keys is rejected
|
||||
func TestSSES3BothConfigsReject(t *testing.T) {
|
||||
setViperKey(t, sseS3KEKConfigKey, hex.EncodeToString(make([]byte, 32)))
|
||||
setViperKey(t, sseS3KeyConfigKey, "some-passphrase")
|
||||
|
||||
km := NewSSES3KeyManager()
|
||||
err := km.InitializeWithFiler(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when both configs set, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "only one") {
|
||||
t.Errorf("expected 'only one' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSSES3Headers tests SSE-S3 header generation
|
||||
func TestGetSSES3Headers(t *testing.T) {
|
||||
headers := GetSSES3Headers()
|
||||
|
||||
Reference in New Issue
Block a user