Files
galaxy-game/gateway/internal/config/config.go
T
2026-05-07 00:58:53 +03:00

1317 lines
52 KiB
Go

// Package config loads process-level gateway configuration from environment
// variables.
package config
import (
"fmt"
"net/url"
"os"
"strconv"
"strings"
"time"
"galaxy/redisconn"
)
const gatewayRedisEnvPrefix = "GATEWAY"
const (
// shutdownTimeoutEnvVar names the environment variable that controls the
// maximum time granted to each component shutdown call.
shutdownTimeoutEnvVar = "GATEWAY_SHUTDOWN_TIMEOUT"
// logLevelEnvVar names the environment variable that configures the process
// log level used by structured JSON logging.
logLevelEnvVar = "GATEWAY_LOG_LEVEL"
// publicHTTPAddrEnvVar names the environment variable that configures the
// public REST listener address.
publicHTTPAddrEnvVar = "GATEWAY_PUBLIC_HTTP_ADDR"
// publicHTTPReadHeaderTimeoutEnvVar names the environment variable that
// configures the maximum time allowed to read public REST request headers.
publicHTTPReadHeaderTimeoutEnvVar = "GATEWAY_PUBLIC_HTTP_READ_HEADER_TIMEOUT"
// publicHTTPReadTimeoutEnvVar names the environment variable that configures
// the maximum time allowed to read the full public REST request.
publicHTTPReadTimeoutEnvVar = "GATEWAY_PUBLIC_HTTP_READ_TIMEOUT"
// publicHTTPIdleTimeoutEnvVar names the environment variable that configures
// the keep-alive idle timeout for the public REST listener.
publicHTTPIdleTimeoutEnvVar = "GATEWAY_PUBLIC_HTTP_IDLE_TIMEOUT"
// publicAuthUpstreamTimeoutEnvVar names the environment variable that
// configures the timeout budget used for public auth upstream calls.
publicAuthUpstreamTimeoutEnvVar = "GATEWAY_PUBLIC_AUTH_UPSTREAM_TIMEOUT"
// backendHTTPURLEnvVar names the environment variable that configures
// the absolute base URL of the consolidated backend HTTP listener used
// for public auth, internal session lookup, and authenticated user /
// lobby commands.
backendHTTPURLEnvVar = "GATEWAY_BACKEND_HTTP_URL"
// backendGRPCPushURLEnvVar names the environment variable that
// configures the dial target of backend's gRPC `Push.SubscribePush`
// listener.
backendGRPCPushURLEnvVar = "GATEWAY_BACKEND_GRPC_PUSH_URL"
// backendGatewayClientIDEnvVar names the environment variable that
// configures the durable identifier this gateway instance presents to
// backend in `GatewaySubscribeRequest.gateway_client_id`.
backendGatewayClientIDEnvVar = "GATEWAY_BACKEND_GATEWAY_CLIENT_ID"
// backendHTTPTimeoutEnvVar names the environment variable that
// configures the per-call timeout applied to backend HTTP requests.
backendHTTPTimeoutEnvVar = "GATEWAY_BACKEND_HTTP_TIMEOUT"
// backendPushReconnectBaseBackoffEnvVar names the environment variable
// that configures the starting delay between reconnect attempts of the
// gRPC SubscribePush stream.
backendPushReconnectBaseBackoffEnvVar = "GATEWAY_BACKEND_PUSH_RECONNECT_BASE_BACKOFF"
// backendPushReconnectMaxBackoffEnvVar names the environment variable
// that configures the upper bound for exponential reconnect delays.
backendPushReconnectMaxBackoffEnvVar = "GATEWAY_BACKEND_PUSH_RECONNECT_MAX_BACKOFF"
// adminHTTPAddrEnvVar names the environment variable that configures the
// private admin HTTP listener address. When it is empty, the admin listener
// remains disabled.
adminHTTPAddrEnvVar = "GATEWAY_ADMIN_HTTP_ADDR"
// adminHTTPReadHeaderTimeoutEnvVar names the environment variable that
// configures the maximum time allowed to read admin listener request
// headers.
adminHTTPReadHeaderTimeoutEnvVar = "GATEWAY_ADMIN_HTTP_READ_HEADER_TIMEOUT"
// adminHTTPReadTimeoutEnvVar names the environment variable that configures
// the maximum time allowed to read one admin listener request.
adminHTTPReadTimeoutEnvVar = "GATEWAY_ADMIN_HTTP_READ_TIMEOUT"
// adminHTTPIdleTimeoutEnvVar names the environment variable that configures
// the keep-alive idle timeout for the admin listener.
adminHTTPIdleTimeoutEnvVar = "GATEWAY_ADMIN_HTTP_IDLE_TIMEOUT"
// authenticatedGRPCAddrEnvVar names the environment variable that configures
// the authenticated gRPC listener address.
authenticatedGRPCAddrEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ADDR"
// authenticatedGRPCConnectionTimeoutEnvVar names the environment variable
// that configures the inbound connection handshake timeout for the
// authenticated gRPC listener.
authenticatedGRPCConnectionTimeoutEnvVar = "GATEWAY_AUTHENTICATED_GRPC_CONNECTION_TIMEOUT"
// authenticatedGRPCDownstreamTimeoutEnvVar names the environment variable
// that configures the timeout budget used for downstream unary execution.
authenticatedGRPCDownstreamTimeoutEnvVar = "GATEWAY_AUTHENTICATED_DOWNSTREAM_TIMEOUT"
// authenticatedGRPCFreshnessWindowEnvVar names the environment variable that
// configures the accepted client timestamp skew window for authenticated
// gRPC requests.
authenticatedGRPCFreshnessWindowEnvVar = "GATEWAY_AUTHENTICATED_GRPC_FRESHNESS_WINDOW"
// authenticatedGRPCIPRateLimitRequestsEnvVar names the environment
// variable that configures the authenticated gRPC per-IP request budget per
// window.
authenticatedGRPCIPRateLimitRequestsEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_REQUESTS"
// authenticatedGRPCIPRateLimitWindowEnvVar names the environment variable
// that configures the authenticated gRPC per-IP rate-limit window.
authenticatedGRPCIPRateLimitWindowEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_WINDOW"
// authenticatedGRPCIPRateLimitBurstEnvVar names the environment variable
// that configures the authenticated gRPC per-IP rate-limit burst.
authenticatedGRPCIPRateLimitBurstEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_IP_RATE_LIMIT_BURST"
// authenticatedGRPCSessionRateLimitRequestsEnvVar names the environment
// variable that configures the authenticated gRPC per-session request
// budget per window.
authenticatedGRPCSessionRateLimitRequestsEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_REQUESTS"
// authenticatedGRPCSessionRateLimitWindowEnvVar names the environment
// variable that configures the authenticated gRPC per-session rate-limit
// window.
authenticatedGRPCSessionRateLimitWindowEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_WINDOW"
// authenticatedGRPCSessionRateLimitBurstEnvVar names the environment
// variable that configures the authenticated gRPC per-session rate-limit
// burst.
authenticatedGRPCSessionRateLimitBurstEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_SESSION_RATE_LIMIT_BURST"
// authenticatedGRPCUserRateLimitRequestsEnvVar names the environment
// variable that configures the authenticated gRPC per-user request budget
// per window.
authenticatedGRPCUserRateLimitRequestsEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_REQUESTS"
// authenticatedGRPCUserRateLimitWindowEnvVar names the environment
// variable that configures the authenticated gRPC per-user rate-limit
// window.
authenticatedGRPCUserRateLimitWindowEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_WINDOW"
// authenticatedGRPCUserRateLimitBurstEnvVar names the environment variable
// that configures the authenticated gRPC per-user rate-limit burst.
authenticatedGRPCUserRateLimitBurstEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_USER_RATE_LIMIT_BURST"
// authenticatedGRPCMessageClassRateLimitRequestsEnvVar names the
// environment variable that configures the authenticated gRPC per-message
// class request budget per window.
authenticatedGRPCMessageClassRateLimitRequestsEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_REQUESTS"
// authenticatedGRPCMessageClassRateLimitWindowEnvVar names the environment
// variable that configures the authenticated gRPC per-message-class
// rate-limit window.
authenticatedGRPCMessageClassRateLimitWindowEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_WINDOW"
// authenticatedGRPCMessageClassRateLimitBurstEnvVar names the environment
// variable that configures the authenticated gRPC per-message-class
// rate-limit burst.
authenticatedGRPCMessageClassRateLimitBurstEnvVar = "GATEWAY_AUTHENTICATED_GRPC_ANTI_ABUSE_MESSAGE_CLASS_RATE_LIMIT_BURST"
// sessionCacheMaxEntriesEnvVar names the environment variable that configures
// the in-memory session cache LRU bound (entries).
sessionCacheMaxEntriesEnvVar = "GATEWAY_SESSION_CACHE_MAX_ENTRIES"
// sessionCacheTTLEnvVar names the environment variable that configures the
// in-memory session cache safety-net TTL applied to every cached entry.
sessionCacheTTLEnvVar = "GATEWAY_SESSION_CACHE_TTL"
// replayRedisKeyPrefixEnvVar names the environment variable that configures
// the Redis key prefix used for authenticated replay reservations.
replayRedisKeyPrefixEnvVar = "GATEWAY_REPLAY_REDIS_KEY_PREFIX"
// replayRedisReserveTimeoutEnvVar names the environment variable that
// configures the timeout used for authenticated replay reservations and
// startup connectivity checks.
replayRedisReserveTimeoutEnvVar = "GATEWAY_REPLAY_REDIS_RESERVE_TIMEOUT"
// responseSignerPrivateKeyPEMPathEnvVar names the environment variable that
// configures the path to the PKCS#8 PEM-encoded Ed25519 private key used to
// sign authenticated unary responses and stream events.
responseSignerPrivateKeyPEMPathEnvVar = "GATEWAY_RESPONSE_SIGNER_PRIVATE_KEY_PEM_PATH"
// publicAuthMaxBodyBytesEnvVar names the environment variable that
// configures the maximum accepted request body size for public_auth.
publicAuthMaxBodyBytesEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_MAX_BODY_BYTES"
// publicAuthRateLimitRequestsEnvVar names the environment variable that
// configures the public_auth request budget per window.
publicAuthRateLimitRequestsEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_REQUESTS"
// publicAuthRateLimitWindowEnvVar names the environment variable that
// configures the public_auth rate-limit window.
publicAuthRateLimitWindowEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_WINDOW"
// publicAuthRateLimitBurstEnvVar names the environment variable that
// configures the public_auth rate-limit burst.
publicAuthRateLimitBurstEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_AUTH_RATE_LIMIT_BURST"
// browserBootstrapMaxBodyBytesEnvVar names the environment variable that
// configures the maximum accepted request body size for browser_bootstrap.
browserBootstrapMaxBodyBytesEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_BOOTSTRAP_MAX_BODY_BYTES"
// browserBootstrapRateLimitRequestsEnvVar names the environment variable
// that configures the browser_bootstrap request budget per window.
browserBootstrapRateLimitRequestsEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_BOOTSTRAP_RATE_LIMIT_REQUESTS"
// browserBootstrapRateLimitWindowEnvVar names the environment variable that
// configures the browser_bootstrap rate-limit window.
browserBootstrapRateLimitWindowEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_BOOTSTRAP_RATE_LIMIT_WINDOW"
// browserBootstrapRateLimitBurstEnvVar names the environment variable that
// configures the browser_bootstrap rate-limit burst.
browserBootstrapRateLimitBurstEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_BOOTSTRAP_RATE_LIMIT_BURST"
// browserAssetMaxBodyBytesEnvVar names the environment variable that
// configures the maximum accepted request body size for browser_asset.
browserAssetMaxBodyBytesEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_ASSET_MAX_BODY_BYTES"
// browserAssetRateLimitRequestsEnvVar names the environment variable that
// configures the browser_asset request budget per window.
browserAssetRateLimitRequestsEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_ASSET_RATE_LIMIT_REQUESTS"
// browserAssetRateLimitWindowEnvVar names the environment variable that
// configures the browser_asset rate-limit window.
browserAssetRateLimitWindowEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_ASSET_RATE_LIMIT_WINDOW"
// browserAssetRateLimitBurstEnvVar names the environment variable that
// configures the browser_asset rate-limit burst.
browserAssetRateLimitBurstEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_BROWSER_ASSET_RATE_LIMIT_BURST"
// publicMiscMaxBodyBytesEnvVar names the environment variable that
// configures the maximum accepted request body size for public_misc.
publicMiscMaxBodyBytesEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_MAX_BODY_BYTES"
// publicMiscRateLimitRequestsEnvVar names the environment variable that
// configures the public_misc request budget per window.
publicMiscRateLimitRequestsEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_REQUESTS"
// publicMiscRateLimitWindowEnvVar names the environment variable that
// configures the public_misc rate-limit window.
publicMiscRateLimitWindowEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_WINDOW"
// publicMiscRateLimitBurstEnvVar names the environment variable that
// configures the public_misc rate-limit burst.
publicMiscRateLimitBurstEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_PUBLIC_MISC_RATE_LIMIT_BURST"
// sendEmailCodeIdentityRateLimitRequestsEnvVar names the environment
// variable that configures the send-email-code identity request budget per
// window.
sendEmailCodeIdentityRateLimitRequestsEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS"
// sendEmailCodeIdentityRateLimitWindowEnvVar names the environment variable
// that configures the send-email-code identity rate-limit window.
sendEmailCodeIdentityRateLimitWindowEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_WINDOW"
// sendEmailCodeIdentityRateLimitBurstEnvVar names the environment variable
// that configures the send-email-code identity rate-limit burst.
sendEmailCodeIdentityRateLimitBurstEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_SEND_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST"
// confirmEmailCodeIdentityRateLimitRequestsEnvVar names the environment
// variable that configures the confirm-email-code identity request budget
// per window.
confirmEmailCodeIdentityRateLimitRequestsEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_REQUESTS"
// confirmEmailCodeIdentityRateLimitWindowEnvVar names the environment
// variable that configures the confirm-email-code identity rate-limit
// window.
confirmEmailCodeIdentityRateLimitWindowEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_WINDOW"
// confirmEmailCodeIdentityRateLimitBurstEnvVar names the environment
// variable that configures the confirm-email-code identity rate-limit burst.
confirmEmailCodeIdentityRateLimitBurstEnvVar = "GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_CONFIRM_EMAIL_CODE_IDENTITY_RATE_LIMIT_BURST"
// defaultShutdownTimeout is applied when shutdownTimeoutEnvVar is absent.
defaultShutdownTimeout = 5 * time.Second
// defaultLogLevel is applied when logLevelEnvVar is absent.
defaultLogLevel = "info"
// defaultPublicHTTPAddr is applied when publicHTTPAddrEnvVar is absent.
defaultPublicHTTPAddr = ":8080"
defaultPublicHTTPReadHeaderTimeout = 2 * time.Second
defaultPublicHTTPReadTimeout = 10 * time.Second
defaultPublicHTTPIdleTimeout = time.Minute
defaultPublicAuthUpstreamTimeout = 3 * time.Second
defaultAdminHTTPReadHeaderTimeout = 2 * time.Second
defaultAdminHTTPReadTimeout = 10 * time.Second
defaultAdminHTTPIdleTimeout = time.Minute
// defaultAuthenticatedGRPCAddr is applied when
// authenticatedGRPCAddrEnvVar is absent.
defaultAuthenticatedGRPCAddr = ":9090"
defaultAuthenticatedGRPCConnectionTimeout = 5 * time.Second
defaultAuthenticatedGRPCDownstreamTimeout = 5 * time.Second
defaultAuthenticatedGRPCFreshnessWindow = 5 * time.Minute
defaultAuthenticatedGRPCIPRateLimitRequests = 120
defaultAuthenticatedGRPCIPRateLimitBurst = 40
defaultAuthenticatedGRPCSessionRateLimitRequests = 60
defaultAuthenticatedGRPCSessionRateLimitBurst = 20
defaultAuthenticatedGRPCUserRateLimitRequests = 120
defaultAuthenticatedGRPCUserRateLimitBurst = 40
defaultAuthenticatedGRPCMessageClassRateLimitRequests = 60
defaultAuthenticatedGRPCMessageClassRateLimitBurst = 20
defaultSessionCacheMaxEntries = 50_000
defaultSessionCacheTTL = 10 * time.Minute
defaultReplayRedisKeyPrefix = "gateway:replay:"
defaultReplayRedisReserveTimeout = 250 * time.Millisecond
defaultBackendHTTPTimeout = 5 * time.Second
defaultBackendPushReconnectBaseBackoff = 250 * time.Millisecond
defaultBackendPushReconnectMaxBackoff = 30 * time.Second
defaultPublicAuthMaxBodyBytes = int64(8192)
defaultPublicAuthRateLimitRequests = 30
defaultPublicAuthRateLimitBurst = 10
defaultBrowserBootstrapRateLimitRequests = 60
defaultBrowserBootstrapRateLimitBurst = 20
defaultBrowserAssetRateLimitRequests = 300
defaultBrowserAssetRateLimitBurst = 80
defaultPublicMiscRateLimitRequests = 30
defaultPublicMiscRateLimitBurst = 10
defaultSendEmailCodeIdentityRateLimitRequests = 3
defaultSendEmailCodeIdentityRateLimitBurst = 1
defaultConfirmEmailCodeIdentityRateLimitRequests = 6
defaultConfirmEmailCodeIdentityRateLimitBurst = 2
)
var (
defaultClassRateLimitWindow = time.Minute
defaultIdentityRateLimitWindow = 10 * time.Minute
)
// RateLimitConfig describes a single rate-limit budget.
type RateLimitConfig struct {
// Requests is the number of accepted requests replenished per Window.
Requests int
// Window is the interval over which Requests are replenished.
Window time.Duration
// Burst is the maximum number of immediately available tokens.
Burst int
}
// PublicRateLimitConfig identifies the generic rate-limit budget shape used by
// public REST policy.
type PublicRateLimitConfig = RateLimitConfig
// AuthenticatedRateLimitConfig identifies the generic rate-limit budget shape
// used by authenticated gRPC policy.
type AuthenticatedRateLimitConfig = RateLimitConfig
// PublicRoutePolicyConfig describes the anti-abuse policy enforced for one
// stable public REST traffic class.
type PublicRoutePolicyConfig struct {
// MaxBodyBytes is the maximum accepted request body size. Zero means that
// the request must not carry a body.
MaxBodyBytes int64
// RateLimit configures the per-IP budget for the route class.
RateLimit PublicRateLimitConfig
}
// PublicAuthIdentityPolicyConfig describes the additional identity-based
// limiter applied to one public auth command.
type PublicAuthIdentityPolicyConfig struct {
// RateLimit configures the accepted request budget for one normalized public
// auth identity key.
RateLimit PublicRateLimitConfig
}
// PublicHTTPAntiAbuseConfig describes the public REST anti-abuse policy used
// before route handling.
type PublicHTTPAntiAbuseConfig struct {
// PublicAuth applies to the stable public_auth route class.
PublicAuth PublicRoutePolicyConfig
// BrowserBootstrap applies to the stable browser_bootstrap route class.
BrowserBootstrap PublicRoutePolicyConfig
// BrowserAsset applies to the stable browser_asset route class.
BrowserAsset PublicRoutePolicyConfig
// PublicMisc applies to the stable public_misc route class.
PublicMisc PublicRoutePolicyConfig
// SendEmailCodeIdentity applies the additional identity limiter for
// send-email-code.
SendEmailCodeIdentity PublicAuthIdentityPolicyConfig
// ConfirmEmailCodeIdentity applies the additional identity limiter for
// confirm-email-code.
ConfirmEmailCodeIdentity PublicAuthIdentityPolicyConfig
}
// AuthenticatedGRPCAntiAbuseConfig describes the authenticated gRPC
// rate-limit budgets enforced after request authenticity has been established.
type AuthenticatedGRPCAntiAbuseConfig struct {
// IP applies to the transport peer IP derived from the gRPC connection.
IP AuthenticatedRateLimitConfig
// Session applies to the authenticated device_session_id.
Session AuthenticatedRateLimitConfig
// User applies to the authenticated user_id resolved from SessionCache.
User AuthenticatedRateLimitConfig
// MessageClass applies to the current authenticated message class. The
// gateway uses the full message_type literal as the stable v1 class key.
MessageClass AuthenticatedRateLimitConfig
}
// PublicHTTPConfig describes the public unauthenticated REST listener exposed
// by the gateway.
type PublicHTTPConfig struct {
// Addr is the TCP listen address used by the public REST server.
Addr string
// ReadHeaderTimeout bounds how long the listener may spend reading request
// headers before the gateway rejects the connection.
ReadHeaderTimeout time.Duration
// ReadTimeout bounds how long the listener may spend reading one public
// request.
ReadTimeout time.Duration
// IdleTimeout bounds how long the listener keeps an idle keep-alive
// connection open.
IdleTimeout time.Duration
// AuthUpstreamTimeout bounds one public auth adapter call.
AuthUpstreamTimeout time.Duration
// AntiAbuse configures the public REST anti-abuse middleware.
AntiAbuse PublicHTTPAntiAbuseConfig
}
// BackendConfig describes the consolidated backend service the gateway
// talks to. Every authenticated and public HTTP request is forwarded to
// `HTTPBaseURL`; the gRPC `Push.SubscribePush` stream is opened against
// `GRPCPushURL`.
type BackendConfig struct {
// HTTPBaseURL is the absolute base URL of the backend HTTP listener
// (`/api/v1/{public,user,internal}/*`). Required.
HTTPBaseURL string
// GRPCPushURL is the dial target of the backend `Push.SubscribePush`
// listener (`host:port`). Required.
GRPCPushURL string
// GatewayClientID is the durable identifier this gateway instance
// presents to backend in `GatewaySubscribeRequest.gateway_client_id`.
// Required.
GatewayClientID string
// HTTPTimeout bounds individual REST calls. Must be positive.
HTTPTimeout time.Duration
// PushReconnectBaseBackoff is the starting delay between reconnect
// attempts of `Push.SubscribePush`. Must be positive.
PushReconnectBaseBackoff time.Duration
// PushReconnectMaxBackoff is the upper bound for exponential
// reconnect delays. Must be greater than or equal to
// PushReconnectBaseBackoff.
PushReconnectMaxBackoff time.Duration
}
// AdminHTTPConfig describes the private operational HTTP listener used for
// metrics exposure. The listener remains disabled when Addr is empty.
type AdminHTTPConfig struct {
// Addr is the TCP listen address used by the admin HTTP server. An empty
// value disables the listener.
Addr string
// ReadHeaderTimeout bounds how long the listener may spend reading request
// headers before the gateway rejects the connection.
ReadHeaderTimeout time.Duration
// ReadTimeout bounds how long the listener may spend reading one admin
// request.
ReadTimeout time.Duration
// IdleTimeout bounds how long the listener keeps an idle keep-alive
// connection open.
IdleTimeout time.Duration
}
// AuthenticatedGRPCConfig describes the authenticated gRPC listener exposed by
// the gateway.
type AuthenticatedGRPCConfig struct {
// Addr is the TCP listen address used by the authenticated gRPC server.
Addr string
// ConnectionTimeout bounds one inbound connection handshake.
ConnectionTimeout time.Duration
// DownstreamTimeout bounds one downstream unary execution after the request
// has passed the full authenticated ingress pipeline.
DownstreamTimeout time.Duration
// FreshnessWindow is the accepted skew window around current server time
// used for client request timestamps.
FreshnessWindow time.Duration
// AntiAbuse configures the authenticated gRPC rate limits enforced after
// the request passes the transport authenticity checks.
AntiAbuse AuthenticatedGRPCAntiAbuseConfig
}
// SessionCacheConfig describes the bounds of the gateway's in-memory
// session cache. The cache fronts every authenticated request and
// falls back to a synchronous backend lookup on miss; push-event
// driven invalidations flip cached records to revoked status without
// a backend roundtrip.
type SessionCacheConfig struct {
// MaxEntries bounds the LRU. Zero or negative values fall back to
// the package default at construction time.
MaxEntries int
// TTL is the safety-net freshness window applied to every cached
// entry. Zero or negative values fall back to the package default.
TTL time.Duration
}
// ReplayRedisConfig describes the Redis namespace and timeout used for
// authenticated replay reservations.
type ReplayRedisConfig struct {
// KeyPrefix is prepended to every ReplayStore Redis key.
KeyPrefix string
// ReserveTimeout bounds individual ReplayStore Redis operations.
ReserveTimeout time.Duration
}
// ResponseSignerConfig describes the private-key material used to sign
// authenticated unary responses and stream events.
type ResponseSignerConfig struct {
// PrivateKeyPEMPath is the filesystem path to the PKCS#8 PEM-encoded
// Ed25519 private key loaded during startup.
PrivateKeyPEMPath string
}
// LoggingConfig describes the process-wide structured logging settings.
type LoggingConfig struct {
// Level is the configured minimum log level literal.
Level string
}
// Config describes process-wide settings required to start and stop the
// gateway safely.
type Config struct {
// ShutdownTimeout limits how long each component may spend in Shutdown
// before the gateway reports a timeout.
ShutdownTimeout time.Duration
// Logging configures the process-wide structured logger.
Logging LoggingConfig
// PublicHTTP configures the public unauthenticated REST listener.
PublicHTTP PublicHTTPConfig
// Backend configures the consolidated backend the gateway forwards
// every public auth and authenticated user/lobby request to and the
// gRPC `Push.SubscribePush` stream consumed for inbound events.
Backend BackendConfig
// AdminHTTP configures the optional private admin listener used for metrics
// exposure.
AdminHTTP AdminHTTPConfig
// AuthenticatedGRPC configures the authenticated gRPC listener.
AuthenticatedGRPC AuthenticatedGRPCConfig
// Redis carries the master/replica/password connection topology used
// by the anti-replay reservation store, sourced from the
// GATEWAY_REDIS_* environment variables managed by `pkg/redisconn`.
// The implementation dropped session cache projection and the two Redis
// Streams; Redis is now used only for replay reservations.
Redis redisconn.Config
// SessionCache configures the in-memory session cache fronting
// every authenticated request.
SessionCache SessionCacheConfig
// ReplayRedis configures the Redis-backed authenticated ReplayStore.
ReplayRedis ReplayRedisConfig
// ResponseSigner configures the authenticated response and event signer
// loaded during startup.
ResponseSigner ResponseSignerConfig
}
// DefaultPublicHTTPConfig returns the default listener and anti-abuse settings
// for the public REST surface.
func DefaultPublicHTTPConfig() PublicHTTPConfig {
return PublicHTTPConfig{
Addr: defaultPublicHTTPAddr,
ReadHeaderTimeout: defaultPublicHTTPReadHeaderTimeout,
ReadTimeout: defaultPublicHTTPReadTimeout,
IdleTimeout: defaultPublicHTTPIdleTimeout,
AuthUpstreamTimeout: defaultPublicAuthUpstreamTimeout,
AntiAbuse: PublicHTTPAntiAbuseConfig{
PublicAuth: PublicRoutePolicyConfig{
MaxBodyBytes: defaultPublicAuthMaxBodyBytes,
RateLimit: PublicRateLimitConfig{
Requests: defaultPublicAuthRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultPublicAuthRateLimitBurst,
},
},
BrowserBootstrap: PublicRoutePolicyConfig{
RateLimit: PublicRateLimitConfig{
Requests: defaultBrowserBootstrapRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultBrowserBootstrapRateLimitBurst,
},
},
BrowserAsset: PublicRoutePolicyConfig{
RateLimit: PublicRateLimitConfig{
Requests: defaultBrowserAssetRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultBrowserAssetRateLimitBurst,
},
},
PublicMisc: PublicRoutePolicyConfig{
RateLimit: PublicRateLimitConfig{
Requests: defaultPublicMiscRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultPublicMiscRateLimitBurst,
},
},
SendEmailCodeIdentity: PublicAuthIdentityPolicyConfig{
RateLimit: PublicRateLimitConfig{
Requests: defaultSendEmailCodeIdentityRateLimitRequests,
Window: defaultIdentityRateLimitWindow,
Burst: defaultSendEmailCodeIdentityRateLimitBurst,
},
},
ConfirmEmailCodeIdentity: PublicAuthIdentityPolicyConfig{
RateLimit: PublicRateLimitConfig{
Requests: defaultConfirmEmailCodeIdentityRateLimitRequests,
Window: defaultIdentityRateLimitWindow,
Burst: defaultConfirmEmailCodeIdentityRateLimitBurst,
},
},
},
}
}
// DefaultAdminHTTPConfig returns the default settings for the optional private
// admin listener. The zero address keeps the listener disabled by default.
func DefaultAdminHTTPConfig() AdminHTTPConfig {
return AdminHTTPConfig{
ReadHeaderTimeout: defaultAdminHTTPReadHeaderTimeout,
ReadTimeout: defaultAdminHTTPReadTimeout,
IdleTimeout: defaultAdminHTTPIdleTimeout,
}
}
// DefaultAuthenticatedGRPCConfig returns the default listener, freshness, and
// anti-abuse settings for the authenticated gRPC surface.
func DefaultAuthenticatedGRPCConfig() AuthenticatedGRPCConfig {
return AuthenticatedGRPCConfig{
Addr: defaultAuthenticatedGRPCAddr,
ConnectionTimeout: defaultAuthenticatedGRPCConnectionTimeout,
DownstreamTimeout: defaultAuthenticatedGRPCDownstreamTimeout,
FreshnessWindow: defaultAuthenticatedGRPCFreshnessWindow,
AntiAbuse: AuthenticatedGRPCAntiAbuseConfig{
IP: AuthenticatedRateLimitConfig{
Requests: defaultAuthenticatedGRPCIPRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultAuthenticatedGRPCIPRateLimitBurst,
},
Session: AuthenticatedRateLimitConfig{
Requests: defaultAuthenticatedGRPCSessionRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultAuthenticatedGRPCSessionRateLimitBurst,
},
User: AuthenticatedRateLimitConfig{
Requests: defaultAuthenticatedGRPCUserRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultAuthenticatedGRPCUserRateLimitBurst,
},
MessageClass: AuthenticatedRateLimitConfig{
Requests: defaultAuthenticatedGRPCMessageClassRateLimitRequests,
Window: defaultClassRateLimitWindow,
Burst: defaultAuthenticatedGRPCMessageClassRateLimitBurst,
},
},
}
}
// DefaultLoggingConfig returns the default structured logging settings.
func DefaultLoggingConfig() LoggingConfig {
return LoggingConfig{Level: defaultLogLevel}
}
// DefaultReplayRedisConfig returns the default Redis key namespace and timeout
// used for authenticated replay reservations.
func DefaultReplayRedisConfig() ReplayRedisConfig {
return ReplayRedisConfig{
KeyPrefix: defaultReplayRedisKeyPrefix,
ReserveTimeout: defaultReplayRedisReserveTimeout,
}
}
// DefaultSessionCacheConfig returns the default LRU bound and safety-net TTL
// used by the in-memory session cache.
func DefaultSessionCacheConfig() SessionCacheConfig {
return SessionCacheConfig{
MaxEntries: defaultSessionCacheMaxEntries,
TTL: defaultSessionCacheTTL,
}
}
// DefaultBackendConfig returns the default backend settings used for the
// gateway → backend HTTP and gRPC conversation. URL fields stay empty and
// must be supplied explicitly via env vars.
func DefaultBackendConfig() BackendConfig {
return BackendConfig{
HTTPTimeout: defaultBackendHTTPTimeout,
PushReconnectBaseBackoff: defaultBackendPushReconnectBaseBackoff,
PushReconnectMaxBackoff: defaultBackendPushReconnectMaxBackoff,
}
}
// DefaultResponseSignerConfig returns the default response-signer settings.
// The private key path remains empty and must be supplied explicitly.
func DefaultResponseSignerConfig() ResponseSignerConfig {
return ResponseSignerConfig{}
}
// LoadFromEnv loads Config from the process environment, applies defaults for
// omitted settings, and validates the resulting values.
func LoadFromEnv() (Config, error) {
cfg := Config{
ShutdownTimeout: defaultShutdownTimeout,
Logging: DefaultLoggingConfig(),
PublicHTTP: DefaultPublicHTTPConfig(),
Backend: DefaultBackendConfig(),
AdminHTTP: DefaultAdminHTTPConfig(),
AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(),
Redis: redisconn.DefaultConfig(),
SessionCache: DefaultSessionCacheConfig(),
ReplayRedis: DefaultReplayRedisConfig(),
ResponseSigner: DefaultResponseSignerConfig(),
}
rawShutdownTimeout, ok := os.LookupEnv(shutdownTimeoutEnvVar)
if ok {
shutdownTimeout, err := time.ParseDuration(rawShutdownTimeout)
if err != nil {
return Config{}, fmt.Errorf("load gateway config: parse %s: %w", shutdownTimeoutEnvVar, err)
}
cfg.ShutdownTimeout = shutdownTimeout
}
rawLogLevel, ok := os.LookupEnv(logLevelEnvVar)
if ok {
cfg.Logging.Level = rawLogLevel
}
rawPublicHTTPAddr, ok := os.LookupEnv(publicHTTPAddrEnvVar)
if ok {
cfg.PublicHTTP.Addr = rawPublicHTTPAddr
}
publicHTTPReadHeaderTimeout, err := loadDurationEnvWithDefault(publicHTTPReadHeaderTimeoutEnvVar, cfg.PublicHTTP.ReadHeaderTimeout)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.ReadHeaderTimeout = publicHTTPReadHeaderTimeout
publicHTTPReadTimeout, err := loadDurationEnvWithDefault(publicHTTPReadTimeoutEnvVar, cfg.PublicHTTP.ReadTimeout)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.ReadTimeout = publicHTTPReadTimeout
publicHTTPIdleTimeout, err := loadDurationEnvWithDefault(publicHTTPIdleTimeoutEnvVar, cfg.PublicHTTP.IdleTimeout)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.IdleTimeout = publicHTTPIdleTimeout
publicAuthUpstreamTimeout, err := loadDurationEnvWithDefault(publicAuthUpstreamTimeoutEnvVar, cfg.PublicHTTP.AuthUpstreamTimeout)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AuthUpstreamTimeout = publicAuthUpstreamTimeout
if v, ok := os.LookupEnv(backendHTTPURLEnvVar); ok {
cfg.Backend.HTTPBaseURL = v
}
if v, ok := os.LookupEnv(backendGRPCPushURLEnvVar); ok {
cfg.Backend.GRPCPushURL = v
}
if v, ok := os.LookupEnv(backendGatewayClientIDEnvVar); ok {
cfg.Backend.GatewayClientID = v
}
backendHTTPTimeout, err := loadDurationEnvWithDefault(backendHTTPTimeoutEnvVar, cfg.Backend.HTTPTimeout)
if err != nil {
return Config{}, err
}
cfg.Backend.HTTPTimeout = backendHTTPTimeout
backendPushReconnectBaseBackoff, err := loadDurationEnvWithDefault(backendPushReconnectBaseBackoffEnvVar, cfg.Backend.PushReconnectBaseBackoff)
if err != nil {
return Config{}, err
}
cfg.Backend.PushReconnectBaseBackoff = backendPushReconnectBaseBackoff
backendPushReconnectMaxBackoff, err := loadDurationEnvWithDefault(backendPushReconnectMaxBackoffEnvVar, cfg.Backend.PushReconnectMaxBackoff)
if err != nil {
return Config{}, err
}
cfg.Backend.PushReconnectMaxBackoff = backendPushReconnectMaxBackoff
rawAdminHTTPAddr, ok := os.LookupEnv(adminHTTPAddrEnvVar)
if ok {
cfg.AdminHTTP.Addr = rawAdminHTTPAddr
}
adminHTTPReadHeaderTimeout, err := loadDurationEnvWithDefault(adminHTTPReadHeaderTimeoutEnvVar, cfg.AdminHTTP.ReadHeaderTimeout)
if err != nil {
return Config{}, err
}
cfg.AdminHTTP.ReadHeaderTimeout = adminHTTPReadHeaderTimeout
adminHTTPReadTimeout, err := loadDurationEnvWithDefault(adminHTTPReadTimeoutEnvVar, cfg.AdminHTTP.ReadTimeout)
if err != nil {
return Config{}, err
}
cfg.AdminHTTP.ReadTimeout = adminHTTPReadTimeout
adminHTTPIdleTimeout, err := loadDurationEnvWithDefault(adminHTTPIdleTimeoutEnvVar, cfg.AdminHTTP.IdleTimeout)
if err != nil {
return Config{}, err
}
cfg.AdminHTTP.IdleTimeout = adminHTTPIdleTimeout
rawAuthenticatedGRPCAddr, ok := os.LookupEnv(authenticatedGRPCAddrEnvVar)
if ok {
cfg.AuthenticatedGRPC.Addr = rawAuthenticatedGRPCAddr
}
authenticatedGRPCConnectionTimeout, err := loadDurationEnvWithDefault(authenticatedGRPCConnectionTimeoutEnvVar, cfg.AuthenticatedGRPC.ConnectionTimeout)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.ConnectionTimeout = authenticatedGRPCConnectionTimeout
authenticatedGRPCDownstreamTimeout, err := loadDurationEnvWithDefault(authenticatedGRPCDownstreamTimeoutEnvVar, cfg.AuthenticatedGRPC.DownstreamTimeout)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.DownstreamTimeout = authenticatedGRPCDownstreamTimeout
authenticatedGRPCFreshnessWindow, err := loadDurationEnvWithDefault(authenticatedGRPCFreshnessWindowEnvVar, cfg.AuthenticatedGRPC.FreshnessWindow)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.FreshnessWindow = authenticatedGRPCFreshnessWindow
authenticatedGRPCIPRateLimit, err := loadRateLimitConfigFromEnv(
cfg.AuthenticatedGRPC.AntiAbuse.IP,
authenticatedGRPCIPRateLimitRequestsEnvVar,
authenticatedGRPCIPRateLimitWindowEnvVar,
authenticatedGRPCIPRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.AntiAbuse.IP = authenticatedGRPCIPRateLimit
authenticatedGRPCSessionRateLimit, err := loadRateLimitConfigFromEnv(
cfg.AuthenticatedGRPC.AntiAbuse.Session,
authenticatedGRPCSessionRateLimitRequestsEnvVar,
authenticatedGRPCSessionRateLimitWindowEnvVar,
authenticatedGRPCSessionRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.AntiAbuse.Session = authenticatedGRPCSessionRateLimit
authenticatedGRPCUserRateLimit, err := loadRateLimitConfigFromEnv(
cfg.AuthenticatedGRPC.AntiAbuse.User,
authenticatedGRPCUserRateLimitRequestsEnvVar,
authenticatedGRPCUserRateLimitWindowEnvVar,
authenticatedGRPCUserRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.AntiAbuse.User = authenticatedGRPCUserRateLimit
messageClassRateLimit, err := loadRateLimitConfigFromEnv(
cfg.AuthenticatedGRPC.AntiAbuse.MessageClass,
authenticatedGRPCMessageClassRateLimitRequestsEnvVar,
authenticatedGRPCMessageClassRateLimitWindowEnvVar,
authenticatedGRPCMessageClassRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.AuthenticatedGRPC.AntiAbuse.MessageClass = messageClassRateLimit
redisConn, err := redisconn.LoadFromEnv(gatewayRedisEnvPrefix)
if err != nil {
return Config{}, err
}
cfg.Redis = redisConn
sessionCacheMaxEntries, err := loadIntEnvWithDefault(sessionCacheMaxEntriesEnvVar, cfg.SessionCache.MaxEntries)
if err != nil {
return Config{}, err
}
cfg.SessionCache.MaxEntries = sessionCacheMaxEntries
sessionCacheTTL, err := loadDurationEnvWithDefault(sessionCacheTTLEnvVar, cfg.SessionCache.TTL)
if err != nil {
return Config{}, err
}
cfg.SessionCache.TTL = sessionCacheTTL
rawReplayRedisKeyPrefix, ok := os.LookupEnv(replayRedisKeyPrefixEnvVar)
if ok {
cfg.ReplayRedis.KeyPrefix = rawReplayRedisKeyPrefix
}
replayRedisReserveTimeout, err := loadDurationEnvWithDefault(replayRedisReserveTimeoutEnvVar, cfg.ReplayRedis.ReserveTimeout)
if err != nil {
return Config{}, err
}
cfg.ReplayRedis.ReserveTimeout = replayRedisReserveTimeout
rawSignerKeyPath, ok := os.LookupEnv(responseSignerPrivateKeyPEMPathEnvVar)
if ok {
cfg.ResponseSigner.PrivateKeyPEMPath = rawSignerKeyPath
}
publicAuthPolicy, err := loadPublicRoutePolicyConfigFromEnv(
cfg.PublicHTTP.AntiAbuse.PublicAuth,
publicAuthMaxBodyBytesEnvVar,
publicAuthRateLimitRequestsEnvVar,
publicAuthRateLimitWindowEnvVar,
publicAuthRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AntiAbuse.PublicAuth = publicAuthPolicy
browserBootstrapPolicy, err := loadPublicRoutePolicyConfigFromEnv(
cfg.PublicHTTP.AntiAbuse.BrowserBootstrap,
browserBootstrapMaxBodyBytesEnvVar,
browserBootstrapRateLimitRequestsEnvVar,
browserBootstrapRateLimitWindowEnvVar,
browserBootstrapRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AntiAbuse.BrowserBootstrap = browserBootstrapPolicy
browserAssetPolicy, err := loadPublicRoutePolicyConfigFromEnv(
cfg.PublicHTTP.AntiAbuse.BrowserAsset,
browserAssetMaxBodyBytesEnvVar,
browserAssetRateLimitRequestsEnvVar,
browserAssetRateLimitWindowEnvVar,
browserAssetRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AntiAbuse.BrowserAsset = browserAssetPolicy
publicMiscPolicy, err := loadPublicRoutePolicyConfigFromEnv(
cfg.PublicHTTP.AntiAbuse.PublicMisc,
publicMiscMaxBodyBytesEnvVar,
publicMiscRateLimitRequestsEnvVar,
publicMiscRateLimitWindowEnvVar,
publicMiscRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AntiAbuse.PublicMisc = publicMiscPolicy
sendIdentityPolicy, err := loadPublicAuthIdentityPolicyConfigFromEnv(
cfg.PublicHTTP.AntiAbuse.SendEmailCodeIdentity,
sendEmailCodeIdentityRateLimitRequestsEnvVar,
sendEmailCodeIdentityRateLimitWindowEnvVar,
sendEmailCodeIdentityRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AntiAbuse.SendEmailCodeIdentity = sendIdentityPolicy
confirmIdentityPolicy, err := loadPublicAuthIdentityPolicyConfigFromEnv(
cfg.PublicHTTP.AntiAbuse.ConfirmEmailCodeIdentity,
confirmEmailCodeIdentityRateLimitRequestsEnvVar,
confirmEmailCodeIdentityRateLimitWindowEnvVar,
confirmEmailCodeIdentityRateLimitBurstEnvVar,
)
if err != nil {
return Config{}, err
}
cfg.PublicHTTP.AntiAbuse.ConfirmEmailCodeIdentity = confirmIdentityPolicy
if cfg.ShutdownTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", shutdownTimeoutEnvVar)
}
if err := validateLogLevel(cfg.Logging.Level); err != nil {
return Config{}, fmt.Errorf("load gateway config: %w", err)
}
if strings.TrimSpace(cfg.PublicHTTP.Addr) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", publicHTTPAddrEnvVar)
}
if cfg.PublicHTTP.ReadHeaderTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", publicHTTPReadHeaderTimeoutEnvVar)
}
if cfg.PublicHTTP.ReadTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", publicHTTPReadTimeoutEnvVar)
}
if cfg.PublicHTTP.IdleTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", publicHTTPIdleTimeoutEnvVar)
}
if cfg.PublicHTTP.AuthUpstreamTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", publicAuthUpstreamTimeoutEnvVar)
}
cfg.Backend.HTTPBaseURL = strings.TrimSpace(cfg.Backend.HTTPBaseURL)
if cfg.Backend.HTTPBaseURL == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", backendHTTPURLEnvVar)
}
parsedBackendHTTP, err := url.Parse(strings.TrimRight(cfg.Backend.HTTPBaseURL, "/"))
if err != nil {
return Config{}, fmt.Errorf("load gateway config: parse %s: %w", backendHTTPURLEnvVar, err)
}
if parsedBackendHTTP.Scheme == "" || parsedBackendHTTP.Host == "" {
return Config{}, fmt.Errorf("load gateway config: %s must be an absolute URL", backendHTTPURLEnvVar)
}
cfg.Backend.HTTPBaseURL = parsedBackendHTTP.String()
cfg.Backend.GRPCPushURL = strings.TrimSpace(cfg.Backend.GRPCPushURL)
if cfg.Backend.GRPCPushURL == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", backendGRPCPushURLEnvVar)
}
cfg.Backend.GatewayClientID = strings.TrimSpace(cfg.Backend.GatewayClientID)
if cfg.Backend.GatewayClientID == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", backendGatewayClientIDEnvVar)
}
if cfg.Backend.HTTPTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", backendHTTPTimeoutEnvVar)
}
if cfg.Backend.PushReconnectBaseBackoff <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", backendPushReconnectBaseBackoffEnvVar)
}
if cfg.Backend.PushReconnectMaxBackoff < cfg.Backend.PushReconnectBaseBackoff {
return Config{}, fmt.Errorf("load gateway config: %s must be >= %s", backendPushReconnectMaxBackoffEnvVar, backendPushReconnectBaseBackoffEnvVar)
}
if addr := strings.TrimSpace(cfg.AdminHTTP.Addr); addr != "" {
cfg.AdminHTTP.Addr = addr
}
if cfg.AdminHTTP.ReadHeaderTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", adminHTTPReadHeaderTimeoutEnvVar)
}
if cfg.AdminHTTP.ReadTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", adminHTTPReadTimeoutEnvVar)
}
if cfg.AdminHTTP.IdleTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", adminHTTPIdleTimeoutEnvVar)
}
if strings.TrimSpace(cfg.AuthenticatedGRPC.Addr) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", authenticatedGRPCAddrEnvVar)
}
if cfg.AuthenticatedGRPC.ConnectionTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", authenticatedGRPCConnectionTimeoutEnvVar)
}
if cfg.AuthenticatedGRPC.DownstreamTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", authenticatedGRPCDownstreamTimeoutEnvVar)
}
if cfg.AuthenticatedGRPC.FreshnessWindow <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", authenticatedGRPCFreshnessWindowEnvVar)
}
if err := validateRateLimitConfig(
cfg.AuthenticatedGRPC.AntiAbuse.IP,
authenticatedGRPCIPRateLimitRequestsEnvVar,
authenticatedGRPCIPRateLimitWindowEnvVar,
authenticatedGRPCIPRateLimitBurstEnvVar,
); err != nil {
return Config{}, err
}
if err := validateRateLimitConfig(
cfg.AuthenticatedGRPC.AntiAbuse.Session,
authenticatedGRPCSessionRateLimitRequestsEnvVar,
authenticatedGRPCSessionRateLimitWindowEnvVar,
authenticatedGRPCSessionRateLimitBurstEnvVar,
); err != nil {
return Config{}, err
}
if err := validateRateLimitConfig(
cfg.AuthenticatedGRPC.AntiAbuse.User,
authenticatedGRPCUserRateLimitRequestsEnvVar,
authenticatedGRPCUserRateLimitWindowEnvVar,
authenticatedGRPCUserRateLimitBurstEnvVar,
); err != nil {
return Config{}, err
}
if err := validateRateLimitConfig(
cfg.AuthenticatedGRPC.AntiAbuse.MessageClass,
authenticatedGRPCMessageClassRateLimitRequestsEnvVar,
authenticatedGRPCMessageClassRateLimitWindowEnvVar,
authenticatedGRPCMessageClassRateLimitBurstEnvVar,
); err != nil {
return Config{}, err
}
if err := cfg.Redis.Validate(); err != nil {
return Config{}, fmt.Errorf("load gateway config: redis: %w", err)
}
if strings.TrimSpace(cfg.ReplayRedis.KeyPrefix) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", replayRedisKeyPrefixEnvVar)
}
if cfg.ReplayRedis.ReserveTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", replayRedisReserveTimeoutEnvVar)
}
if strings.TrimSpace(cfg.ResponseSigner.PrivateKeyPEMPath) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", responseSignerPrivateKeyPEMPathEnvVar)
}
if err := validatePublicRoutePolicyConfig(cfg.PublicHTTP.AntiAbuse.PublicAuth, publicAuthMaxBodyBytesEnvVar, publicAuthRateLimitRequestsEnvVar, publicAuthRateLimitWindowEnvVar, publicAuthRateLimitBurstEnvVar); err != nil {
return Config{}, err
}
if err := validatePublicRoutePolicyConfig(cfg.PublicHTTP.AntiAbuse.BrowserBootstrap, browserBootstrapMaxBodyBytesEnvVar, browserBootstrapRateLimitRequestsEnvVar, browserBootstrapRateLimitWindowEnvVar, browserBootstrapRateLimitBurstEnvVar); err != nil {
return Config{}, err
}
if err := validatePublicRoutePolicyConfig(cfg.PublicHTTP.AntiAbuse.BrowserAsset, browserAssetMaxBodyBytesEnvVar, browserAssetRateLimitRequestsEnvVar, browserAssetRateLimitWindowEnvVar, browserAssetRateLimitBurstEnvVar); err != nil {
return Config{}, err
}
if err := validatePublicRoutePolicyConfig(cfg.PublicHTTP.AntiAbuse.PublicMisc, publicMiscMaxBodyBytesEnvVar, publicMiscRateLimitRequestsEnvVar, publicMiscRateLimitWindowEnvVar, publicMiscRateLimitBurstEnvVar); err != nil {
return Config{}, err
}
if err := validatePublicAuthIdentityPolicyConfig(cfg.PublicHTTP.AntiAbuse.SendEmailCodeIdentity, sendEmailCodeIdentityRateLimitRequestsEnvVar, sendEmailCodeIdentityRateLimitWindowEnvVar, sendEmailCodeIdentityRateLimitBurstEnvVar); err != nil {
return Config{}, err
}
if err := validatePublicAuthIdentityPolicyConfig(cfg.PublicHTTP.AntiAbuse.ConfirmEmailCodeIdentity, confirmEmailCodeIdentityRateLimitRequestsEnvVar, confirmEmailCodeIdentityRateLimitWindowEnvVar, confirmEmailCodeIdentityRateLimitBurstEnvVar); err != nil {
return Config{}, err
}
return cfg, nil
}
func loadPublicRoutePolicyConfigFromEnv(defaults PublicRoutePolicyConfig, maxBodyEnvVar string, requestsEnvVar string, windowEnvVar string, burstEnvVar string) (PublicRoutePolicyConfig, error) {
policy := defaults
maxBodyBytes, err := loadInt64EnvWithDefault(maxBodyEnvVar, defaults.MaxBodyBytes)
if err != nil {
return PublicRoutePolicyConfig{}, err
}
policy.MaxBodyBytes = maxBodyBytes
rateLimit, err := loadRateLimitConfigFromEnv(defaults.RateLimit, requestsEnvVar, windowEnvVar, burstEnvVar)
if err != nil {
return PublicRoutePolicyConfig{}, err
}
policy.RateLimit = rateLimit
return policy, nil
}
func loadPublicAuthIdentityPolicyConfigFromEnv(defaults PublicAuthIdentityPolicyConfig, requestsEnvVar string, windowEnvVar string, burstEnvVar string) (PublicAuthIdentityPolicyConfig, error) {
rateLimit, err := loadRateLimitConfigFromEnv(defaults.RateLimit, requestsEnvVar, windowEnvVar, burstEnvVar)
if err != nil {
return PublicAuthIdentityPolicyConfig{}, err
}
return PublicAuthIdentityPolicyConfig{RateLimit: rateLimit}, nil
}
func loadRateLimitConfigFromEnv(defaults RateLimitConfig, requestsEnvVar string, windowEnvVar string, burstEnvVar string) (RateLimitConfig, error) {
cfg := defaults
requests, err := loadIntEnvWithDefault(requestsEnvVar, defaults.Requests)
if err != nil {
return RateLimitConfig{}, err
}
cfg.Requests = requests
window, err := loadDurationEnvWithDefault(windowEnvVar, defaults.Window)
if err != nil {
return RateLimitConfig{}, err
}
cfg.Window = window
burst, err := loadIntEnvWithDefault(burstEnvVar, defaults.Burst)
if err != nil {
return RateLimitConfig{}, err
}
cfg.Burst = burst
return cfg, nil
}
func validateLogLevel(level string) error {
switch strings.ToLower(strings.TrimSpace(level)) {
case "debug", "info", "warn", "error", "dpanic", "panic", "fatal":
return nil
default:
return fmt.Errorf("%s must be one of debug, info, warn, error, dpanic, panic, fatal", logLevelEnvVar)
}
}
func loadIntEnvWithDefault(envVar string, fallback int) (int, error) {
rawValue, ok := os.LookupEnv(envVar)
if !ok {
return fallback, nil
}
value, err := strconv.Atoi(rawValue)
if err != nil {
return 0, fmt.Errorf("load gateway config: parse %s: %w", envVar, err)
}
return value, nil
}
func loadInt64EnvWithDefault(envVar string, fallback int64) (int64, error) {
rawValue, ok := os.LookupEnv(envVar)
if !ok {
return fallback, nil
}
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
return 0, fmt.Errorf("load gateway config: parse %s: %w", envVar, err)
}
return value, nil
}
func loadDurationEnvWithDefault(envVar string, fallback time.Duration) (time.Duration, error) {
rawValue, ok := os.LookupEnv(envVar)
if !ok {
return fallback, nil
}
value, err := time.ParseDuration(rawValue)
if err != nil {
return 0, fmt.Errorf("load gateway config: parse %s: %w", envVar, err)
}
return value, nil
}
func loadBoolEnvWithDefault(envVar string, fallback bool) (bool, error) {
rawValue, ok := os.LookupEnv(envVar)
if !ok {
return fallback, nil
}
value, err := strconv.ParseBool(rawValue)
if err != nil {
return false, fmt.Errorf("load gateway config: parse %s: %w", envVar, err)
}
return value, nil
}
func validatePublicRoutePolicyConfig(cfg PublicRoutePolicyConfig, maxBodyEnvVar string, requestsEnvVar string, windowEnvVar string, burstEnvVar string) error {
if cfg.MaxBodyBytes < 0 {
return fmt.Errorf("load gateway config: %s must not be negative", maxBodyEnvVar)
}
return validateRateLimitConfig(cfg.RateLimit, requestsEnvVar, windowEnvVar, burstEnvVar)
}
func validatePublicAuthIdentityPolicyConfig(cfg PublicAuthIdentityPolicyConfig, requestsEnvVar string, windowEnvVar string, burstEnvVar string) error {
return validateRateLimitConfig(cfg.RateLimit, requestsEnvVar, windowEnvVar, burstEnvVar)
}
func validateRateLimitConfig(cfg RateLimitConfig, requestsEnvVar string, windowEnvVar string, burstEnvVar string) error {
if cfg.Requests <= 0 {
return fmt.Errorf("load gateway config: %s must be positive", requestsEnvVar)
}
if cfg.Window <= 0 {
return fmt.Errorf("load gateway config: %s must be positive", windowEnvVar)
}
if cfg.Burst <= 0 {
return fmt.Errorf("load gateway config: %s must be positive", burstEnvVar)
}
return nil
}