mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 08:01:16 +00:00
180 lines
5.2 KiB
Go
180 lines
5.2 KiB
Go
// Package instanceconfig describes the environment the running backend
|
|
// resolved, so an operator can see what a variable actually became.
|
|
//
|
|
// It never reads a second source of truth: every resolver calls the same
|
|
// config helper the runtime uses, so the page cannot drift from behaviour.
|
|
// Nothing here writes: the environment is authoritative.
|
|
package instanceconfig
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/warmbly/warmbly/internal/config"
|
|
)
|
|
|
|
// Groups the admin panel renders as sections.
|
|
const (
|
|
GroupDeployment = "deployment"
|
|
GroupAddresses = "addresses"
|
|
GroupDatabase = "database"
|
|
GroupCache = "cache"
|
|
GroupMail = "mail"
|
|
GroupAuth = "auth"
|
|
GroupEncryption = "encryption"
|
|
GroupStorage = "storage"
|
|
GroupEventBus = "eventbus"
|
|
GroupWorkers = "workers"
|
|
GroupTracking = "tracking"
|
|
GroupCaptcha = "captcha"
|
|
GroupObservability = "observability"
|
|
)
|
|
|
|
// Where a resolved value came from.
|
|
const (
|
|
SourceEnv = "env"
|
|
SourceDefault = "default"
|
|
SourceDerived = "derived"
|
|
SourceUnset = "unset"
|
|
)
|
|
|
|
// Whether a change needs a restart.
|
|
const (
|
|
ChangeBootOnly = "boot-only"
|
|
ChangePerRequest = "per-request"
|
|
)
|
|
|
|
// PublishedDefaults are the working secret values shipped in docker-compose.yml
|
|
// and the Makefile. They are public in this repository, so a deployment still
|
|
// running on one is not protected by it at all.
|
|
//
|
|
// This mirrors insecureDefaults in cmd/backend/boot.go; the two must stay
|
|
// identical or the health check and the boot refusal will disagree.
|
|
var PublishedDefaults = map[string]string{
|
|
"AUTH_SECRET": "local-dev-auth-secret-minimum-32-characters-long",
|
|
"KMS_LOCAL_MASTER_KEY": "Xr0JA7gqF2POy29a7MRByyqddivTNt8WOyKsOXklazk=",
|
|
"CREDENTIALS_ENCRYPTION_KEY": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
"INTERNAL_API_TOKEN": "local-dev-internal-token",
|
|
"SECRET_KEY_BASE": "local-development-secret-key-base-minimum-64-characters-for-phoenix",
|
|
}
|
|
|
|
// Runtime carries the facts only cmd/backend knows after boot: values it
|
|
// derived rather than read, and the outcome of its own preflight. A nil
|
|
// Runtime is valid; every resolver falls back to the environment.
|
|
type Runtime struct {
|
|
CORSOrigins []string
|
|
WebAuthnRPID string
|
|
WebAuthnRPOrigins []string
|
|
OIDCRedirectURL string
|
|
OIDCConfigured bool
|
|
// OIDCDiscoveryErr is the boot-time discovery failure, empty when discovery
|
|
// succeeded or was never attempted.
|
|
OIDCDiscoveryErr string
|
|
MailTransportKind string
|
|
MailTransportDesc string
|
|
MailDelivers bool
|
|
PasskeysUsable bool
|
|
// InsecureDefaults are the variables boot found still holding a published
|
|
// default value.
|
|
InsecureDefaults []string
|
|
Policy *config.AuthPolicy
|
|
WebsocketURL string
|
|
}
|
|
|
|
// Entry describes one self-host-relevant environment variable.
|
|
type Entry struct {
|
|
Key string
|
|
// Aliases are older names for the same setting. Any of them being set
|
|
// makes the source env.
|
|
Aliases []string
|
|
Group string
|
|
// RuntimeChangeable is ChangeBootOnly or ChangePerRequest.
|
|
RuntimeChangeable string
|
|
// Effect is one sentence saying what the resolved value does.
|
|
Effect string
|
|
DocsAnchor string
|
|
// WhenUnset is the source reported when no variable is set but a value
|
|
// still resolves. SourceDefault when empty.
|
|
WhenUnset string
|
|
Resolve func(rt *Runtime) string
|
|
}
|
|
|
|
// Resolved is one row of GET /admin/instance/config.
|
|
type Resolved struct {
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
// Source is one of the Source* constants.
|
|
Source string `json:"source"`
|
|
Sensitive bool `json:"sensitive"`
|
|
Set bool `json:"set"`
|
|
// Fingerprint is the first 4 hex characters of SHA-256 of a sensitive
|
|
// value, so two services can be compared without either being disclosed.
|
|
Fingerprint string `json:"fingerprint"`
|
|
Group string `json:"group"`
|
|
Effect string `json:"effect"`
|
|
Docs string `json:"docs"`
|
|
// RuntimeChangeable is one of the Change* constants.
|
|
RuntimeChangeable string `json:"runtime_changeable"`
|
|
}
|
|
|
|
// Entries resolves the whole table in declaration order.
|
|
func Entries(rt *Runtime) []Resolved {
|
|
out := make([]Resolved, 0, len(table))
|
|
for _, e := range table {
|
|
out = append(out, e.resolve(rt))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (e Entry) resolve(rt *Runtime) Resolved {
|
|
value := ""
|
|
if e.Resolve != nil {
|
|
value = strings.TrimSpace(e.Resolve(rt))
|
|
}
|
|
|
|
r := Resolved{
|
|
Key: e.Key,
|
|
Group: e.Group,
|
|
Effect: e.Effect,
|
|
Docs: e.DocsAnchor,
|
|
RuntimeChangeable: e.RuntimeChangeable,
|
|
Sensitive: Sensitive(e.Key),
|
|
Set: e.envSet(),
|
|
}
|
|
r.Source = e.source(r.Set, value)
|
|
|
|
// A sensitive value never leaves the process; the fingerprint is what an
|
|
// operator compares across services.
|
|
if r.Sensitive {
|
|
r.Fingerprint = Fingerprint(value)
|
|
return r
|
|
}
|
|
r.Value = value
|
|
return r
|
|
}
|
|
|
|
func (e Entry) envSet() bool {
|
|
if strings.TrimSpace(os.Getenv(e.Key)) != "" {
|
|
return true
|
|
}
|
|
for _, alias := range e.Aliases {
|
|
if strings.TrimSpace(os.Getenv(alias)) != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (e Entry) source(set bool, value string) string {
|
|
if set {
|
|
return SourceEnv
|
|
}
|
|
if value == "" {
|
|
return SourceUnset
|
|
}
|
|
if e.WhenUnset != "" {
|
|
return e.WhenUnset
|
|
}
|
|
return SourceDefault
|
|
}
|