Files
galaxy-game/gateway/internal/config/config.go
T
2026-04-28 20:39:18 +02:00

1401 lines
55 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"
// authServiceBaseURLEnvVar names the environment variable that configures
// the optional Auth / Session Service public HTTP base URL used by gateway
// public-auth delegation.
authServiceBaseURLEnvVar = "GATEWAY_AUTH_SERVICE_BASE_URL"
// userServiceBaseURLEnvVar names the environment variable that configures
// the optional User Service internal HTTP base URL used by authenticated
// gateway self-service delegation.
userServiceBaseURLEnvVar = "GATEWAY_USER_SERVICE_BASE_URL"
// lobbyServiceBaseURLEnvVar names the environment variable that configures
// the optional Game Lobby public HTTP base URL used by authenticated
// gateway platform-command delegation.
lobbyServiceBaseURLEnvVar = "GATEWAY_LOBBY_SERVICE_BASE_URL"
// 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"
// sessionCacheRedisKeyPrefixEnvVar names the environment variable that
// configures the Redis key prefix used for SessionCache records.
sessionCacheRedisKeyPrefixEnvVar = "GATEWAY_SESSION_CACHE_REDIS_KEY_PREFIX"
// sessionCacheRedisLookupTimeoutEnvVar names the environment variable that
// configures the timeout used for SessionCache Redis lookups.
sessionCacheRedisLookupTimeoutEnvVar = "GATEWAY_SESSION_CACHE_REDIS_LOOKUP_TIMEOUT"
// 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"
// sessionEventsRedisStreamEnvVar names the environment variable that
// configures the Redis Stream key consumed for session lifecycle updates.
sessionEventsRedisStreamEnvVar = "GATEWAY_SESSION_EVENTS_REDIS_STREAM"
// sessionEventsRedisReadBlockTimeoutEnvVar names the environment variable
// that configures the blocking read timeout used by the session event
// subscriber.
sessionEventsRedisReadBlockTimeoutEnvVar = "GATEWAY_SESSION_EVENTS_REDIS_READ_BLOCK_TIMEOUT"
// clientEventsRedisStreamEnvVar names the environment variable that
// configures the Redis Stream key consumed for client-facing push events.
clientEventsRedisStreamEnvVar = "GATEWAY_CLIENT_EVENTS_REDIS_STREAM"
// clientEventsRedisReadBlockTimeoutEnvVar names the environment variable
// that configures the blocking read timeout used by the client-event
// subscriber.
clientEventsRedisReadBlockTimeoutEnvVar = "GATEWAY_CLIENT_EVENTS_REDIS_READ_BLOCK_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
defaultSessionCacheRedisKeyPrefix = "gateway:session:"
defaultSessionCacheRedisLookupTimeout = 250 * time.Millisecond
defaultReplayRedisKeyPrefix = "gateway:replay:"
defaultReplayRedisReserveTimeout = 250 * time.Millisecond
defaultSessionEventsRedisReadBlockTimeout = time.Second
defaultClientEventsRedisReadBlockTimeout = 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
}
// AuthServiceConfig describes the optional public-auth upstream used by the
// gateway runtime.
type AuthServiceConfig struct {
// BaseURL is the absolute base URL of the Auth / Session Service public
// HTTP API. When BaseURL is empty, the gateway keeps using its built-in
// unavailable public-auth adapter.
BaseURL string
}
// UserServiceConfig describes the optional authenticated self-service upstream
// used by the gateway runtime.
type UserServiceConfig struct {
// BaseURL is the absolute base URL of the User Service internal HTTP API.
// When BaseURL is empty, the gateway keeps using its built-in unavailable
// downstream adapter for the reserved `user.*` routes.
BaseURL string
}
// LobbyServiceConfig describes the optional authenticated platform-command
// upstream used by the gateway runtime.
type LobbyServiceConfig struct {
// BaseURL is the absolute base URL of the Game Lobby public HTTP API.
// When BaseURL is empty, the gateway keeps using its built-in unavailable
// downstream adapter for the reserved `lobby.*` routes.
BaseURL string
}
// 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
}
// SessionCacheRedisConfig describes the namespace and timeout used for
// authenticated SessionCache lookups. Connection topology is shared with the
// other Redis-backed gateway components and lives on Config.Redis (see
// `pkg/redisconn`).
type SessionCacheRedisConfig struct {
// KeyPrefix is prepended to every SessionCache Redis key.
KeyPrefix string
// LookupTimeout bounds individual SessionCache Redis operations.
LookupTimeout 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
}
// SessionEventsRedisConfig describes the Redis Stream consumed by the gateway
// to keep the process-local session cache synchronized with session lifecycle
// updates.
type SessionEventsRedisConfig struct {
// Stream is the Redis Stream key carrying full session snapshot events.
Stream string
// ReadBlockTimeout bounds one blocking XREAD call so shutdown remains
// responsive even when the stream is idle.
ReadBlockTimeout time.Duration
}
// ClientEventsRedisConfig describes the Redis Stream consumed by the gateway
// to deliver client-facing events to active push streams.
type ClientEventsRedisConfig struct {
// Stream is the Redis Stream key carrying client-facing event entries.
Stream string
// ReadBlockTimeout bounds one blocking XREAD call so shutdown remains
// responsive even when the stream is idle.
ReadBlockTimeout 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
// AuthService configures the optional public-auth delegation to the Auth /
// Session Service.
AuthService AuthServiceConfig
// UserService configures the optional authenticated self-service
// delegation to User Service.
UserService UserServiceConfig
// LobbyService configures the optional authenticated platform-command
// delegation to Game Lobby.
LobbyService LobbyServiceConfig
// 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 shared by
// every gateway Redis component, sourced from the GATEWAY_REDIS_*
// environment variables managed by `pkg/redisconn`.
Redis redisconn.Config
// SessionCacheRedis configures the Redis-backed authenticated SessionCache.
SessionCacheRedis SessionCacheRedisConfig
// ReplayRedis configures the Redis-backed authenticated ReplayStore.
ReplayRedis ReplayRedisConfig
// SessionEventsRedis configures the Redis Stream consumed for session cache
// updates and revocations.
SessionEventsRedis SessionEventsRedisConfig
// ClientEventsRedis configures the Redis Stream consumed for client-facing
// push delivery.
ClientEventsRedis ClientEventsRedisConfig
// 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}
}
// DefaultSessionCacheRedisConfig returns the default optional namespace and
// timeout settings for the Redis-backed authenticated SessionCache.
func DefaultSessionCacheRedisConfig() SessionCacheRedisConfig {
return SessionCacheRedisConfig{
KeyPrefix: defaultSessionCacheRedisKeyPrefix,
LookupTimeout: defaultSessionCacheRedisLookupTimeout,
}
}
// DefaultReplayRedisConfig returns the default Redis key namespace and timeout
// used for authenticated replay reservations.
func DefaultReplayRedisConfig() ReplayRedisConfig {
return ReplayRedisConfig{
KeyPrefix: defaultReplayRedisKeyPrefix,
ReserveTimeout: defaultReplayRedisReserveTimeout,
}
}
// DefaultSessionEventsRedisConfig returns the default optional settings for the
// session lifecycle event subscriber. Stream remains empty and must be
// supplied explicitly.
func DefaultSessionEventsRedisConfig() SessionEventsRedisConfig {
return SessionEventsRedisConfig{
ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout,
}
}
// DefaultClientEventsRedisConfig returns the default optional settings for the
// client-facing event subscriber. Stream remains empty and must be supplied
// explicitly.
func DefaultClientEventsRedisConfig() ClientEventsRedisConfig {
return ClientEventsRedisConfig{
ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout,
}
}
// DefaultResponseSignerConfig returns the default response-signer settings.
// The private key path remains empty and must be supplied explicitly.
func DefaultResponseSignerConfig() ResponseSignerConfig {
return ResponseSignerConfig{}
}
// DefaultAuthServiceConfig returns the default public-auth upstream settings.
// The zero value keeps the built-in unavailable adapter active.
func DefaultAuthServiceConfig() AuthServiceConfig {
return AuthServiceConfig{}
}
// DefaultUserServiceConfig returns the default authenticated self-service
// upstream settings. The zero value keeps the built-in unavailable adapter
// active for reserved `user.*` routes.
func DefaultUserServiceConfig() UserServiceConfig {
return UserServiceConfig{}
}
// DefaultLobbyServiceConfig returns the default authenticated platform-command
// upstream settings. The zero value keeps the built-in unavailable adapter
// active for reserved `lobby.*` routes.
func DefaultLobbyServiceConfig() LobbyServiceConfig {
return LobbyServiceConfig{}
}
// 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(),
AuthService: DefaultAuthServiceConfig(),
UserService: DefaultUserServiceConfig(),
LobbyService: DefaultLobbyServiceConfig(),
AdminHTTP: DefaultAdminHTTPConfig(),
AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(),
Redis: redisconn.DefaultConfig(),
SessionCacheRedis: DefaultSessionCacheRedisConfig(),
ReplayRedis: DefaultReplayRedisConfig(),
SessionEventsRedis: DefaultSessionEventsRedisConfig(),
ClientEventsRedis: DefaultClientEventsRedisConfig(),
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
rawAuthServiceBaseURL, ok := os.LookupEnv(authServiceBaseURLEnvVar)
if ok {
cfg.AuthService.BaseURL = rawAuthServiceBaseURL
}
rawUserServiceBaseURL, ok := os.LookupEnv(userServiceBaseURLEnvVar)
if ok {
cfg.UserService.BaseURL = rawUserServiceBaseURL
}
rawLobbyServiceBaseURL, ok := os.LookupEnv(lobbyServiceBaseURLEnvVar)
if ok {
cfg.LobbyService.BaseURL = rawLobbyServiceBaseURL
}
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
rawSessionCacheRedisKeyPrefix, ok := os.LookupEnv(sessionCacheRedisKeyPrefixEnvVar)
if ok {
cfg.SessionCacheRedis.KeyPrefix = rawSessionCacheRedisKeyPrefix
}
sessionCacheRedisLookupTimeout, err := loadDurationEnvWithDefault(sessionCacheRedisLookupTimeoutEnvVar, cfg.SessionCacheRedis.LookupTimeout)
if err != nil {
return Config{}, err
}
cfg.SessionCacheRedis.LookupTimeout = sessionCacheRedisLookupTimeout
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
rawSessionEventsRedisStream, ok := os.LookupEnv(sessionEventsRedisStreamEnvVar)
if ok {
cfg.SessionEventsRedis.Stream = rawSessionEventsRedisStream
}
sessionEventsRedisReadBlockTimeout, err := loadDurationEnvWithDefault(sessionEventsRedisReadBlockTimeoutEnvVar, cfg.SessionEventsRedis.ReadBlockTimeout)
if err != nil {
return Config{}, err
}
cfg.SessionEventsRedis.ReadBlockTimeout = sessionEventsRedisReadBlockTimeout
rawClientEventsRedisStream, ok := os.LookupEnv(clientEventsRedisStreamEnvVar)
if ok {
cfg.ClientEventsRedis.Stream = rawClientEventsRedisStream
}
clientEventsRedisReadBlockTimeout, err := loadDurationEnvWithDefault(clientEventsRedisReadBlockTimeoutEnvVar, cfg.ClientEventsRedis.ReadBlockTimeout)
if err != nil {
return Config{}, err
}
cfg.ClientEventsRedis.ReadBlockTimeout = clientEventsRedisReadBlockTimeout
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.AuthService.BaseURL = strings.TrimSpace(cfg.AuthService.BaseURL)
if cfg.AuthService.BaseURL != "" {
parsedAuthServiceBaseURL, err := url.Parse(cfg.AuthService.BaseURL)
if err != nil {
return Config{}, fmt.Errorf("load gateway config: parse %s: %w", authServiceBaseURLEnvVar, err)
}
if parsedAuthServiceBaseURL.Scheme == "" || parsedAuthServiceBaseURL.Host == "" {
return Config{}, fmt.Errorf("load gateway config: %s must be an absolute URL", authServiceBaseURLEnvVar)
}
cfg.AuthService.BaseURL = strings.TrimRight(parsedAuthServiceBaseURL.String(), "/")
}
cfg.UserService.BaseURL = strings.TrimSpace(cfg.UserService.BaseURL)
if cfg.UserService.BaseURL != "" {
parsedUserServiceBaseURL, err := url.Parse(cfg.UserService.BaseURL)
if err != nil {
return Config{}, fmt.Errorf("load gateway config: parse %s: %w", userServiceBaseURLEnvVar, err)
}
if parsedUserServiceBaseURL.Scheme == "" || parsedUserServiceBaseURL.Host == "" {
return Config{}, fmt.Errorf("load gateway config: %s must be an absolute URL", userServiceBaseURLEnvVar)
}
cfg.UserService.BaseURL = strings.TrimRight(parsedUserServiceBaseURL.String(), "/")
}
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.SessionCacheRedis.KeyPrefix) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", sessionCacheRedisKeyPrefixEnvVar)
}
if cfg.SessionCacheRedis.LookupTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", sessionCacheRedisLookupTimeoutEnvVar)
}
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.SessionEventsRedis.Stream) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", sessionEventsRedisStreamEnvVar)
}
if cfg.SessionEventsRedis.ReadBlockTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", sessionEventsRedisReadBlockTimeoutEnvVar)
}
if strings.TrimSpace(cfg.ClientEventsRedis.Stream) == "" {
return Config{}, fmt.Errorf("load gateway config: %s must not be empty", clientEventsRedisStreamEnvVar)
}
if cfg.ClientEventsRedis.ReadBlockTimeout <= 0 {
return Config{}, fmt.Errorf("load gateway config: %s must be positive", clientEventsRedisReadBlockTimeoutEnvVar)
}
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
}