gcpkms

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package gcpkms is a secrets.Cipher backed by Cloud KMS's raw Encrypt and Decrypt: the key material never leaves the service, and the platform stores only ciphertext (docs/plan/20_gcp-deployment.md, Decision 3). It is what lets a GCP deployment drop the bundled OpenBao — KMS needs a key resource name, which is not a secret, plus Workload Identity, so there is no static token to distribute or rotate.

Two properties are worth reading before choosing this backend.

A ceiling

KMS's raw Encrypt bounds plaintext at MaxPlaintextBytes; OpenBao's transit engine has no such bound. So this backend cannot serve every input the vault credential API accepts, and that is handled as the real difference between two ciphers behind one interface that it is: the refusal is secrets.ErrPlaintextTooLarge rather than a raw InvalidArgument from the KMS client, the API classifies it as the caller's error and answers with a 4xx naming the limit, and docs/DIVERGENCES.md records it. Envelope encryption removes the ceiling entirely and is deferred to #242.

A format marker

Ciphertext is "gcpkms:v1:" followed by exactly what KMS returned. The version is what makes #242 introducible later without rewriting stored rows: an envelope format becomes v2, v1 rows keep decrypting, and a v2 ciphertext reaching an older build is refused by name instead of being handed to KMS as if it were raw. That marker is the one piece of the escape hatch that is cheap now and expensive retrofitted, which is why it ships in the first commit rather than with the feature that needs it.

Index

Constants

View Source
const (
	// MaxPlaintextBytes is the ceiling for a SOFTWARE, EXTERNAL or EXTERNAL_VPC
	// key version. Measured against the real service (plan 20's ground truth,
	// 2026-08-01): 65536 accepted, 65537 refused with `max_expected_size:65536`.
	MaxPlaintextBytes = 65536

	// MaxPlaintextBytesHSM is the ceiling for an HSM key version, where the
	// service bounds plaintext *plus* additional authenticated data at 8 KiB.
	// This backend sends no AAD, so the whole budget is plaintext.
	MaxPlaintextBytesHSM = 8192
)

The two raw-Encrypt ceilings Cloud KMS applies, and the reason the limit is not a constant: which one binds is a property of the KEY, not of this code.

Getting that wrong is not cosmetic. A plaintext between the two bounds on an HSM key would pass a 64 KiB guard, be refused by the service as a bare InvalidArgument, and reach the caller as exactly the generic 500 this backend's size classification exists to prevent — so the ceiling is read from the key at startup (see New) rather than assumed.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cipher

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

Cipher is the KMS-backed implementation of secrets.Cipher.

func New

func New(ctx context.Context, cfg Config) (*Cipher, error)

New validates the key name eagerly and proves the key is usable before returning, so a typo or a missing IAM binding fails at startup rather than on the first credential write (openbao.New's key-ensure, for the same reason).

The proof is a throwaway Encrypt, deliberately. GetCryptoKey would be the obvious probe and needs cloudkms.cryptoKeys.get, which roles/cloudkms.cryptoKeyEncrypterDecrypter — the only role this backend otherwise requires — does not carry. Probing with the privilege the runtime path already needs keeps the deploy guide's IAM grant at exactly encrypt/decrypt (plan 20, Decision 11's least-privilege discipline).

func (*Cipher) Decrypt

func (c *Cipher) Decrypt(ctx context.Context, ciphertext []byte, keyID string) ([]byte, error)

Decrypt reverses Encrypt. A keyID other than the configured key name is refused locally — this cipher holds exactly one key.

func (*Cipher) Encrypt

func (c *Cipher) Encrypt(ctx context.Context, plaintext []byte) ([]byte, string, error)

Encrypt seals plaintext under the configured CryptoKey. The keyID is that key's resource name.

func (*Cipher) MaxPlaintext

func (c *Cipher) MaxPlaintext() int

MaxPlaintext reports the plaintext ceiling this cipher's key enforces — MaxPlaintextBytes, or MaxPlaintextBytesHSM for an HSM-protected key and for one whose probe did not report a protection level at all.

type Config

type Config struct {
	// KeyName is the full CryptoKey resource name
	// (projects/P/locations/L/keyRings/R/cryptoKeys/K). It doubles as the
	// keyID stored beside each ciphertext.
	KeyName string

	// Client overrides the ADC-authenticated client New would build. Tests
	// point it at an in-process fake; the caller owns closing it.
	Client *kms.KeyManagementClient
}

Config names the key. Authentication is Application Default Credentials — Workload Identity on GKE — so no credential appears here.

Directories

Path Synopsis
Package gcpkmstest is test support for the gcpkms cipher: an in-process fake Cloud KMS gRPC server, so the shared secrets contract runs hermetically and `make test` never touches GCP or spends money, plus the opt-in gate for the live tier that calls the real service.
Package gcpkmstest is test support for the gcpkms cipher: an in-process fake Cloud KMS gRPC server, so the shared secrets contract runs hermetically and `make test` never touches GCP or spends money, plus the opt-in gate for the live tier that calls the real service.

Jump to

Keyboard shortcuts

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