Documentation
¶
Overview ¶
Package config parses the process environment into one typed Config struct. It imports stdlib + the env parser only; nothing else in the tree configures itself. A parse failure reports every missing/invalid variable at once (see Load).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
BaseURL string `env:"OSCTF_BASE_URL,required"` // public origin
PublicHost string `env:"OSCTF_PUBLIC_HOST"` // host used in connection info; derived if empty
HTTPAddr string `env:"OSCTF_HTTP_ADDR" envDefault:":8080"`
DatabaseURL string `env:"OSCTF_DATABASE_URL,required"`
RedisURL string `env:"OSCTF_REDIS_URL,required"`
S3Endpoint string `env:"OSCTF_S3_ENDPOINT,required"`
S3AccessKey string `env:"OSCTF_S3_ACCESS_KEY,required"`
S3SecretKey string `env:"OSCTF_S3_SECRET_KEY,required"`
S3Bucket string `env:"OSCTF_S3_BUCKET" envDefault:"osctf"`
S3UseSSL bool `env:"OSCTF_S3_USE_SSL" envDefault:"false"`
// Admin seed credentials: required on first boot, validated by the seeder
// (not at parse time, so `platform migrate` runs without them).
AdminEmail string `env:"OSCTF_ADMIN_EMAIL"`
AdminPassword string `env:"OSCTF_ADMIN_PASSWORD"`
SessionTTL time.Duration `env:"OSCTF_SESSION_TTL" envDefault:"168h"`
RegistrationOpen bool `env:"OSCTF_REGISTRATION_OPEN" envDefault:"true"`
// APIV0Sunset is the date advertised in the /api/v0 Sunset header (RFC 3339; formatted
// to an HTTP-date at response time so the weekday is always correct). /api/v0 is the
// deprecated alias of /api/v1 and is removed no earlier than v0.4; this is the earliest
// advertised removal, not a promise to remove on that day. The default is deliberately a
// date the project can plausibly honour. If it passes while v0 is still mounted the
// server logs a startup warning — an advertised date nobody tracks is worse than none.
APIV0Sunset time.Time `env:"OSCTF_V0_SUNSET" envDefault:"2027-02-01T00:00:00Z"`
// AuthEmailLogin enables the built-in email/password login. Set false for an
// SSO-only deployment. On by default as a fail-closed break-glass path; the platform
// refuses to boot if this is false and no other auth provider is registered, since
// booting with no way to log in is worse than refusing to boot. Disabling it while
// relying solely on an external IdP means an IdP outage locks everyone out — keep a
// break-glass path (re-enable this via env in an emergency).
AuthEmailLogin bool `env:"OSCTF_AUTH_EMAIL_LOGIN" envDefault:"true"`
// AuthProvision is the provisioning policy for a first external (plugin) login that carries
// no existing binding: "open", "invite-only", or "off". Parsed at startup via
// auth.ParseProvisionPolicy, which rejects anything else rather than defaulting — guessing
// would choose a security posture for the operator.
//
// The default is invite-only: an external login attaches to an account an admin already
// created, and never creates one, so a compromised or careless provider cannot manufacture
// accounts. Set "open" for a public event where the identity provider is the front door.
AuthProvision string `env:"OSCTF_AUTH_PROVISION" envDefault:"invite-only"`
// Registration is unauthenticated, so its abuse limit can only key on the client IP
// -- and a venue is a hundred-plus players on one NAT registering in the first couple
// of minutes. The default is therefore generous per IP; tighten it for a public-
// internet deployment, or set the burst to 0 to disable the limit (and use
// OSCTF_REGISTRATION_OPEN=false to close registration for an invite-only event).
RegisterIPBurst int `env:"OSCTF_REGISTER_IP_BURST" envDefault:"500"` // registrations per window per IP (0 = disabled)
RegisterIPWindow time.Duration `env:"OSCTF_REGISTER_IP_WINDOW" envDefault:"600s"`
// Login is per-IP rate-limited too, so a shared-NAT venue logging in at event
// start is not throttled to a handful (GitHub issue #4, the login sibling of
// #1). Generous by default; the per-account login limit (5/5min) is the real
// credential-stuffing guard. 0 disables the per-IP login limit.
LoginIPBurst int `env:"OSCTF_LOGIN_IP_BURST" envDefault:"500"` // logins per window per IP (0 = disabled)
LoginIPWindow time.Duration `env:"OSCTF_LOGIN_IP_WINDOW" envDefault:"600s"`
// PasswordHashConcurrency bounds concurrent argon2id derivations (registration
// hashing + login verification + the unknown-email timing burn). Each costs
// ~64 MiB, so an unbounded burst can OOM a small host; the gate queues excess
// requests, then sheds them with 503 + Retry-After. 0 = derive from the host
// memory limit at startup (a quarter of memory / 64 MiB, clamped to [2,64]).
// Peak hashing memory ≈ value × 64 MiB (issue #3).
PasswordHashConcurrency int `env:"OSCTF_PASSWORD_HASH_CONCURRENCY" envDefault:"0"`
PasswordHashMaxWait time.Duration `env:"OSCTF_PASSWORD_HASH_MAX_WAIT" envDefault:"5s"`
TeamMaxSize int `env:"OSCTF_TEAM_MAX_SIZE" envDefault:"4"`
MaxAttachmentMB int `env:"OSCTF_MAX_ATTACHMENT_MB" envDefault:"100"`
PortRangeStart int `env:"OSCTF_PORT_RANGE_START" envDefault:"30000"`
PortRangeEnd int `env:"OSCTF_PORT_RANGE_END" envDefault:"32767"`
// Per-team instance scheduler (v0.2). See docs/v0.2/08-deployment.md.
InstanceTTL time.Duration `env:"OSCTF_INSTANCE_TTL" envDefault:"3600s"` // default per-team TTL
InstanceExtend time.Duration `env:"OSCTF_INSTANCE_EXTEND" envDefault:"1800s"` // added per Extend
InstanceMaxTTL time.Duration `env:"OSCTF_INSTANCE_MAX_TTL" envDefault:"14400s"` // max total lifetime
TeamInstanceQuota int `env:"OSCTF_TEAM_INSTANCE_QUOTA" envDefault:"3"` // concurrent per team
InstanceReapAfter time.Duration `env:"OSCTF_INSTANCE_REAP_AFTER" envDefault:"900s"` // reap stuck pending/error rows older than this (frees leaked ports)
FlagPrefix string `env:"OSCTF_FLAG_PREFIX" envDefault:"osctf"` // per-instance flag prefix
DockerHost string `env:"OSCTF_DOCKER_HOST"`
SeedExamples bool `env:"OSCTF_SEED_EXAMPLES" envDefault:"true"`
ExamplesDir string `env:"OSCTF_EXAMPLES_DIR" envDefault:"examples"`
TrustProxy bool `env:"OSCTF_TRUST_PROXY" envDefault:"false"`
// AllowUnisolatedInstances overrides the fail-closed refusal to start CONTAINER challenges when
// the Docker daemon does not enforce per-team network isolation (Docker Desktop; issue #2).
// Default false = refuse, so one team cannot reach another team's instance by accident. Set true
// ONLY for a local trial / dev — never a real event; when set, the platform logs loudly at boot
// and on every unisolated deploy (docs/v0.2/03-runtime.md).
AllowUnisolatedInstances bool `env:"OSCTF_ALLOW_UNISOLATED_INSTANCES" envDefault:"false"`
// Live-scoreboard WebSocket admission control. The endpoint is public and
// unauthenticated; these caps stop a client from opening connections until the
// process dies. Caps and the handshake rate key on the authenticated user where a
// session exists, falling back to the client IP for anonymous connections — so a
// campus/venue NAT of logged-in players is not throttled as a single IP (the
// shared-IP class of GitHub issue #1). Raise the per-connection cap for large events
// with many anonymous scoreboard viewers behind one NAT.
WSMaxConns int `env:"OSCTF_WS_MAX_CONNS" envDefault:"20000"` // global live-connection ceiling
WSMaxConnsPerConn int `env:"OSCTF_WS_MAX_CONNS_PER_CLIENT" envDefault:"256"` // per user (or per anon IP)
WSHandshakeBurst int `env:"OSCTF_WS_HANDSHAKE_BURST" envDefault:"600"` // handshakes per client per window
WSHandshakeWindow time.Duration `env:"OSCTF_WS_HANDSHAKE_WINDOW" envDefault:"60s"`
// Plugin in-flight budget. PerPlugin bounds one plugin; Total is the global cap claimed from
// the shared fd accountant (a blocked call pins its inbound fd), so WebSockets and plugins
// divide one fd budget with the reserve counted once. QueueWait is how long a call queues at
// a full cap before it sheds 503.
PluginMaxInflight int `env:"OSCTF_PLUGIN_MAX_INFLIGHT" envDefault:"64"`
PluginMaxInflightTotal int `env:"OSCTF_PLUGIN_MAX_INFLIGHT_TOTAL" envDefault:"256"`
PluginQueueWait time.Duration `env:"OSCTF_PLUGIN_QUEUE_WAIT" envDefault:"1s"`
PluginDrainTimeout time.Duration `env:"OSCTF_PLUGIN_DRAIN_TIMEOUT" envDefault:"30s"`
// Plugin discovery + supervision. Enabled=false is pure-core mode (== v0.2); a default
// deployment has no plugins dir, which is a silent no-op. RuntimeDir (pidfiles for the boot
// orphan sweep) defaults to an OS-temp subdir, resolved in main when empty.
PluginsEnabled bool `env:"OSCTF_PLUGINS_ENABLED" envDefault:"true"`
PluginsDir string `env:"OSCTF_PLUGINS_DIR" envDefault:"./plugins"`
RuntimeDir string `env:"OSCTF_RUNTIME_DIR" envDefault:""`
PluginHealthStable time.Duration `env:"OSCTF_PLUGIN_HEALTH_STABLE" envDefault:"60s"`
PluginRestartCap int `env:"OSCTF_PLUGIN_RESTART_CAP" envDefault:"5"`
PluginStartTimeout time.Duration `env:"OSCTF_PLUGIN_START_TIMEOUT" envDefault:"30s"`
// PluginScoringFallback controls how a plugin-scored solve is valued when its plugin is down at
// solve time (#9). Default ON: record the static value so the participant sees a real number and
// the board is right — the recommended default (docs/v0.1/10-deployment.md), since it avoids a
// participant-visible 0. OFF records 'pending' (resolves to 0 until the repair worker fills the
// plugin's value on recovery) for deployments that must never show a fallback value.
PluginScoringFallback bool `env:"OSCTF_PLUGIN_SCORING_FALLBACK" envDefault:"true"`
// API-token rate limit, keyed by TOKEN IDENTITY (not IP or account). Automation traffic
// is legitimately unlike a browser's, and the venue-NAT lesson (issue #1) applies to bots:
// an IP-keyed limit throttles a CI runner or many tokens behind one egress IP. Generous by
// default (100 req/s sustained); 0 disables.
TokenRateBurst int `env:"OSCTF_TOKEN_RATE_BURST" envDefault:"6000"`
TokenRateWindow time.Duration `env:"OSCTF_TOKEN_RATE_WINDOW" envDefault:"60s"`
// Token lifetime policy: a create without an explicit lifetime gets the default; a
// requested lifetime above the max is rejected. Tokens are never immortal — a permanent
// admin-scoped credential is exactly what a security product should not mint by default.
TokenDefaultTTL time.Duration `env:"OSCTF_TOKEN_DEFAULT_TTL" envDefault:"2160h"` // 90 days
TokenMaxTTL time.Duration `env:"OSCTF_TOKEN_MAX_TTL" envDefault:"8760h"` // 365 days
CORSDevOrigin string `env:"OSCTF_CORS_DEV_ORIGIN"`
LogFormat string `env:"OSCTF_LOG_FORMAT" envDefault:"json"`
LogLevel string `env:"OSCTF_LOG_LEVEL" envDefault:"info"`
// contains filtered or unexported fields
}
Config is the fully-resolved configuration. Field docs point at the env var. The complete reference lives in docs/v0.1/10-deployment.md.
func Load ¶
Load parses the environment into a Config, applies derived defaults, and validates cross-field constraints. Every problem found is returned in a single error so the operator can fix them all at once.
func (*Config) BaseOrigin ¶
BaseOrigin returns scheme://host of the base URL, used by the CSRF origin check.
func (*Config) IsHTTPS ¶
IsHTTPS reports whether the public base URL uses TLS (drives the cookie Secure flag).
func (*Config) V0SunsetPassed ¶
V0SunsetPassed reports whether the advertised /api/v0 Sunset has already elapsed as of now. main uses it to warn at startup: serving past an advertised sunset date that nobody is tracking is worse than advertising none.