kmsclient

package
v0.1.11 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 32 Imported by: 0

README

kmsclient — Go SDK for the KMS parameter store

kmsclient is the Go client for the KMS parameter-store and secret-management service. It hides gRPC boilerplate behind a small, safe surface: simple reads, declarative store-backed config fields, and hot reload of parameters — with secret plaintext that never leaks into logs, errors, or string/JSON output.

import "github.com/Suhaibinator/kms/sdk/go/kmsclient"

Connect

Follow the production mTLS onboarding runbook to create the application's namespace and identity and deliver its client cert/key plus the operator's server CA bundle.

client, err := kmsclient.NewClient(kmsclient.Config{
    Endpoint:  "parameter-store.prod.internal:8443",
    TLS:       kmsclient.MTLSFromFiles("client.crt", "client.key", "server-ca.crt"),
    CacheTTL:  time.Minute,                                         // optional in-memory read cache
})
if err != nil {
    return err
}
defer client.Close()

The preferred posture is a client certificate (proof of possession, minted by the KMS CA): identity derives from the cert server-side, so Token is optional. Token is only required for token-method identities. server-ca.crt must trust the operator-provided server certificate; it is not the built-in client CA shown by admin ca show. A namespace-bound identity discovers its home namespace through WhoAmI; Config.Namespace remains available when an explicit namespace is preferable.

Transport security must be explicit: without TLS, NewClient fails instead of silently sending credentials and secret plaintext over cleartext. A local development server can be reached with Insecure: true; do not use that option across an untrusted network. Low-level callers may instead provide explicit transport credentials through DialOptions.

Namespaces and keys

A client operates in one namespace — a fixed (env, app) pair like prod/gradethis. Keys are relative to it (rate-limit, billing/stripe-key); a key's interior slashes are part of the name, never namespace structure.

  • Set Config.Namespace explicitly, or leave it empty to discover it from the identity at first use (one WhoAmI call, cached for the client's lifetime). A relative key on an unbound identity fails with ErrNoNamespace.
  • A leading-slash key is an absolute /env/app/key display path, split in the SDK to reach another namespace:
rate, err := client.GetParameter(ctx, "rate-limit")              // relative to prod/gradethis
other, err := client.GetParameter(ctx, "/staging/billing/rate")  // absolute, cross-namespace

Read parameters and secrets

rate, err := client.GetParameter(ctx, "rate-limit")

pw, err := client.GetSecret(ctx, "postgres/password")
db.Connect(pw.Value()) // []byte plaintext; pw itself prints "[REDACTED]"

Read options:

client.GetParameter(ctx, key, kmsclient.WithVersion(3))
client.GetSecret(ctx, key, kmsclient.WithLabel("previous"))
client.GetSecret(ctx, key, kmsclient.WithSecretToken(tok)) // token-protected / client-bound

Redaction

Secret, SecretValue, and anything containing them redact in every common sink — fmt (%v, %s, %+v, %#v, %q), Stringer, and json.Marshal:

log.Printf("secret=%v", pw)          // secret=[REDACTED]
json.Marshal(cfg)                    // {"DBPassword":"[REDACTED]", ...}

Plaintext is only reachable through explicit accessors: Secret.Value(), Secret.StringValue(), SecretValue.Value().

Declarative config (drop-in pattern)

Declare store-backed fields and resolve the whole struct in one call. Resolution order per field: env override → store fetch → Default → error naming the key.

Default is a dev-only escape hatch: it is used only when the store affirmatively reports the value absent (ErrNotFound). Any other fetch error — unavailable, timeout, unauthenticated, permission denied — fails Init/Resolve even when a Default is set, so a process can never silently boot on a dev default because the store was briefly unreachable. Set Config.FallbackToDefaultsOnError: true to opt into any-error → Default.

type Config struct {
    DBPassword kmsclient.SecretValue
    StripeKey  kmsclient.SecretValue
    RateLimit  kmsclient.ParameterValue
    Payments   struct { // nested structs are walked too
        Timeout kmsclient.ParameterValue
    }
}

cfg := Config{
    DBPassword: kmsclient.SecretValue{Key: "postgres/password"},
    StripeKey:  kmsclient.SecretValue{Key: "stripe/api-key", EnvVar: "STRIPE_KEY"},
    RateLimit:  kmsclient.ParameterValue{Key: "rate-limit"}, // hot-reloads by default
}
cfg.Payments.Timeout = kmsclient.ParameterValue{Key: "timeout", Default: "30s", Static: true}

if err := client.Resolve(ctx, &cfg); err != nil { // batches fetches concurrently
    return err
}

pw := cfg.DBPassword.Value()   // plaintext
rl := cfg.RateLimit.Get()      // latest value

Prefer explicit initialization? Every value type also has Init(client):

if err := cfg.DBPassword.Init(client); err != nil { return err }

Init is idempotent. Env-overridden values are pinned and never hot-reload.

Hot reload

Parameters hot-reload by default. A ParameterValue tracks the store over a Subscribe stream the SDK owns end to end: subscribe on startup, heartbeat/ack, reconnect with jittered backoff, resume by revision, and a 5-minute reconciliation safety net. Every non-static value in a namespace shares one namespace-wide subscription. Set Static: true to pin a value to its boot-time read (ParameterValue{Key: "log-format", Static: true}).

Default flip: values now change at runtime by default. Get() was already the documented read pattern; use Static: true where you need a fixed value.

// Always-current handle:
limit := cfg.RateLimit.Get()

// React to changes (runs on a dedicated goroutine; a slow/panicking callback
// can never stall the stream):
cfg.RateLimit.OnChange(func(old, new string) {
    pool.Resize(mustAtoi(new))
})

// Watch fires for EVERY change in the client's namespace — there is no key
// pattern. Filter inside the callback if you only care about a subset. Use
// client.WatchNamespace(ctx, "env/app", fn) to watch a different namespace.
stop, _ := client.Watch(ctx, func(ev kmsclient.Event) {
    if !strings.HasPrefix(ev.Key, "billing/") {
        return
    }
    log.Printf("%s %s/%s -> %s", ev.Type, ev.Namespace, ev.Key, ev.Value)
})
defer stop()

If the store is unreachable the SDK keeps serving last-known values and reconnects in the background.

Atomic configuration releases

Use a release loader when related values must be resolved and installed together rather than through independent key callbacks:

loader, err := kmsclient.NewReleaseLoader(client, kmsclient.ReleaseLoaderConfig{
    Name: "runtime",
    SecretTokenProvider: func(alias, path string) (string, bool) {
        token, ok := localTokens[alias]
        return token, ok
    },
})
if err != nil { return err }

err = loader.Run(ctx, func(ctx context.Context, snapshot kmsclient.ReleaseSnapshot) (
    kmsclient.PreparedRelease, error,
) {
    return decodeValidateAndPrepare(ctx, snapshot)
})

The snapshot exposes the release version, activation revision, deterministic digest, schema pin, and exact alias-keyed resource pins. Resolved maps are immutable-by-copy and normal formatting excludes values; secret plaintext still requires explicit Secret.Value/StringValue. PreparedRelease.Commit must be infallible and normally performs an atomic swap; Abort releases any prepared candidate that becomes stale or fails the final active-release check. The loader fails startup until one release applies, then retains the last-known-good state through outages and rejections.

RunTypedRelease[T] adds an explicit decode step and uses no reflection. See ../../../docs/sdk-go.md for lifecycle, acknowledgement, token-provider, and status details.

For an application-specific store with generated strict group decoders, source-owned default drift checks, hot/restart policy, immutable snapshots, typed consumer views, and schema/contract generation, use the additive sdk/go/configstore managed configuration layer and cmd/kms-config-gen. Existing ReleaseLoader, RunTypedRelease, ParameterValue, and SecretValue integrations do not need to change.

Errors

Map gRPC codes to sentinels with errors.Is:

if errors.Is(err, kmsclient.ErrNotFound) { ... }

ErrNotFound, ErrPermissionDenied, ErrUnauthenticated, ErrFailedPrecondition, ErrNoNamespace. No error ever contains secret plaintext.

Testing against a fake

kmsclient/kmsclienttest provides an in-process, scriptable gRPC fake (bufconn) for your own tests: set values by namespace + relative key (or display path), inject errors, set the WhoAmI identity, and drive the Subscribe stream (snapshots, changes, heartbeats, forced disconnects).

srv, _ := kmsclienttest.New()
defer srv.Close()
srv.SetParameter("prod/gradethis", "rate-limit", "100")
srv.SetParameterPath("/prod/gradethis/rate-limit", "100") // equivalent

client, _ := kmsclient.NewClient(kmsclient.Config{
    Endpoint:    srv.Target(),
    Namespace:   "prod/gradethis",
    DialOptions: srv.DialOptions(),
})

srv.DialOptions() includes explicit cleartext transport credentials for the in-process connection, so no Insecure flag is needed in this test setup.

Documentation

Overview

Package kmsclient is the Go client SDK for the KMS parameter store and secret management service.

It hides gRPC boilerplate behind a small ergonomic surface:

Secret plaintext never appears in logs, errors, or the default string/JSON representation of any type in this package. Access to plaintext always requires an explicit call (Secret.Value, SecretValue.Value).

Namespaces and keys

A client operates in a namespace — a fixed (env, app) pair such as "prod/gradethis". Keys are relative to that namespace ("postgres/password"); interior slashes are part of the key name, not namespace structure. Set the namespace with Config.Namespace, or leave it empty to discover it from the identity at first use (via WhoAmI); a relative key on an unbound identity then fails with ErrNoNamespace. A leading-slash key is an absolute "/env/app/key" display path, split in the SDK to reach another namespace.

Hot reload

Non-secret ParameterValue fields hot-reload by default: they track the store over a shared Subscribe stream and ParameterValue.Get always returns the latest value. Set ParameterValue.Static to resolve once at Init instead.

Quick start

client, err := kmsclient.NewClient(kmsclient.Config{
    Endpoint:  "parameter-store.prod.internal:8443",
    Namespace: "prod/payments", // or "" to discover via WhoAmI
    TLS:       kmsclient.MTLSFromFiles("client.crt", "client.key", "server-ca.crt"),
    CacheTTL:  time.Minute,
})
if err != nil {
    return err
}
defer client.Close()

dbPassword, err := client.GetSecret(ctx, "postgres/password")
if err != nil {
    return err
}
_ = dbPassword.Value() // []byte plaintext, never logged

Index

Examples

Constants

View Source
const (
	ReleaseStateReceived = "received"
	ReleaseStatePrepared = "prepared"
	ReleaseStateApplied  = "applied"
	ReleaseStateRejected = "rejected"

	ReleaseRejectResolutionFailed       = "resolution_failed"
	ReleaseRejectTokenUnavailable       = "token_unavailable"
	ReleaseRejectVersionMismatch        = "version_mismatch"
	ReleaseRejectDigestMismatch         = "digest_mismatch"
	ReleaseRejectPrepareFailed          = "prepare_failed"
	ReleaseRejectConfigContractMismatch = "config_contract_mismatch"
	ReleaseRejectConfigDecodeFailed     = "config_decode_failed"
	ReleaseRejectConfigValidationFailed = "config_validation_failed"
	ReleaseRejectDefaultMismatch        = "default_mismatch"
	ReleaseRejectRestartRequired        = "restart_required"
	ReleaseRejectSuperseded             = "superseded"
	ReleaseRejectActiveCheck            = "active_check_failed"
	ReleaseRejectInternal               = "internal"
)

Variables

View Source
var (
	// ErrNotFound is returned when a parameter or secret (or the requested
	// version/label) does not exist.
	ErrNotFound = errors.New("kmsclient: not found")

	// ErrPermissionDenied is returned when the caller is authenticated but not
	// authorized for the requested path or operation.
	ErrPermissionDenied = errors.New("kmsclient: permission denied")

	// ErrUnauthenticated is returned when the client identity token is missing,
	// invalid, or expired.
	ErrUnauthenticated = errors.New("kmsclient: unauthenticated")

	// ErrFailedPrecondition is returned when the request is well-formed but the
	// server state does not allow it (e.g. mode mismatch on a client-bound
	// secret).
	ErrFailedPrecondition = errors.New("kmsclient: failed precondition")

	// ErrNotInitialized is reserved for compatibility. Declarative values do not
	// currently return it: SecretValue.Value panics with a descriptive message,
	// while ParameterValue.Get returns the empty string before Init/Resolve.
	ErrNotInitialized = errors.New("kmsclient: value not initialized")

	// ErrNoNamespace is returned when a relative key must be resolved but no
	// namespace is available: Config.Namespace is empty and the identity is
	// unbound (WhoAmI reports no namespace). Set Config.Namespace, bind the
	// identity to a namespace, or use an absolute "/env/app/key" display path.
	ErrNoNamespace = errors.New("kmsclient: no namespace")
)

Sentinel errors returned by the SDK. Callers should test with errors.Is. None of these values, nor any error wrapping them, ever contains secret plaintext.

Functions

func MTLSConfig

func MTLSConfig(certFile, keyFile, caFile string) (*tls.Config, error)

MTLSConfig is the error-returning form of MTLSFromFiles.

func MTLSFromFiles

func MTLSFromFiles(certFile, keyFile, caFile string) *tls.Config

MTLSFromFiles builds a *tls.Config for mutual TLS: it presents the given client certificate/key and verifies the server against caFile.

It panics on error so it can be used inline in a Config literal; use MTLSConfig for an error-returning variant.

func RunTypedRelease

func RunTypedRelease[T any](
	ctx context.Context,
	loader *ReleaseLoader,
	decode func(ReleaseSnapshot) (T, error),
	prepare func(context.Context, T) (PreparedRelease, error),
) error

RunTypedRelease explicitly decodes a release snapshot into T and then calls the application preparation function. It uses no reflection.

func TLSConfig

func TLSConfig(caFile string) (*tls.Config, error)

TLSConfig is the error-returning form of TLSFromFiles.

func TLSFromFiles

func TLSFromFiles(caFile string) *tls.Config

TLSFromFiles builds a *tls.Config that verifies the server against the CA bundle in caFile. Use it for one-way TLS where the server does not require a client certificate.

It panics if the files cannot be read or parsed, so it is convenient to use inline in a Config literal at startup; use TLSConfig for an error-returning variant.

Types

type Client

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

Client is a connection to the parameter store. It is safe for concurrent use and should be shared for the lifetime of the process. Call Close to release resources.

func NewClient

func NewClient(cfg Config) (*Client, error)

NewClient dials the parameter store and returns a ready Client. Transport security must be selected explicitly with Config.TLS, Config.Insecure, or custom transport credentials in Config.DialOptions.

Example
package main

import (
	"context"
	"log"
	"time"

	"github.com/Suhaibinator/kms/sdk/go/kmsclient"
)

func main() {
	client, err := kmsclient.NewClient(kmsclient.Config{
		Endpoint: "parameter-store.prod.internal:8443",
		// Cert-only identity: the namespace is discovered from the cert via
		// WhoAmI. Set Namespace explicitly to skip discovery.
		TLS:      kmsclient.MTLSFromFiles("client.crt", "client.key", "server-ca.crt"),
		CacheTTL: time.Minute,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	ctx := context.Background()
	dbPassword, err := client.GetSecret(ctx, "postgres/password")
	if err != nil {
		log.Fatal(err)
	}
	// dbPassword prints as [REDACTED]; call Value() for plaintext.
	_ = dbPassword.Value()
}

func (*Client) Close

func (c *Client) Close() error

Close releases the connection and stops all background goroutines. It is safe to call multiple times.

func (*Client) GetParameter

func (c *Client) GetParameter(ctx context.Context, key string, opts ...GetOption) (string, error)

GetParameter returns the value of a non-secret parameter. key is relative to the client namespace, or an absolute "/env/app/key" display path. By default it reads the "current" label; use WithVersion or WithLabel to read another.

func (*Client) GetSecret

func (c *Client) GetSecret(ctx context.Context, key string, opts ...GetOption) (Secret, error)

GetSecret returns a secret. The returned Secret redacts itself in logs and string/JSON formatting; call Value or StringValue for plaintext. Use WithSecretToken for token-protected or client-bound secrets.

Token-gated reads (WithSecretToken) bypass the client cache entirely: caching them under the token-less key would let later calls without the token read the plaintext from cache, skipping the server's per-secret token check, and would keep serving after a token rotation until the TTL expired.

Example (ErrorHandling)
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/Suhaibinator/kms/sdk/go/kmsclient"
)

func main() {
	client, _ := kmsclient.NewClient(kmsclient.Config{
		Endpoint:  "localhost:8443",
		Namespace: "prod/payments",
		Insecure:  true, // local development only
	})
	defer func() { _ = client.Close() }()

	_, err := client.GetSecret(context.Background(), "missing")
	switch {
	case errors.Is(err, kmsclient.ErrNotFound):
		fmt.Println("not found")
	case errors.Is(err, kmsclient.ErrPermissionDenied):
		fmt.Println("denied")
	}
}

func (*Client) PutParameter

func (c *Client) PutParameter(ctx context.Context, key, value string, opts ...PutOption) (PutParameterResult, error)

PutParameter creates a new immutable version of a parameter. key is relative to the client namespace, or an absolute "/env/app/key" display path. It is intended for tooling; most applications only read.

func (*Client) PutSecret

func (c *Client) PutSecret(ctx context.Context, key string, value []byte, opts ...PutSecretOption) (PutSecretResult, error)

PutSecret creates a new immutable version of a secret. key is relative to the client namespace, or an absolute "/env/app/key" display path. It is intended for tooling.

func (*Client) Resolve

func (c *Client) Resolve(ctx context.Context, cfg any) error

Resolve walks cfg (which must be a non-nil pointer to a struct) and initializes every SecretValue and ParameterValue field it finds. Fetches are issued concurrently to minimize startup latency.

Walked: exported struct fields, non-nil pointers (including pointer chains), and the elements of slices and arrays — recursively, so a []SubConfig or []*SubConfig whose elements hold SecretValue/ParameterValue fields is fully initialized.

Not walked: map values (dynamically keyed), interface values, unexported fields, and channels/funcs. A SecretValue/ParameterValue reached only through one of these is left uninitialized and will panic at .Value()/return "" from .Get(); place such fields where Resolve can reach them.

If any field fails to resolve, Resolve returns the first error (after all in-flight fetches settle); already-initialized fields are left initialized.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Suhaibinator/kms/sdk/go/kmsclient"
)

func main() {
	type Config struct {
		DBPassword kmsclient.SecretValue
		StripeKey  kmsclient.SecretValue
		RateLimit  kmsclient.ParameterValue
	}

	client, err := kmsclient.NewClient(kmsclient.Config{
		Endpoint:  "localhost:8443",
		Namespace: "prod/payments",
		Insecure:  true, // local development only
	})
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	cfg := Config{
		DBPassword: kmsclient.SecretValue{Key: "postgres/password"},
		StripeKey:  kmsclient.SecretValue{Key: "stripe/api-key", EnvVar: "STRIPE_KEY"},
		RateLimit:  kmsclient.ParameterValue{Key: "rate-limit"}, // hot-reloads by default
	}
	if err := client.Resolve(context.Background(), &cfg); err != nil {
		log.Fatal(err)
	}

	cfg.RateLimit.OnChange(func(old, new string) {
		fmt.Printf("rate limit changed: %s -> %s\n", old, new)
	})
	_ = cfg.DBPassword.Value()
	_ = cfg.RateLimit.Get()
}

func (*Client) Watch

func (c *Client) Watch(ctx context.Context, fn func(Event)) (stop func(), err error)

Watch subscribes to the client's whole namespace and invokes fn for every change in it — there is no key pattern. An app that only cares about a subset filters inside fn by its own convention (e.g. strings.HasPrefix(ev.Key, "db/")). The returned stop function unregisters the watcher; it is also called automatically if ctx is cancelled. Watch requires the client to have a namespace (Config.Namespace or a namespace-bound identity); otherwise it returns ErrNoNamespace.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Suhaibinator/kms/sdk/go/kmsclient"
)

func main() {
	client, _ := kmsclient.NewClient(kmsclient.Config{
		Endpoint:  "localhost:8443",
		Namespace: "prod/payments",
		Insecure:  true, // local development only
	})
	defer func() { _ = client.Close() }()

	// Watch fires for every change in the client's namespace; filter inside the
	// callback if you only care about a subset.
	stop, err := client.Watch(context.Background(), func(ev kmsclient.Event) {
		fmt.Printf("%s %s => %s\n", ev.Type, ev.Key, ev.Value)
	})
	if err != nil {
		log.Fatal(err)
	}
	defer stop()
}

func (*Client) WatchNamespace

func (c *Client) WatchNamespace(ctx context.Context, namespace string, fn func(Event)) (stop func(), err error)

WatchNamespace watches a specific namespace ("env/app") the client is authorized for, invoking fn for every change in it. Like Watch it takes no key pattern. Use it to observe a namespace other than the client's own.

type Config

type Config struct {
	// Endpoint is the server address as host:port. For custom transports it
	// may be any gRPC target; see DialOptions.
	Endpoint string

	// Namespace is the client's home namespace in "env/app" form (e.g.
	// "prod/gradethis"). Relative keys are resolved against it. Leave it empty
	// to discover the namespace from the identity at first use via WhoAmI; a
	// relative key on an unbound identity then fails with ErrNoNamespace.
	// Absolute "/env/app/key" display paths never require a namespace.
	Namespace string

	// Token is the per-client identity token, sent as
	// "authorization: Bearer <token>" on every RPC. It is optional when TLS
	// carries a client certificate (the identity then derives from the cert
	// server-side); required only for token-method identities. Empty is also
	// allowed for unauthenticated/dev servers.
	Token string

	// TLS configures transport security. Use TLSFromFiles or MTLSFromFiles to
	// build one. When TLS is nil, NewClient fails unless Insecure is explicitly
	// set or DialOptions supplies custom transport credentials.
	TLS *tls.Config

	// Insecure explicitly permits a cleartext connection. Use it only for local
	// development on a trusted network. It is mutually exclusive with TLS.
	Insecure bool

	// CacheTTL enables an in-memory read cache for GetParameter/GetSecret when
	// greater than zero. Cached parameter and secret entries are invalidated by
	// watch events when a subscription is active.
	CacheTTL time.Duration

	// FallbackToDefaultsOnError controls whether declarative SecretValue /
	// ParameterValue fields fall back to their Default on ANY store-fetch error.
	//
	// By default (false) a Default is used only when the store affirmatively
	// reports the value absent (ErrNotFound). Every other error — unavailable,
	// timeout, unauthenticated, permission denied — fails Init/Resolve, so a
	// process cannot silently boot on a dev Default (e.g. "sk_test_...") merely
	// because the store was briefly unreachable at startup.
	//
	// Set true to restore the permissive any-error → Default behavior (plan
	// 9.2.9, "fallback behavior where explicitly configured"). Use it only where
	// booting on a stale or dev default is genuinely acceptable.
	FallbackToDefaultsOnError bool

	// Timeout is the default per-RPC deadline applied when the caller's context
	// has no earlier deadline. Defaults to 5s. Does not apply to the long-lived
	// Subscribe stream.
	Timeout time.Duration

	// ClientName identifies this client to the subscription registry (plan
	// 8.4). Defaults to the base name of os.Args[0].
	ClientName string

	// Logger receives operational log lines. It never receives secret
	// plaintext. Defaults to the standard library logger.
	Logger Logger

	// DialOptions are appended to the dial options the SDK builds, allowing
	// advanced transport tuning or injection of a custom dialer. Options here
	// override earlier ones (e.g. transport credentials). When neither TLS nor
	// Insecure is set, DialOptions must supply transport credentials; this is how
	// tests explicitly dial an in-process cleartext server.
	DialOptions []grpc.DialOption
}

Config configures a Client.

type Event

type Event struct {
	Type EventType
	// Namespace is the "env/app" namespace of the changed resource.
	Namespace string
	// Key is the resource key, relative to Namespace.
	Key        string
	Value      string // populated for EventPut
	Version    uint64
	Revision   uint64
	ChangeType string // raw server change_type, useful for secret changes
}

Event is delivered to Watch callbacks for every change in a watched namespace.

func (Event) Path

func (e Event) Path() string

Path returns the "/env/app/key" display path of the event's resource.

type EventType

type EventType int

EventType classifies a watch Event.

const (
	// EventPut is a parameter create/update or label move; Value holds the new
	// value.
	EventPut EventType = iota
	// EventDelete is a parameter deletion.
	EventDelete
	// EventSecretChange is a secret metadata change (no plaintext); the app
	// re-fetches via GetSecret if it cares.
	EventSecretChange
)

func (EventType) String

func (t EventType) String() string

type GetOption

type GetOption func(*getOptions)

GetOption customizes a single GetParameter / GetSecret call.

func WithLabel

func WithLabel(label string) GetOption

WithLabel reads the version currently pointed at by the given label (e.g. "current", "previous"). Ignored if WithVersion is also supplied.

func WithSecretToken

func WithSecretToken(token string) GetOption

WithSecretToken sets the x-kms-secret-token metadata for the call. It is required for token-protected and client-bound secrets; for client-bound secrets the token also carries the client key share (see plan 10.7).

func WithVersion

func WithVersion(n uint64) GetOption

WithVersion pins the read to a specific immutable version. It takes precedence over WithLabel.

type Logger

type Logger interface {
	Printf(format string, args ...any)
}

Logger is the minimal logging surface the SDK uses. It mirrors the signature of the standard library's log.Printf.

The SDK only ever logs operational information (paths, env-var names, connection state, revisions). It never logs secret plaintext.

type ParameterValue

type ParameterValue struct {
	// Key is the parameter key, relative to the client namespace (e.g.
	// "rate-limit"), or an absolute "/env/app/key" display path.
	Key string
	// EnvVar optionally overrides the store value; an env-set value is pinned
	// and does not hot-reload.
	EnvVar string
	// Default is an optional fallback, intended for development.
	Default string
	// Static disables hot reload: the value resolves once at Init and never
	// changes afterward. The zero value keeps hot reload ON — every non-static
	// ParameterValue tracks its namespace over the shared Subscribe stream.
	Static bool
	// contains filtered or unexported fields
}

ParameterValue is a declarative, store-backed non-secret field. By default it hot-reloads over the client's shared Subscribe stream, so Get always returns the latest value without an RPC. Set Static to opt out and resolve once at Init.

Like SecretValue, mutable state lives behind a pointer so the value type stays copy-safe.

func (*ParameterValue) Get

func (p *ParameterValue) Get() string

Get returns the latest value. Safe for concurrent use; returns "" before Init.

func (*ParameterValue) Init

func (p *ParameterValue) Init(client *Client) error

Init resolves the value (env override, store fetch, Default only when the store reports the value absent unless Config.FallbackToDefaultsOnError is set, else error) and, unless Static is set or the value came from an env override, registers the value's namespace on the client's shared subscription for hot reload. It is idempotent.

func (*ParameterValue) InitContext

func (p *ParameterValue) InitContext(ctx context.Context, client *Client) error

InitContext is Init with a caller-supplied context.

func (*ParameterValue) Initialized

func (p *ParameterValue) Initialized() bool

Initialized reports whether the value has been resolved.

func (*ParameterValue) OnChange

func (p *ParameterValue) OnChange(fn func(old, new string))

OnChange registers a callback invoked (on a dedicated goroutine) whenever the value changes. old and new are the previous and current values. Registering a callback on a Static or env-pinned value is allowed but it will never fire.

func (ParameterValue) String

func (p ParameterValue) String() string

String returns the current value (non-secret), or "" before Init.

type PrepareReleaseFunc

type PrepareReleaseFunc func(context.Context, ReleaseSnapshot) (PreparedRelease, error)

PrepareReleaseFunc validates a candidate and constructs resources before it becomes visible. The context is canceled when a newer release supersedes the candidate.

type PreparedRelease

type PreparedRelease interface {
	Commit()
	Abort()
}

PreparedRelease is application-owned state built from a complete candidate. Commit must be infallible and should normally perform only an atomic swap.

type PutOption

type PutOption func(*putOptions)

PutOption customizes a PutParameter call.

func WithContentType

func WithContentType(ct string) PutOption

WithContentType sets the parameter content type (string, integer, float, boolean, json, binary). Defaults to "string" on the server.

func WithMetadataJSON

func WithMetadataJSON(j string) PutOption

WithMetadataJSON attaches an opaque JSON metadata blob to the parameter.

type PutParameterResult

type PutParameterResult struct {
	Version  uint64
	Revision uint64
}

PutParameterResult reports the outcome of a parameter write.

type PutSecretOption

type PutSecretOption func(*putSecretOptions)

PutSecretOption customizes a PutSecret call.

func WithClientBound

func WithClientBound() PutSecretOption

WithClientBound opts a secret into client-bound double wrapping. New secrets also require WithGenerateAccessToken; updates must keep this option set and supply the current token with WithPutSecretToken.

func WithExpiresAt

func WithExpiresAt(unixMS int64) PutSecretOption

WithExpiresAt sets an expiry (unix milliseconds) for the new secret version; 0 means never.

func WithGenerateAccessToken

func WithGenerateAccessToken() PutSecretOption

WithGenerateAccessToken asks the server to mint a per-secret access token, returned exactly once in PutSecretResult.AccessToken.

func WithPutSecretToken

func WithPutSecretToken(token string) PutSecretOption

WithPutSecretToken sets the x-kms-secret-token for the write, needed when updating an existing token-protected or client-bound secret.

func WithSecretContentType

func WithSecretContentType(ct string) PutSecretOption

WithSecretContentType sets the secret content type.

func WithSecretMetadataJSON

func WithSecretMetadataJSON(j string) PutSecretOption

WithSecretMetadataJSON attaches an opaque JSON metadata blob to the secret.

type PutSecretResult

type PutSecretResult struct {
	Version     uint64
	Revision    uint64
	AccessToken string
}

PutSecretResult reports the outcome of a secret write. AccessToken is set only when WithGenerateAccessToken was supplied, and is never retrievable again.

type ReleaseEntryMetadata

type ReleaseEntryMetadata struct {
	Alias           string `json:"alias"`
	Kind            string `json:"kind"`
	Path            string `json:"path"`
	Version         uint64 `json:"version"`
	ContentType     string `json:"content_type,omitempty"`
	MetadataJSON    string `json:"metadata_json,omitempty"`
	ParameterDigest string `json:"parameter_digest,omitempty"`
	ClientBound     bool   `json:"client_bound,omitempty"`
	HasAccessToken  bool   `json:"has_access_token,omitempty"`
}

ReleaseEntryMetadata describes one immutable resource pin in a configuration release. It contains no parameter value, secret plaintext, or access token.

type ReleaseLoader

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

ReleaseLoader owns release stream reliability, exact resource resolution, lifecycle acknowledgements, and last-known-good behavior.

func NewReleaseLoader

func NewReleaseLoader(client *Client, cfg ReleaseLoaderConfig) (*ReleaseLoader, error)

NewReleaseLoader creates a loader. It does not contact KMS until Run.

func (*ReleaseLoader) InstanceID

func (l *ReleaseLoader) InstanceID() string

InstanceID returns the subscriber instance ID reused across reconnects.

func (*ReleaseLoader) Run

func (l *ReleaseLoader) Run(ctx context.Context, prepare PrepareReleaseFunc) error

Run watches, resolves, prepares, and atomically applies release candidates. Before the first successful Commit, a non-supersession candidate failure is returned. After that point errors reject the candidate and the last-known-good release remains applied until a later candidate succeeds.

func (*ReleaseLoader) Stats

func (l *ReleaseLoader) Stats() ReleaseLoaderStats

Stats returns bounded counters without resource aliases or paths.

func (*ReleaseLoader) Status

func (l *ReleaseLoader) Status() ReleaseLoaderStatus

Status returns a redacted, concurrency-safe loader status snapshot.

type ReleaseLoaderConfig

type ReleaseLoaderConfig struct {
	// Name is the release name within the client's home namespace.
	Name string
	// ReconcileInterval controls fresh GetActiveRelease safety checks. It
	// defaults to one minute.
	ReconcileInterval time.Duration
	// SecretTokenProvider supplies locally held credentials for protected
	// secret entries. Tokens are sent only to the corresponding GetSecret RPC.
	SecretTokenProvider SecretTokenProvider
	// ValidateManifest optionally validates the immutable unresolved manifest.
	// It runs after release identity, digest, and basic entry validation, but
	// before any resource fetch or secret-token lookup.
	ValidateManifest ValidateReleaseManifestFunc
	// MaxConcurrentFetches bounds parallel pinned resource reads. Values <= 0
	// use 16; values above 256 are rejected.
	MaxConcurrentFetches int
	// InstanceID overrides the generated process-lifetime subscriber ID. It is
	// mainly useful when an application already has a stable replica identifier.
	InstanceID string
}

ReleaseLoaderConfig configures a high-level configuration release loader.

type ReleaseLoaderStats

type ReleaseLoaderStats struct {
	Candidates uint64
	Applied    uint64
	Rejected   map[string]uint64
	Reconnects uint64
}

ReleaseLoaderStats contains bounded counters suitable for metrics export. It intentionally contains no aliases, paths, diagnostics, or secret metadata.

type ReleaseLoaderStatus

type ReleaseLoaderStatus struct {
	State                  string
	ObservedVersion        uint64
	ObservedRevision       uint64
	AppliedVersion         uint64
	AppliedRevision        uint64
	LastFailureCategory    string
	LastFailureAt          time.Time
	LastResolutionDuration time.Duration
	Reconnects             uint64
}

ReleaseLoaderStatus is a redacted point-in-time view of loader progress.

type ReleaseManifest

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

ReleaseManifest is an immutable, unresolved configuration release. It contains only release identity and non-sensitive entry metadata, never parameter values, secret plaintext, or access tokens. Its entry map is private and accessors return copies so validation callbacks cannot alter the candidate that the loader will resolve.

func (ReleaseManifest) ActivationRevision

func (m ReleaseManifest) ActivationRevision() uint64

func (ReleaseManifest) Digest

func (m ReleaseManifest) Digest() string

func (ReleaseManifest) Entries

Entries returns an alias-keyed copy of every unresolved release entry.

func (ReleaseManifest) Entry

func (m ReleaseManifest) Entry(alias string) (ReleaseEntryMetadata, bool)

Entry returns metadata for one stable alias.

func (ReleaseManifest) Format

func (m ReleaseManifest) Format(f fmt.State, _ rune)

Format prevents formatting from reflecting private implementation fields.

func (ReleaseManifest) GoString

func (m ReleaseManifest) GoString() string

GoString uses the same safe representation as String.

func (ReleaseManifest) MarshalJSON

func (m ReleaseManifest) MarshalJSON() ([]byte, error)

MarshalJSON emits only release identity and non-sensitive entry metadata.

func (ReleaseManifest) MetadataJSON

func (m ReleaseManifest) MetadataJSON() string

func (ReleaseManifest) Name

func (m ReleaseManifest) Name() string

func (ReleaseManifest) Namespace

func (m ReleaseManifest) Namespace() string

func (ReleaseManifest) SchemaID

func (m ReleaseManifest) SchemaID() string

func (ReleaseManifest) SchemaVersion

func (m ReleaseManifest) SchemaVersion() uint64

func (ReleaseManifest) String

func (m ReleaseManifest) String() string

String intentionally contains only release identity, never resolved values.

func (ReleaseManifest) Version

func (m ReleaseManifest) Version() uint64

type ReleaseParameter

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

ReleaseParameter is a resolved, version-pinned non-secret value. Its metadata is copied from the immutable release manifest.

func (ReleaseParameter) Entry

Entry returns the resource pin and non-sensitive metadata.

func (ReleaseParameter) StringValue

func (p ReleaseParameter) StringValue() string

StringValue is an alias for Value.

func (ReleaseParameter) Value

func (p ReleaseParameter) Value() string

Value returns the parameter document exactly as stored.

type ReleaseSnapshot

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

ReleaseSnapshot is a completely resolved configuration release candidate. Its maps are private and accessors return copies, so application code cannot alter the candidate seen by another preparation step.

func (ReleaseSnapshot) ActivationRevision

func (s ReleaseSnapshot) ActivationRevision() uint64

func (ReleaseSnapshot) Digest

func (s ReleaseSnapshot) Digest() string

func (ReleaseSnapshot) Entries

Entries returns an alias-keyed copy of every release entry.

func (ReleaseSnapshot) Entry

func (s ReleaseSnapshot) Entry(alias string) (ReleaseEntryMetadata, bool)

Entry returns metadata for one stable alias.

func (ReleaseSnapshot) Format

func (s ReleaseSnapshot) Format(f fmt.State, _ rune)

Format prevents %+v and %#v from reflecting private secret-bearing fields.

func (ReleaseSnapshot) GoString

func (s ReleaseSnapshot) GoString() string

GoString uses the same redacted representation as String.

func (ReleaseSnapshot) MarshalJSON

func (s ReleaseSnapshot) MarshalJSON() ([]byte, error)

MarshalJSON emits release identity and entry metadata only. Resolved values are deliberately excluded so snapshots are safe to attach to diagnostics.

func (ReleaseSnapshot) MetadataJSON

func (s ReleaseSnapshot) MetadataJSON() string

func (ReleaseSnapshot) Name

func (s ReleaseSnapshot) Name() string

func (ReleaseSnapshot) Namespace

func (s ReleaseSnapshot) Namespace() string

func (ReleaseSnapshot) Parameter

func (s ReleaseSnapshot) Parameter(alias string) (ReleaseParameter, bool)

Parameter returns one resolved parameter document.

func (ReleaseSnapshot) Parameters

func (s ReleaseSnapshot) Parameters() map[string]ReleaseParameter

Parameters returns an alias-keyed copy of all resolved parameter documents.

func (ReleaseSnapshot) SchemaID

func (s ReleaseSnapshot) SchemaID() string

func (ReleaseSnapshot) SchemaVersion

func (s ReleaseSnapshot) SchemaVersion() uint64

func (ReleaseSnapshot) Secret

func (s ReleaseSnapshot) Secret(alias string) (Secret, bool)

Secret returns one resolved secret without exposing it through formatting.

func (ReleaseSnapshot) Secrets

func (s ReleaseSnapshot) Secrets() map[string]Secret

Secrets returns an alias-keyed copy of the resolved secret values. Every Secret preserves the SDK's redacting formatting behavior; plaintext remains available only through Secret.Value/StringValue.

func (ReleaseSnapshot) String

func (s ReleaseSnapshot) String() string

String intentionally contains only release identity, never resolved values.

func (ReleaseSnapshot) Version

func (s ReleaseSnapshot) Version() uint64

type Secret

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

Secret holds secret plaintext together with non-sensitive metadata. It is deliberately hard to leak: its String, GoString, Format and MarshalJSON implementations all emit "[REDACTED]", so it is safe to pass to fmt.Printf, structured loggers, or json.Marshal. Plaintext is only accessible through the explicit Value / StringValue accessors.

Secret is a value type; copies redact identically.

func NewSecret

func NewSecret(value []byte) Secret

NewSecret wraps plaintext in a Secret. It is mainly useful in tests and tooling; normal code obtains Secrets from Client.GetSecret.

func (Secret) Clone

func (s Secret) Clone() Secret

Clone returns an independent copy of the Secret. The plaintext buffer is deep-copied while the immutable path, version, and content-type metadata are preserved.

func (Secret) ContentType

func (s Secret) ContentType() string

ContentType returns the declared content type of the secret, if known.

func (Secret) Format

func (s Secret) Format(f fmt.State, verb rune)

Format implements fmt.Formatter so that every verb (%v, %s, %+v, %#v, %q, ...) redacts the plaintext. %q wraps the redaction in quotes for valid output.

func (Secret) GoString

func (s Secret) GoString() string

GoString implements fmt.GoStringer (used by the %#v verb) and always redacts.

func (Secret) IsZero

func (s Secret) IsZero() bool

IsZero reports whether the Secret carries no plaintext.

func (Secret) MarshalJSON

func (s Secret) MarshalJSON() ([]byte, error)

MarshalJSON always emits the redaction string, so a Secret embedded in a JSON-marshaled struct never leaks plaintext.

func (Secret) Path

func (s Secret) Path() string

Path returns the store path the secret was read from, if known.

func (Secret) String

func (s Secret) String() string

String implements fmt.Stringer and always redacts.

func (Secret) StringValue

func (s Secret) StringValue() string

StringValue returns the secret plaintext as a string.

func (Secret) Value

func (s Secret) Value() []byte

Value returns the raw secret plaintext. This is the only way to read it.

func (Secret) Version

func (s Secret) Version() uint64

Version returns the secret version that was read, if known.

type SecretTokenProvider

type SecretTokenProvider func(alias, path string) (token string, ok bool)

SecretTokenProvider supplies a locally held per-secret access token or client-bound key share. It is called only for release entries marked as token-protected or client-bound.

type SecretValue

type SecretValue struct {
	// Key is the secret key, relative to the client namespace (e.g.
	// "stripe/api-key"), or an absolute "/env/app/key" display path.
	Key string
	// Token is the per-secret access token. For client-bound secrets it is also
	// the client key share (plan 10.7).
	Token string
	// EnvVar is an optional environment variable that, when set and non-empty,
	// overrides the store value.
	EnvVar string
	// Default is an optional fallback value, intended for development only.
	Default string
	// contains filtered or unexported fields
}

SecretValue is a declarative, store-backed secret field. Declare it in a config struct, then resolve it with Init (or via Client.Resolve). Once initialized it redacts itself in all string/JSON formatting; plaintext is only reachable through Value/StringValue.

The mutable state lives behind an unexported pointer so that SecretValue's redacting formatter methods can use value receivers (a struct that embedded a mutex directly could not, and would leak plaintext through fmt's reflection of unexported fields).

func (SecretValue) Format

func (v SecretValue) Format(f fmt.State, verb rune)

Format redacts for every verb.

func (SecretValue) GoString

func (v SecretValue) GoString() string

GoString redacts (used by %#v).

func (*SecretValue) Init

func (v *SecretValue) Init(client *Client) error

Init resolves the value using the standard order: env override, then store fetch, then Default (only when the store reports the value absent, unless Config.FallbackToDefaultsOnError is set), else an error naming the path. It is idempotent: a second call after success is a no-op. Init is not safe to call concurrently on the same SecretValue (Client.Resolve initializes distinct fields in parallel, which is safe).

func (*SecretValue) InitContext

func (v *SecretValue) InitContext(ctx context.Context, client *Client) error

InitContext is Init with a caller-supplied context.

func (*SecretValue) Initialized

func (v *SecretValue) Initialized() bool

Initialized reports whether the value has been resolved.

func (SecretValue) MarshalJSON

func (v SecretValue) MarshalJSON() ([]byte, error)

MarshalJSON redacts.

func (*SecretValue) Secret

func (v *SecretValue) Secret() Secret

Secret returns the resolved plaintext wrapped in a redacting Secret.

func (SecretValue) String

func (v SecretValue) String() string

String redacts. Value receiver so both SecretValue and *SecretValue redact, and so fmt does not fall through to the unexported state.

func (*SecretValue) StringValue

func (v *SecretValue) StringValue() string

StringValue is an alias for Value.

func (*SecretValue) Value

func (v *SecretValue) Value() string

Value returns the resolved plaintext. It panics if the value has not been initialized, which surfaces wiring mistakes at first use rather than serving an empty secret.

type ValidateReleaseManifestFunc

type ValidateReleaseManifestFunc func(context.Context, ReleaseManifest) error

ValidateReleaseManifestFunc validates unresolved release identity and entry metadata. It runs before any pinned parameter or secret is fetched and before SecretTokenProvider is called.

Directories

Path Synopsis
Package kmsclienttest provides an in-process, scriptable fake of the KMS gRPC services, backed by bufconn.
Package kmsclienttest provides an in-process, scriptable fake of the KMS gRPC services, backed by bufconn.

Jump to

Keyboard shortcuts

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