kek

package
v0.0.0-...-0febee4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package kek implements KEK (Key Encryption Key) providers that wrap and unwrap DEKs (Data Encryption Keys) per §5.1 of the data-at-rest encryption design.

The KEK never appears in elastickv's data dir. It is held externally — in a KMS, a sealed file, or HashiCorp Vault — and only exercised at process boot and at DEK rotation.

Implementations include AWS KMS, GCP KMS, Vault Transit, a static file, and the test/CI-only environment provider described by the design.

Index

Constants

View Source
const EnvVar = "ELASTICKV_KEK_BASE64"

EnvVar is the test/CI-only environment variable accepted as a static KEK source. Production deployments should use a remote KMS provider.

Variables

View Source
var (
	// ErrInvalidDEKLength rejects non-AES-256 data keys at every provider
	// boundary, including malformed provider responses.
	ErrInvalidDEKLength = errors.New("kek: DEK must be exactly 32 bytes")
	// ErrInvalidProviderResponse rejects an empty or integrity-invalid response
	// before it can be persisted in the encryption sidecar.
	ErrInvalidProviderResponse = errors.New("kek: invalid provider response")
	// ErrKEKPreflightFailed prevents mutators from opening when the configured
	// provider cannot complete a real wrap/unwrap round trip.
	ErrKEKPreflightFailed = errors.New("kek: provider preflight failed")
)
View Source
var (
	// ErrMultipleKEKSources rejects ambiguous key configuration rather than
	// silently selecting one source by precedence.
	ErrMultipleKEKSources = errors.New("kek: configure exactly one of --kekFile, --kekUri, or ELASTICKV_KEK_BASE64")
	// ErrInvalidKEKURI rejects unknown providers and malformed provider targets.
	ErrInvalidKEKURI = errors.New("kek: invalid KEK URI")
)
View Source
var ErrInsecureKEKFile = errors.New("kek: file is group/world-accessible; require owner-only mode")

ErrInsecureKEKFile is returned by NewFileWrapper when the KEK file permission bits permit group or other access. Loading such a file would silently weaken the at-rest encryption boundary on a multi-user host (any local user could read the master key bytes), so the wrapper fails closed rather than warning. Owner-only modes (0o400 / 0o600) are accepted; anything with bits in 0o077 is not.

View Source
var ErrNilEnvWrapper = errors.New("kek: EnvWrapper is nil or uninitialised; construct with NewEnvWrapper")
View Source
var ErrNilFileWrapper = errors.New("kek: FileWrapper is nil or uninitialised; construct with NewFileWrapper")

ErrNilFileWrapper is returned by Wrap/Unwrap when called on a nil receiver or a zero-value FileWrapper (i.e. one whose internal AEAD was never initialised by NewFileWrapper). Surfaced as a typed error rather than a nil-deref panic so a wiring mistake during bootstrap or rotation surfaces as a recoverable failure instead of a process crash. Mirrors the encryption.Cipher / encryption.Keystore zero-value contract.

Functions

func VerifyWrapper

func VerifyWrapper(wrapper Wrapper) error

VerifyWrapper proves credentials, provider reachability, encrypt/decrypt permissions, and key binding before any encryption mutator can commit a wrapped DEK. Provider constructors alone only validate local configuration.

Types

type AWSKMSWrapper

type AWSKMSWrapper struct {
	// contains filtered or unexported fields
}

AWSKMSWrapper wraps DEKs using an AWS KMS symmetric ENCRYPT_DECRYPT key.

func NewAWSKMSWrapper

func NewAWSKMSWrapper(ctx context.Context, keyARN string) (*AWSKMSWrapper, error)

NewAWSKMSWrapper loads the standard AWS credential chain and derives the KMS endpoint region from keyARN.

func (*AWSKMSWrapper) Name

func (w *AWSKMSWrapper) Name() string

Name returns the provider and configured key ARN.

func (*AWSKMSWrapper) Unwrap

func (w *AWSKMSWrapper) Unwrap(wrapped []byte) ([]byte, error)

Unwrap calls AWS KMS Decrypt and rejects any non-32-byte plaintext response.

func (*AWSKMSWrapper) Wrap

func (w *AWSKMSWrapper) Wrap(dek []byte) ([]byte, error)

Wrap calls AWS KMS Encrypt with a fixed encryption context that is required again by Unwrap.

type EnvWrapper

type EnvWrapper struct {
	// contains filtered or unexported fields
}

EnvWrapper wraps DEKs with a process-local AES-256-GCM key loaded from EnvVar. NewEnvWrapper removes EnvVar immediately after decoding it so the raw KEK is not retained in the process environment.

func NewEnvWrapper

func NewEnvWrapper() (*EnvWrapper, error)

NewEnvWrapper reads a standard-base64 encoded 32-byte KEK. EnvVar is unset after the decode attempt on both success and failure paths.

func (*EnvWrapper) Name

func (*EnvWrapper) Name() string

Name identifies the environment-backed provider without exposing key bytes.

func (*EnvWrapper) Unwrap

func (w *EnvWrapper) Unwrap(wrapped []byte) ([]byte, error)

Unwrap authenticates and decrypts an EnvWrapper payload.

func (*EnvWrapper) Wrap

func (w *EnvWrapper) Wrap(dek []byte) ([]byte, error)

Wrap returns nonce || AES-GCM(KEK, DEK), matching FileWrapper's local provider format.

type FileWrapper

type FileWrapper struct {
	// contains filtered or unexported fields
}

FileWrapper wraps DEKs using AES-256-GCM under a KEK read from a file at construction time.

Suitable for tests, single-host clusters, and deployments that store the KEK on a sealed tmpfs volume. Production deployments should prefer a KMS-backed Wrapper (Stage 9: aws_kms.go, gcp_kms.go, vault.go); see §5.1 for the recommended provider ordering.

The zero value is NOT safe to use: Wrap/Unwrap return ErrNilFileWrapper for a nil pointer or a FileWrapper whose internal AEAD was never initialised. Always construct via NewFileWrapper.

func NewFileWrapper

func NewFileWrapper(path string) (*FileWrapper, error)

NewFileWrapper reads a KEK from path. The file must be a regular file containing exactly 32 bytes (an AES-256 key). Any other length returns an error rather than silently padding or truncating.

On unix, the file's permission bits MUST be owner-only (no group or other access bits set, i.e. mode & 0o077 == 0). A misconfigured 0o644 or 0o666 KEK file would let any local user read the master key on a multi-user host, defeating the entire at-rest encryption boundary — NewFileWrapper fails closed with ErrInsecureKEKFile rather than logging a warning. Windows has a fundamentally different permission model and is not gated.

The mode check and the key-bytes read share a single os.File so a path swap (or symlink retarget) between checks cannot race the load. f.Stat resolves the inode behind the open fd, not the path, so what is validated is exactly what is read.

The read is bounded to fileKEKSize+1 bytes and is preceded by a "regular file" stat check, so a misconfigured path pointing at a FIFO, device, or huge file (a misaligned cluster bootstrap could otherwise hang or OOM on /dev/zero) fails fast.

func (*FileWrapper) Name

func (w *FileWrapper) Name() string

Name returns the provider id plus the file path so log lines and the EncryptionAdmin status RPC let an operator distinguish multiple configured KEKs without grepping config.

func (*FileWrapper) Unwrap

func (w *FileWrapper) Unwrap(wrapped []byte) ([]byte, error)

Unwrap reverses Wrap. It returns ErrIntegrity-equivalent errors via the AEAD library (the parent encryption package's ErrIntegrity is the caller's responsibility to wrap, since this package must stay dependency-free of the parent).

The post-Open length check that earlier drafts had was unreachable — the strict-length input check above guarantees Open returns exactly fileKEKSize bytes on success — and has been removed.

Returns ErrNilFileWrapper for a nil receiver or zero-value FileWrapper, symmetric with Wrap.

func (*FileWrapper) Wrap

func (w *FileWrapper) Wrap(dek []byte) ([]byte, error)

Wrap returns AES-GCM(KEK, dek) prefixed by a freshly-drawn random nonce. Output layout:

[nonce 12 bytes] [ciphertext 32 bytes] [tag 16 bytes]

Total wrapped size: 60 bytes for a 32-byte DEK.

Returns ErrNilFileWrapper if w is nil or the embedded AEAD was never initialised by NewFileWrapper. A wiring/configuration mistake during bootstrap or rotation surfaces as a typed error rather than a nil-deref panic.

type GCPKMSWrapper

type GCPKMSWrapper struct {
	// contains filtered or unexported fields
}

GCPKMSWrapper wraps DEKs using a Google Cloud KMS symmetric CryptoKey.

func NewGCPKMSWrapper

func NewGCPKMSWrapper(ctx context.Context, keyName string) (*GCPKMSWrapper, error)

NewGCPKMSWrapper uses Application Default Credentials to construct a Cloud KMS client for keyName.

func (*GCPKMSWrapper) Name

func (w *GCPKMSWrapper) Name() string

Name returns the provider and configured CryptoKey resource name.

func (*GCPKMSWrapper) Unwrap

func (w *GCPKMSWrapper) Unwrap(wrapped []byte) ([]byte, error)

Unwrap calls Cloud KMS Decrypt and verifies the plaintext CRC32C before accepting the 32-byte DEK.

func (*GCPKMSWrapper) Wrap

func (w *GCPKMSWrapper) Wrap(dek []byte) ([]byte, error)

Wrap calls Cloud KMS Encrypt with fixed AAD and verifies request/response CRC32C integrity metadata.

type VaultTransitWrapper

type VaultTransitWrapper struct {
	// contains filtered or unexported fields
}

VaultTransitWrapper wraps DEKs with a Vault Transit symmetric key.

func NewVaultTransitWrapper

func NewVaultTransitWrapper(target string) (*VaultTransitWrapper, error)

NewVaultTransitWrapper uses the standard VAULT_ADDR, VAULT_TOKEN, TLS, and namespace environment configuration. target is <mount>/<key-name>.

func (*VaultTransitWrapper) Name

func (w *VaultTransitWrapper) Name() string

Name returns the provider and Transit mount/key path.

func (*VaultTransitWrapper) Unwrap

func (w *VaultTransitWrapper) Unwrap(wrapped []byte) ([]byte, error)

Unwrap asks Vault Transit to decrypt its versioned ciphertext and validates the returned base64 plaintext as a 32-byte DEK.

func (*VaultTransitWrapper) Wrap

func (w *VaultTransitWrapper) Wrap(dek []byte) ([]byte, error)

Wrap base64-encodes the binary DEK for Vault's JSON API and stores Vault's versioned ciphertext string as the wrapped sidecar bytes.

type Wrapper

type Wrapper interface {
	Wrap(dek []byte) ([]byte, error)
	Unwrap(wrapped []byte) ([]byte, error)

	// Name returns a short identifier of the KEK source ("file",
	// "aws-kms", "gcp-kms", "vault", "env"). Surfaces in logs and the
	// EncryptionAdmin status RPC.
	Name() string
}

Wrapper wraps and unwraps DEK bytes under an externally-held KEK.

Wrap input is always exactly encryption.KeySize (32) bytes; the wrapped output's exact size depends on the provider but is at least the input size plus an AEAD nonce and tag (or KMS protocol overhead).

Implementations MUST be safe for concurrent use by multiple goroutines because the encryption Keystore may issue Wrap/Unwrap from rotation and resync paths simultaneously.

func NewWrapperFromSources

func NewWrapperFromSources(ctx context.Context, filePath, uri string) (Wrapper, error)

NewWrapperFromSources resolves exactly one file, URI, or environment-backed KEK. No configured source returns nil so encryption-disabled deployments keep their existing startup behavior.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL