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.
Two 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.
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
- type HostKey
- type PolicyRef
- type RotateResult
- type Spec
- type Store
- func (s *Store) Delete(ctx context.Context, id string) error
- func (s *Store) Get(ctx context.Context, id string) (*HostKey, error)
- func (s *Store) KeyVersion() int32
- func (s *Store) List(ctx context.Context) ([]*HostKey, error)
- func (s *Store) LoadKeyVersion(ctx context.Context) error
- func (s *Store) Rotate(ctx context.Context, newKey []byte) (RotateResult, error)
- func (s *Store) Upsert(ctx context.Context, k *HostKey) error
- type ValueFrom
- type ValueKind
Constants ¶
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
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) Validate ¶
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 ¶
PolicyRef is the lightweight {id, name} pair used in derived reverse-ref lists exposed by the control API.
type RotateResult ¶
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
// 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.
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 ¶
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 ¶
Delete removes a HostKey by id, plus its stored secret value (best-effort — orphaned ciphertext is harmless but we clean it up).
func (*Store) KeyVersion ¶
KeyVersion returns the current master-key version (0 when env-only).
func (*Store) LoadKeyVersion ¶
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 ¶
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.
type ValueFrom ¶
type ValueFrom struct {
Kind ValueKind `json:"kind" yaml:"kind" validate:"required,oneof=env stored"`
Env string `json:"env,omitempty" yaml:"env,omitempty"`
}
ValueFrom is the value-mode discriminator. For ValueKindEnv, Env names the environment variable. For ValueKindStored, Env is empty.