secrets

package
v0.0.0-...-d25dbbd Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	SettingsModule    = "secrets"
	PolicySettingsKey = "policy"

	AccessModeUseOnly       = "use_only"
	AccessModeRevealOnce    = "reveal_once"
	AccessModeRevealAllowed = "reveal_allowed"
)
View Source
const (
	ReasonNotFound     = "secret not found"
	ReasonRevoked      = "secret has been revoked"
	ReasonExpired      = "secret has expired"
	ReasonAccessDenied = "access denied: secret is private to another user"
)

Machine-readable reason strings for ResolveError.

View Source
const (
	StatusActive  = "active"
	StatusRevoked = "revoked"

	ScopeGlobal      = "global"
	ScopeUserPrivate = "user_private"

	CreatedSourceSystem = "system"
	CreatedSourceUser   = "user"
)
View Source
const (
	VisibleToServer          = "server"
	VisibleToApplication     = "application"
	VisibleToServiceInstance = "service_instance"
	VisibleToConnector       = "connector"
	VisibleToProviderAccount = "provider_account"
	VisibleToAIProvider      = "ai_provider"
)
View Source
const EnvSecretKey = "APPOS_SECRET_KEY" // #nosec G101 -- environment variable name, not an embedded secret
View Source
const SecretRefPrefix = "secretRef:"

SecretRefPrefix is the string prefix that marks a field value as a secret reference. Consuming modules detect this prefix and route through Resolve instead of using the value directly.

View Source
const TemplateSingleValue = "single_value"

Variables

View Source
var (
	ErrRevealDisabled   = errors.New("reveal_disabled")
	ErrRevealNotFound   = errors.New("not_found")
	ErrRevealForbidden  = errors.New("forbidden")
	ErrRevealRevoked    = errors.New("revoked")
	ErrRevealExpired    = errors.New("expired")
	ErrRevealNotAllowed = errors.New("reveal_not_allowed")
)

Sentinel errors returned by RevealPayload for structured route-layer mapping.

Functions

func BuildPayloadMeta

func BuildPayloadMeta(payload map[string]any, tpl Template) map[string]any

func DecryptLegacyValue

func DecryptLegacyValue(ciphertextHex string) (string, error)

DecryptLegacyValue decrypts a hex-encoded AES-256-GCM value produced by the pre-Epic-19 infra/crypto package. Format: hex(nonce || ciphertext || tag).

func DecryptPayload

func DecryptPayload(encrypted string) (map[string]any, error)

func EncryptPayload

func EncryptPayload(payload map[string]any) (string, error)

func EnsureRuntimeKey

func EnsureRuntimeKey(dataDir string) (warning string, generated bool, err error)

func ExtractSecretID

func ExtractSecretID(v string) (string, bool)

ExtractSecretID returns the UUID portion of a secretRef string. If v is not a valid secretRef, it returns ("", false).

func FirstStringFromPayload

func FirstStringFromPayload(payload map[string]any, keys ...string) string

FirstStringFromPayload probes the payload map for a non-empty string under each of the given keys in order, returning the first match. Returns "" if none of the keys yield a non-empty string value.

func IsAllowedAccessMode

func IsAllowedAccessMode(mode string) bool

func IsSecretRef

func IsSecretRef(v string) bool

IsSecretRef reports whether v is a secretRef pointer string.

func LoadKeyFromEnv

func LoadKeyFromEnv() error

LoadKeyFromEnv reads the AES-256 secret key from the APPOS_SECRET_KEY environment variable (base64-encoded 32-byte value) and stores it in memory. Must be called at startup before any encrypt/decrypt operations.

func LoadTemplatesFromDefaultPath

func LoadTemplatesFromDefaultPath() error

func LoadTemplatesFromFile

func LoadTemplatesFromFile(path string) error

func NormalizeAccessMode

func NormalizeAccessMode(mode string) string

func ReadSystemSingleValue

func ReadSystemSingleValue(secret *Secret) (string, error)

ReadSystemSingleValue reads the plaintext of a system-managed single-value secret.

func RegisterHooks

func RegisterHooks(app *pocketbase.PocketBase)

func ValidatePayload

func ValidatePayload(payload map[string]any, tpl Template) error

ValidatePayload checks that:

  1. The payload contains no keys outside the template field definitions.
  2. Every required field is present and non-empty.

This must be called before encrypting to prevent structurally invalid payloads from being stored.

func ValidatePolicy

func ValidatePolicy(raw map[string]any) map[string]string

ValidatePolicy checks the raw policy map for type/value errors. NOTE: This function mutates the input map — it normalises valid fields in-place (e.g. string integers → int, unknown access modes → default).

func ValidateRef

func ValidateRef(app core.App, secretID, userID string) error

ValidateRef checks that secretID exists and that userID is permitted to bind it as a secretRef in another module's config field.

Rules:

  • Secret must exist and not be revoked.
  • If scope == "user_private", only the owner (created_by == userID) may bind it.
  • If scope == "global" (or empty/unset), any authenticated user may bind it.

Types

type Policy

type Policy struct {
	RevealDisabled        bool   `json:"revealDisabled"`
	DefaultAccessMode     string `json:"defaultAccessMode"`
	ClipboardClearSeconds int    `json:"clipboardClearSeconds"`
	// MaxAgeDays is the maximum lifetime of a secret in days.
	// 0 means secrets never expire.
	MaxAgeDays int `json:"maxAgeDays"`
	// WarnBeforeExpiryDays controls how many days before expiry the UI shows a warning.
	// 0 means no warning is shown.
	WarnBeforeExpiryDays int `json:"warnBeforeExpiryDays"`
}

Policy is a value object that governs secret reveal and access behaviour for the entire platform. It is stored in sysconfig and applied at runtime.

func DefaultPolicy

func DefaultPolicy() Policy

func GetPolicy

func GetPolicy(app core.App) Policy

GetPolicy loads the current secret policy from sysconfig. Returns DefaultPolicy() when app is nil or when no policy has been configured.

func NormalizePolicy

func NormalizePolicy(raw map[string]any) Policy

func (Policy) ToMap

func (p Policy) ToMap() map[string]any

type ResolveError

type ResolveError struct {
	SecretID string
	Reason   string
	Cause    error
}

ResolveError is a structured error returned by Resolve and ValidateRef. Callers receive a machine-readable SecretID alongside the human-readable Reason.

func (*ResolveError) Error

func (e *ResolveError) Error() string

func (*ResolveError) Unwrap

func (e *ResolveError) Unwrap() error

type ResolveResult

type ResolveResult struct {
	TemplateID string
	Payload    map[string]any
	ExpiresAt  string // RFC 3339 or empty
}

ResolveResult carries the decrypted payload together with expiry metadata so callers (e.g. the HTTP resolve handler) can surface expiry status.

func Resolve

func Resolve(app core.App, secretID, userID string) (*ResolveResult, error)

Resolve looks up secretID from the DB, validates it is active, decrypts the payload, records last_used_at / last_used_by, emits a "secret.use" audit event, and returns a ResolveResult.

Format support:

  • New format (Epic 19): `payload_encrypted` field — AES-256-GCM, base64 JSON blob.
  • Legacy format (pre-Epic 19): `value` field — AES-256-GCM, hex-encoded, decrypted via DecryptLegacyValue. Result is wrapped as {"value": <plaintext>}.

The returned plaintext map MUST NOT be persisted by the caller (AC5). Resolve is synchronous and not cached in MVP.

type RevealResult

type RevealResult struct {
	Payload    map[string]any
	RecordID   string
	RecordName string
}

RevealResult carries the decrypted payload together with record metadata so the caller (HTTP handler) can write audit entries with full context.

func RevealPayload

func RevealPayload(app core.App, secretID string, auth *core.Record) (*RevealResult, error)

RevealPayload performs the full reveal operation atomically:

  1. Checks the platform-wide reveal policy.
  2. Validates ownership, revocation, and access_mode.
  3. Decrypts the payload.
  4. Downgrades reveal_once → use_only inside the same transaction.

It does NOT write audit — the caller is responsible for that because audit entries may include HTTP-layer context (IP, User-Agent).

type Secret

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

Secret is the aggregate root for the Security and Secret Management domain. It wraps a PocketBase record and exposes typed domain accessors, consolidating all domain rules about what a secret IS and what is ALLOWED on it.

Callers should obtain a Secret via From(rec) and use its methods for all domain-level decisions instead of inspecting raw Record fields directly.

func FindSystemSecretByNameAndType

func FindSystemSecretByNameAndType(app core.App, name, secretType string) (*Secret, error)

FindSystemSecretByNameAndType loads a system-managed secret record by its canonical name and type.

func From

func From(rec *core.Record) *Secret

From wraps a PocketBase record as a Secret aggregate root.

func UpsertSystemPayloadSecret

func UpsertSystemPayloadSecret(app core.App, secret *Secret, name, templateID string, payload map[string]any) (*Secret, error)

UpsertSystemPayloadSecret creates or updates a system-managed encrypted-payload secret.

func UpsertSystemSingleValue

func UpsertSystemSingleValue(app core.App, secret *Secret, name, secretType, plaintext string) (*Secret, error)

UpsertSystemSingleValue creates or updates a system-managed single-value secret using payload_encrypted. It sets the type and clears the legacy value field in the same save as UpsertSystemPayloadSecret.

func (*Secret) AccessMode

func (s *Secret) AccessMode() string

func (*Secret) CanBindByUser

func (s *Secret) CanBindByUser(userID string) bool

CanBindByUser reports whether userID may bind or consume this secret. Global secrets are available to any non-empty caller identity. user_private secrets are restricted to their creator.

func (*Secret) CanReveal

func (s *Secret) CanReveal() bool

CanReveal reports whether the current access_mode permits payload reveal.

func (*Secret) CreatedBy

func (s *Secret) CreatedBy() string

func (*Secret) ExpiresAt

func (s *Secret) ExpiresAt() time.Time

ExpiresAt returns the expiry time of this secret, or zero if no expiry is set.

func (*Secret) ID

func (s *Secret) ID() string

func (*Secret) IsExpired

func (s *Secret) IsExpired() bool

IsExpired reports whether the secret has passed its expiry time.

func (*Secret) IsExpiringSoon

func (s *Secret) IsExpiringSoon(warnDays int) bool

IsExpiringSoon reports whether the secret will expire within warnDays days. Returns false when warnDays is 0 or the secret has no expiry.

func (*Secret) IsOwnedBy

func (s *Secret) IsOwnedBy(auth *core.Record) bool

IsOwnedBy reports whether auth is permitted to manage this secret. Superusers may manage any secret; regular users only their own.

func (*Secret) IsRevoked

func (s *Secret) IsRevoked() bool

IsRevoked reports whether this secret has been revoked and can no longer be used.

func (*Secret) IsSystemManaged

func (s *Secret) IsSystemManaged() bool

IsSystemManaged reports whether this secret was provisioned by the platform itself. System-managed secrets are read-only and cannot be updated or deleted by users.

func (*Secret) Name

func (s *Secret) Name() string

func (*Secret) Record

func (s *Secret) Record() *core.Record

Record returns the underlying PocketBase record for persistence operations.

func (*Secret) Scope

func (s *Secret) Scope() string

func (*Secret) SetAccessMode

func (s *Secret) SetAccessMode(mode string)

SetAccessMode updates the access_mode field on the underlying record.

func (*Secret) Status

func (s *Secret) Status() string

func (*Secret) TemplateID

func (s *Secret) TemplateID() string

func (*Secret) Version

func (s *Secret) Version() int

type Template

type Template struct {
	ID          string          `json:"id"`
	Label       string          `json:"label"`
	Description string          `json:"description,omitempty"`
	Fields      []TemplateField `json:"fields"`
}

func FindTemplate

func FindTemplate(id string) (Template, bool)

func Templates

func Templates() []Template

type TemplateField

type TemplateField struct {
	Key       string `json:"key"`
	Label     string `json:"label"`
	Type      string `json:"type"`
	Required  bool   `json:"required"`
	Sensitive bool   `json:"sensitive"`
	Upload    bool   `json:"upload,omitempty"`
}

Jump to

Keyboard shortcuts

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