awsparamstore

package module
v0.2.0 Latest Latest
Warning

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

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

Documentation

Overview

Package awsparamstore implements ligand.WITIssuer backed by AWS Systems Manager (SSM) Parameter Store. It is the AWS-plane counterpart to provider/witissuer/openbao: a public-key REGISTRY plus local signing. The workload's SVID public key is published (public keys are not secrets, so the Standard tier — free — is the role-correct home), and WITs are signed locally by the provider's own ephemeral ECDSA key. Registered keys are cached in-process, so Parameter Store is not on the hot enforcement path.

Authentication — the deliberate mTLS exception

Like provider/secrets/awssm and unlike every self-hosted dependency provider, this provider does NOT dial its backend over SVID-pinned mTLS and does NOT use ligand.WITIssuerArgs.OutboundTLSConfig. Parameter Store authenticates the SERVER via web PKI and the CLIENT via SigV4 from instance-role credentials the AWS SDK resolves from IMDS. There is no SPIFFE identity to pin, so a ServerID field is deliberately absent, and SigV4's stateless per-request signing means there is no login token to renew on a months-long node.

Layout and per-service write isolation (the C3-analog)

Each public key is stored at "<Prefix>/<service>/<kid>", where <kid> is the RFC 7638 JWK thumbprint of the key and <service> is the final path segment of the workload's SPIFFE ID (e.g. "accounts" for spiffe://…/accounts). The value is JSON {spiffe_id, jwk}.

Splitting by service lets IAM grant each workload ssm:PutParameter / ssm:DeleteParameter only under "<Prefix>/<service>/*" — the per-service write-path isolation the OpenBao provider's C3 hardening prescribes, enforced by IAM instead of policy. It also makes the stored spiffe_id checkable: Provider.ResolveKey derives the expected service from the parameter's PATH and rejects any entry whose stored spiffe_id maps to a different service, so a workload cannot register a key under another workload's identity even with a lying payload. This requires each workload's SPIFFE ID to have a unique, SSM-name-safe (matching [A-Za-z0-9_.-]) final path segment within the fleet — true for the flat VM-plane SPIFFE IDs the EC2 plane assigns.

Resolution

ligand.WITIssuer.ResolveKey receives only a kid, so per-service paths cannot be addressed directly. On a cache miss the provider does one recursive GetParametersByPath over <Prefix>, validates each entry (path-vs-payload service match, then RFC 7638 self-certifying thumbprint), and builds a kid→key index cached in-process. The scanned population is tiny and bounded (≤2 live keys per instance plus shutdown leftovers). The local service's own keys never miss — they are cached by Provider.FetchWIT — so a scan happens only the first time a given peer key is verified.

Key lifecycle

Provider.FetchWIT runs on each SVID rotation (~hourly): fresh key → new kid → PutParameter, and the previous kid's parameter is deleted after Config.WITDuration (the window during which chain links referencing it remain valid). Steady state is ≤2 live parameters per instance regardless of node lifetime. On graceful shutdown the current kid is intentionally left in Parameter Store so in-flight chains stay verifiable; that leftover accumulates one parameter per instance per deployment and is cleared by the EC2 plane's teardown sweep.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrKeyNotFound is returned by [Provider.ResolveKey] when a kid is absent
	// from both the in-process cache and Parameter Store.
	ErrKeyNotFound = errors.New("awsparamstore: key not found")

	// ErrUnsupportedVerifyingKey is returned when a stored JWK is not a public key
	// in the framework's signing-algorithm allowlist (a private key, a symmetric
	// key, or an off-allowlist type/curve/size). A verifying-key registry must
	// hold only keys usable for chain-link verification; anything else fails closed.
	ErrUnsupportedVerifyingKey = errors.New("awsparamstore: unsupported verifying key")

	// ErrUnsafeServiceSegment is returned by [Provider.FetchWIT] when the SPIFFE
	// ID yields no SSM-name-safe service segment.
	ErrUnsafeServiceSegment = errors.New("awsparamstore: SPIFFE ID has no SSM-name-safe final path segment")
)

Functions

func Capture

func Capture(cfg *Config, out **Provider) ligand.WITIssuerFactory

Capture is like Factory but also stores the constructed Provider in *out, for callers that need the concrete type after ligand.Init returns.

func Factory

func Factory(cfg *Config) ligand.WITIssuerFactory

Factory returns a ligand.WITIssuerFactory that builds a Provider from cfg, for use in ligand.Config.WITIssuer. A nil cfg uses all defaults.

Types

type Config

type Config struct {
	// AWSConfig, when non-nil, is used verbatim as the SDK configuration for the
	// SSM client. Supply it to pin a region/endpoint, inject a custom credential
	// provider, or instrument the SDK HTTP client (e.g. otelaws via APIOptions).
	// Nil (the default) loads the SDK default configuration chain (environment,
	// IMDS).
	AWSConfig *aws.Config

	// Prefix is the SSM parameter path prefix under which workload public keys are
	// stored, as "<Prefix>/<service>/<kid>". It must begin with "/". Defaults to
	// "/ligand/witkeys".
	Prefix string

	// WITDuration is the validity window for issued WITs and the delay before a
	// rotated-out key's parameter is deleted. Defaults to 5 minutes.
	WITDuration time.Duration

	// CacheTTL is the revocation-propagation window: how long a key resolved by a
	// prefix scan is served from the in-process cache before re-resolution. Keys
	// written by [Provider.FetchWIT] (the local service's own) use WITDuration
	// instead so they stay valid for the full WIT window. Defaults to 5 minutes.
	CacheTTL time.Duration
}

Config holds the configuration for the SSM Parameter Store WIT issuer.

type Provider

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

Provider implements ligand.WITIssuer backed by SSM Parameter Store.

func New

func New(ctx context.Context, cfg *Config, args *ligand.WITIssuerArgs) (*Provider, error)

New constructs a Provider. It loads the AWS SDK configuration and builds an SSM client authenticated by instance-role SigV4. args is accepted for factory-signature uniformity; its OutboundTLSConfig is deliberately unused (web-PKI TLS + SigV4, not SVID-pinned mTLS — see the package doc). A nil cfg uses all defaults.

func (*Provider) Close

func (p *Provider) Close() error

Close stops the background goroutines (cache sweep and any pending old-key deletions) and waits for them to exit. The current kid is intentionally left in Parameter Store on shutdown — see the package doc. Idempotent.

func (*Provider) FetchWIT

FetchWIT registers req.CNFPublicKey in Parameter Store under "<Prefix>/<service>/<kid>", caches it locally, and returns a locally-signed WIT binding the key to the workload's SPIFFE identity. The returned KID is the RFC 7638 JWK thumbprint of CNFPublicKey.

func (*Provider) ResolveKey

func (p *Provider) ResolveKey(ctx context.Context, kid string) (*ligand.ResolvedKey, error)

ResolveKey returns the public key and bound SPIFFE ID registered under kid. It first checks the in-process cache (populated by FetchWIT and prior scans); an expired entry is treated as a miss. On a miss it scans <Prefix> once, validates every entry (path-vs-payload service match, then RFC 7638 self-certifying thumbprint), repopulates the cache, and returns the requested kid or ErrKeyNotFound.

func (*Provider) VerifyingKeys

func (p *Provider) VerifyingKeys() []crypto.PublicKey

VerifyingKeys returns the public key corresponding to the ephemeral signing key used to sign WITs. The framework merges this into GetJWTAuthorities so VerifyWPT can verify WITs produced by this issuer.

Jump to

Keyboard shortcuts

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