146 lines
4.4 KiB
Go
146 lines
4.4 KiB
Go
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) (Guard, error) {
|
|
rateLimits := make(map[int]Admitter)
|
|
concurrencyLimits := make(map[int]struct{})
|
|
for _, policy := range credentialPolicies(authentication) {
|
|
if policy.MaxConcurrentConnections > 0 {
|
|
concurrencyLimits[policy.MaxConcurrentConnections] = struct{}{}
|
|
}
|
|
limit := policy.RequestsPerMinute
|
|
if limit == 0 {
|
|
continue
|
|
}
|
|
if _, exists := rateLimits[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)
|
|
}
|
|
rateLimits[limit] = limiter
|
|
}
|
|
return chainGuards(
|
|
NewCredentialAdmissionGuard(rateLimits),
|
|
NewCredentialConcurrencyGuard(concurrencyLimits),
|
|
), 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
|
|
}
|