hostkey

package
v0.9.8 Latest Latest
Warning

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

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

Documentation

Overview

Package hostkey is the domain layer for the HostKey entity — a credential the relay uses to authenticate to a Host (a serving endpoint). A HostKey is owned by the actor that created it (Meta.Owner.Kind=user, or =system for YAML-seeded keys) and *targets* a Host via Spec.HostID.

Value modes:

  • ValueKindEnv: Spec.ValueFrom.Env names an environment variable the relay reads at boot. No cleartext touches storage.
  • ValueKindStored: cleartext is supplied on the write boundary (Spec.Value), encrypted by the storage layer with RELAY_MASTER_KEY, and persisted as opaque bytes.
  • ValueKindOAuth: Spec.Value carries an OAuth token blob (access + refresh + expiry). Stored encrypted exactly like ValueKindStored; the resolver returns the access token and refreshes it on expiry via the oauth:<Provider> settings section. Spec.ValueFrom.Provider selects it.

Spec.Value is intentionally tagged `json:"-"` so cleartext never appears in JSONB or in API responses. Loading from YAML is the only path that carries a literal value through this struct.

store.go is the data-access layer for HostKey. It owns the secrets-table rows (metadata/spec + value_kind/value_from_env) but delegates ALL secret material to pkg/secret: env lookups and stored AES-GCM ciphertext resolve through a secret.Registry, stored values are written via the StoredResolver into the generic secret_values table, and master-key rotation is the StoredResolver's job. This package no longer touches crypto or env directly.

One Upsert routes to the env or stored path based on Spec.ValueFrom.Kind. List/Get reconstruct Spec from JSONB and populate the runtime-only Resolved/KeyHash fields by resolving the secret Ref.

Index

Constants

View Source
const (
	StrategyAPI = "api" // per-token metered spend
	StrategySub = "sub" // flat-rate subscription; reference cost only
)

Pricing strategies — see Spec.PricingStrategy.

View Source
const AnonIDPrefix = "anon:"

AnonIDPrefix prefixes the synthetic anonymous key's id, KeyHash, and name so it never collides with a real key (real KeyHash is 12 hex chars).

Variables

This section is empty.

Functions

This section is empty.

Types

type HostKey

type HostKey struct {
	Meta meta.Metadata `json:"metadata" yaml:"metadata"`
	Spec Spec          `json:"spec"     yaml:"spec"`

	// Resolved is the cleartext value reconstructed at load time (read from
	// the env var or decrypted from PG). Runtime-only; never serialized.
	Resolved string `json:"-" yaml:"-"`
	// KeyHash is sha256 hex (first 6 bytes) of Resolved, for telemetry.
	KeyHash string `json:"-" yaml:"-"`

	// Derived: populated by the control-API enrich step from the catalog
	// snapshot; not stored and not loaded from YAML. Lists every user
	// Policy whose Spec.HostKeyIDs references this HostKey, so the admin
	// UI can show where a key is in use.
	Policies []PolicyRef `json:"policies,omitempty" yaml:"-"`
}

HostKey is a credential bound to a Host.

func Anonymous added in v0.2.0

func Anonymous(hostID, hostName string) *HostKey

Anonymous returns the synthetic, never-persisted HostKey routing injects for a host marked Spec.NoAuth — a keyless upstream (e.g. self-hosted Ollama). Resolved is empty so the adapter attaches no Authorization header. KeyHash is host-scoped (not derived from the empty value) so every no-auth host gets its own circuit breaker rather than sharing one. Spec.PolicyID is left empty: it is injected past the tier gate and carries no rate-limit tier.

func (*HostKey) IsEnabled

func (k *HostKey) IsEnabled() bool

IsEnabled returns true when Enabled is unset or explicitly true.

func (*HostKey) Validate

func (k *HostKey) Validate() error

Validate runs intra-row rules via the shared meta.Validator and enforces:

  • Owner.Kind must be user or system (the actor that created the key); keys are not owned by the Host they authenticate to.
  • Spec.HostID is required; it points at the Host this key targets.
  • Spec.PolicyID is required; it picks the upstream tier Policy this key mirrors.
  • ValueKindEnv requires Spec.ValueFrom.Env; cleartext Value must be empty.
  • ValueKindStored requires non-empty cleartext Spec.Value at write time; ValueFrom.Env must be empty.

Cross-entity checks (HostID resolves to a Host; PolicyID resolves to a host-owned Policy of THAT host) live in the composition layer.

type PolicyRef

type PolicyRef struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

PolicyRef is the lightweight {id, name} pair used in derived reverse-ref lists exposed by the control API.

type RotateResult

type RotateResult struct {
	Rotated    int
	NewVersion int32
}

RotateResult is the outcome of a successful Rotate call.

type Spec

type Spec struct {
	// HostID is the id of the Host this key authenticates to. Required.
	// Replaces the pre-refactor Meta.Owner pointing at a Host.
	HostID string `json:"hostId" yaml:"hostId" validate:"required"`

	// PolicyID is the id of the host-owned tier Policy this key mirrors.
	// Required: every key must declare an upstream tier so the pipeline
	// has rate-limit rules to apply. The referenced Policy must be host-
	// owned (Owner.Kind=host) AND its Owner.ID must equal HostID — i.e.
	// a key can only mirror a policy belonging to its own host. Cross-
	// entity validation lives in the composition layer.
	PolicyID    string    `json:"policyId" yaml:"policyId" validate:"required"`
	ValueFrom   ValueFrom `json:"valueFrom"             yaml:"valueFrom"             validate:"required"`
	DefaultTier string    `json:"defaultTier,omitempty" yaml:"defaultTier,omitempty" validate:"omitempty,slug"`
	Enabled     *bool     `json:"enabled,omitempty"     yaml:"enabled,omitempty"` // nil = true

	// PricingStrategy selects how this credential's spend is accounted.
	// "api": per-token metered — the cost is real spend and counts toward
	// future budget enforcement. "sub": flat-rate subscription — the cost
	// computed from the binding's pricing is a reference/"would-have-cost"
	// figure only, never enforced. Empty defaults to "api". The value must
	// be one of the credential's Host.Spec.PricingStrategies (the host's
	// menu of offered billing modes); that membership is a composition-layer
	// invariant, like Spec.PolicyID belonging to the host.
	PricingStrategy string `json:"pricingStrategy,omitempty" yaml:"pricingStrategy,omitempty" validate:"omitempty,oneof=api sub"`

	// Value is cleartext on the write path for ValueKindStored. Accepted
	// from JSON and YAML inputs; never returned on reads — Store.Get
	// never repopulates it, and marshalSpec strips it before any JSONB
	// persistence. The `omitempty` tag combined with that read-side
	// invariant keeps cleartext out of GET responses.
	Value string `json:"value,omitempty" yaml:"value,omitempty"`
}

Spec carries non-secret config and the value-mode discriminator. Cleartext (Value) is write-only via YAML; storage encrypts and discards it.

func (Spec) EffectivePricingStrategy added in v0.3.0

func (s Spec) EffectivePricingStrategy() string

EffectivePricingStrategy returns the credential's strategy, defaulting an empty value to StrategyAPI.

type Store

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

Store is the HostKey data-access type. resolver resolves env + stored refs to plaintext; stored is the write/rotate/version authority for the stored backend (nil for env-only deployments — stored-mode operations then error loudly).

func NewStore

func NewStore(q *gen.Queries, resolver *secret.Registry, stored *secret.StoredResolver) *Store

NewStore constructs a Store. Pass the shared secret Registry + the stored resolver (see app/secret.Wire); stored may be nil to run env-only.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, id string) error

Delete removes a HostKey by id, plus its stored secret value (best-effort — orphaned ciphertext is harmless but we clean it up).

func (*Store) Get

func (s *Store) Get(ctx context.Context, id string) (*HostKey, error)

Get returns the HostKey with the given id, or (nil, nil) if not found.

func (*Store) KeyVersion

func (s *Store) KeyVersion() int32

KeyVersion returns the current master-key version (0 when env-only).

func (*Store) List

func (s *Store) List(ctx context.Context) ([]*HostKey, error)

List returns every HostKey row with Resolved + KeyHash populated.

func (*Store) LoadKeyVersion

func (s *Store) LoadKeyVersion(ctx context.Context) error

LoadKeyVersion aligns the stored resolver's in-memory key version to the maximum recorded in the store, so new secrets are tagged with the generation operators last rotated to. No-op when env-only.

func (*Store) Rotate

func (s *Store) Rotate(ctx context.Context, newKey []byte) (RotateResult, error)

Rotate re-encrypts every stored secret under newKey (transactionally, via the stored resolver) and swaps the live master key so the process keeps resolving without a restart. The caller persists newKey to the deployment's RELAY_MASTER_KEY for future boots.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, k *HostKey) error

Upsert writes k. env rows store the var name; stored rows encrypt the cleartext into secret_values (via the stored resolver) and persist a ciphertext-free secrets row. Cleartext Spec.Value never reaches the secrets table and is cleared after a successful stored write.

type ValueFrom

type ValueFrom struct {
	Kind ValueKind `json:"kind"          yaml:"kind"          validate:"required,oneof=env stored oauth"`
	Env  string    `json:"env,omitempty" yaml:"env,omitempty"`
	// Provider selects the oauth:<provider> settings section (refresh
	// endpoints + client). Required for ValueKindOAuth, empty otherwise.
	Provider string `json:"provider,omitempty" yaml:"provider,omitempty"`
}

ValueFrom is the value-mode discriminator. For ValueKindEnv, Env names the environment variable. For ValueKindStored, Env is empty. For ValueKindOAuth, Provider names the oauth:<provider> settings section used to refresh the token, and Spec.Value (on write) carries the initial token blob.

type ValueKind

type ValueKind string

ValueKind enumerates the supported value-storage modes.

const (
	ValueKindEnv    ValueKind = "env"
	ValueKindStored ValueKind = "stored"
	// ValueKindOAuth stores an OAuth token blob (access + refresh + expiry),
	// encrypted like ValueKindStored. The resolver returns the access token and
	// refreshes it on expiry via the oauth:<Provider> config. Same at-rest
	// storage as stored (rotated + decrypted by the same machinery); the
	// distinction is semantic and lives in this Spec, not the value_kind column.
	ValueKindOAuth ValueKind = "oauth"
)

Jump to

Keyboard shortcuts

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