hostkey

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 3, 2026 License: AGPL-3.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.

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

This section is empty.

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 (*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

	// 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

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"`
	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.

type ValueKind

type ValueKind string

ValueKind enumerates the supported value-storage modes.

const (
	ValueKindEnv    ValueKind = "env"
	ValueKindStored ValueKind = "stored"
)

Jump to

Keyboard shortcuts

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