package server import ( "fmt" "time" "proxy-pool/internal/config" "proxy-pool/internal/domain/clientpolicy" "proxy-pool/internal/gateway/policy" platformAdmission "proxy-pool/internal/platform/admission" "proxy-pool/internal/platform/httpsecurity" ) type Protection struct { Auth Guard Access Guard Admission Guard ClientIPs *ClientIPResolver } func BuildProtection(listener config.Listener) (Protection, error) { clientIPs, err := NewClientIPResolver(listener.Access.TrustedProxies) if err != nil { return Protection{}, err } access, err := NewAccessGuard(clientIPs, listener.Access.AllowCIDRs) if err != nil { return Protection{}, err } auth, err := buildConfiguredAuth(listener) if err != nil { return Protection{}, err } var admissionGuards []Guard if listener.Limits.RequestsPerMinute > 0 || listener.Limits.RequestsPerMinutePerClient > 0 { limiter, limiterErr := platformAdmission.NewFixedWindow(platformAdmission.FixedWindowConfig{ Window: time.Minute, Global: listener.Limits.RequestsPerMinute, PerKey: listener.Limits.RequestsPerMinutePerClient, }) if limiterErr != nil { return Protection{}, fmt.Errorf("build gateway admission: %w", limiterErr) } admissionGuards = append(admissionGuards, NewAdmissionGuard(clientIPs, limiter)) } credentialAdmission, err := buildCredentialAdmission(listener.Auth) if err != nil { return Protection{}, err } admissionGuards = append(admissionGuards, credentialAdmission) return Protection{Auth: auth, Access: access, Admission: chainGuards(admissionGuards...), ClientIPs: clientIPs}, nil } func buildCredentialAdmission(authentication config.Auth) (*CredentialAdmissionGuard, error) { limits := make(map[int]Admitter) for _, policy := range credentialPolicies(authentication) { limit := policy.RequestsPerMinute if limit == 0 { continue } if _, exists := limits[limit]; exists { continue } limiter, err := platformAdmission.NewFixedWindow(platformAdmission.FixedWindowConfig{ Window: time.Minute, PerKey: limit, }) if err != nil { return nil, fmt.Errorf("build gateway credential admission: %w", err) } limits[limit] = limiter } return NewCredentialAdmissionGuard(limits), nil } func credentialPolicies(authentication config.Auth) []clientpolicy.Policy { policies := make([]clientpolicy.Policy, 0, len(authentication.Methods)+1) policies = append(policies, authentication.ClientPolicy) for _, method := range authentication.Methods { policies = append(policies, method.ClientPolicy) } return policies } func ConfigFromListener(listener config.Listener) Config { result := Config{ MaxAttempts: listener.Retry.MaxAttempts, RetryMethods: append([]string(nil), listener.Retry.RetryMethods...), MaxConcurrentRequests: listener.Limits.MaxConcurrentConnections, } if listener.StickySession.Enabled { result.StickySession = StickySessionConfig{ Header: listener.StickySession.Header, TTL: listener.StickySession.TTL.Value(), MaxEntries: listener.StickySession.MaxEntries, } } return result } func TargetPolicyFromListener(listener config.Listener) (*policy.TargetPolicy, error) { destination := listener.DestinationPolicy return policy.NewTargetPolicy(policy.Config{ DenyCIDRs: append([]string(nil), destination.DenyCIDRs...), AllowedPorts: append([]uint16(nil), destination.AllowedPorts...), AllowPrivateNetworks: explicitlyAllowed(destination.DenyPrivateNetworks), AllowLoopback: explicitlyAllowed(destination.DenyLoopback), AllowLinkLocal: explicitlyAllowed(destination.DenyLinkLocal), }) } func explicitlyAllowed(deny *bool) bool { return deny != nil && !*deny } func buildConfiguredAuth(listener config.Listener) (Guard, error) { switch listener.Auth.Mode { case "", "none": return nil, nil } authListener := config.Listener{ Access: config.Access{ TrustedProxies: append([]string(nil), listener.Access.TrustedProxies...), }, Auth: listener.Auth, } protection, err := httpsecurity.NewFromListener( authListener, httpsecurity.ClientAuthenticatedOrSourceIP, httpsecurity.ProxySemantics, nil, ) if err != nil { return nil, fmt.Errorf("build gateway authentication: %w", err) } return protection, nil }