config

package
v0.3.0 Latest Latest
Warning

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

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

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

func Load() (*Config, error)

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

func (c *Config) BaseOrigin() string

BaseOrigin returns scheme://host of the base URL, used by the CSRF origin check.

func (*Config) IsHTTPS

func (c *Config) IsHTTPS() bool

IsHTTPS reports whether the public base URL uses TLS (drives the cookie Secure flag).

func (*Config) V0SunsetPassed

func (c *Config) V0SunsetPassed(now time.Time) bool

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.

Jump to

Keyboard shortcuts

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