1855e43699
Adds a `GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS` env-driven allow-list on the public REST server so the dev UI on https://www.galaxy.lan can call https://api.galaxy.lan without the browser blocking the cross-origin response. Defaults to empty (no CORS) so the production posture stays closed. The middleware mounts before route classification and anti-abuse, so OPTIONS preflights never charge against per-class rate-limit buckets. `tools/dev-deploy/docker-compose.yml` opts the dev gateway into a single allowed origin (`https://www.galaxy.lan`); local-dev keeps the defaults because Vite proxies through the same origin. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1339 lines
52 KiB
Go
1339 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"
|
|
|
|
// publicHTTPCORSAllowedOriginsEnvVar names the environment variable that
|
|
// configures the comma-separated list of browser origins permitted to
|
|
// call the public REST surface. An empty value disables CORS entirely;
|
|
// requests without an Origin header still pass through normally.
|
|
publicHTTPCORSAllowedOriginsEnvVar = "GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS"
|
|
|
|
// 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
|
|
|
|
// CORSAllowedOrigins is the exact-match list of browser origins
|
|
// permitted to call the public REST surface. Empty disables CORS:
|
|
// requests without an Origin header continue to work, cross-origin
|
|
// requests are subject to the browser's default same-origin policy.
|
|
CORSAllowedOrigins []string
|
|
}
|
|
|
|
// 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(publicHTTPCORSAllowedOriginsEnvVar); ok {
|
|
origins := make([]string, 0)
|
|
for part := range strings.SplitSeq(v, ",") {
|
|
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
|
origins = append(origins, trimmed)
|
|
}
|
|
}
|
|
cfg.PublicHTTP.CORSAllowedOrigins = origins
|
|
}
|
|
|
|
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
|
|
}
|