@@ -8,24 +8,33 @@ import (
88 "net/http"
99 "net/netip"
1010 "strings"
11+ "sync"
1112 "time"
13+
14+ "golang.org/x/time/rate"
1215)
1316
1417type Config struct {
15- ForwardToken bool `json:"forwardToken,omitempty"`
16- Freshness int64 `json:"freshness,omitempty"`
17- HeaderName string `json:"headerName,omitempty"`
18- AllowSubnet []string `json:"allowSubnet,omitempty"`
19- Timeout int64 `json:"timeout,omitempty"`
18+ ForwardToken bool `json:"forwardToken,omitempty"`
19+ Freshness int64 `json:"freshness,omitempty"`
20+ HeaderName string `json:"headerName,omitempty"`
21+ AllowSubnet []string `json:"allowSubnet,omitempty"`
22+ Timeout int64 `json:"timeout,omitempty"`
23+ GlobalRateLimit int `json:"globalRateLimit,omitempty"`
24+ PerIPRateLimit int `json:"perIPRateLimit,omitempty"`
25+ RateLimitWindow int64 `json:"rateLimitWindow,omitempty"`
2026}
2127
2228func CreateConfig () * Config {
2329 return & Config {
24- HeaderName : "Authorization" ,
25- ForwardToken : false ,
26- Freshness : 3600 ,
27- AllowSubnet : []string {"0.0.0.0/24" },
28- Timeout : 5 ,
30+ HeaderName : "Authorization" ,
31+ ForwardToken : false ,
32+ Freshness : 3600 ,
33+ AllowSubnet : []string {"0.0.0.0/24" },
34+ Timeout : 10 ,
35+ GlobalRateLimit : 100 ,
36+ PerIPRateLimit : 10 ,
37+ RateLimitWindow : 60 ,
2938 }
3039}
3140
@@ -38,6 +47,13 @@ type ICalMiddleware struct {
3847 allowSubnet []netip.Prefix
3948 timeout time.Duration
4049 name string
50+
51+ // Поля для rate limiting
52+ globalLimiter * rate.Limiter
53+ ipLimiters map [string ]* rate.Limiter
54+ ipLimiterMutex sync.Mutex
55+ perIPRateLimit int
56+ rateLimitWindow time.Duration
4157}
4258
4359func New (_ context.Context , next http.Handler , config * Config , name string ) (http.Handler , error ) {
@@ -60,15 +76,25 @@ func New(_ context.Context, next http.Handler, config *Config, name string) (htt
6076 }
6177 cache := NewCache (time .Duration (config .Freshness )* time .Second , 8 * time .Hour )
6278
79+ var globalLimiter * rate.Limiter
80+ if config .GlobalRateLimit > 0 && config .RateLimitWindow > 0 {
81+ ratePerSec := float64 (config .GlobalRateLimit ) / float64 (config .RateLimitWindow )
82+ globalLimiter = rate .NewLimiter (rate .Limit (ratePerSec ), config .GlobalRateLimit )
83+ }
84+
6385 return & ICalMiddleware {
64- headerName : config .HeaderName ,
65- forwardToken : config .ForwardToken ,
66- freshness : config .Freshness ,
67- allowSubnet : cidrs ,
68- next : next ,
69- cache : cache ,
70- timeout : timeout ,
71- name : name ,
86+ headerName : config .HeaderName ,
87+ forwardToken : config .ForwardToken ,
88+ freshness : config .Freshness ,
89+ allowSubnet : cidrs ,
90+ next : next ,
91+ cache : cache ,
92+ timeout : timeout ,
93+ name : name ,
94+ globalLimiter : globalLimiter ,
95+ ipLimiters : make (map [string ]* rate.Limiter ),
96+ perIPRateLimit : config .PerIPRateLimit ,
97+ rateLimitWindow : time .Duration (config .RateLimitWindow ) * time .Second ,
7298 }, nil
7399}
74100
@@ -110,7 +136,6 @@ func (plugin *ICalMiddleware) httpRequestAndCache(url string) error {
110136 return nil
111137}
112138
113-
114139func (plugin * ICalMiddleware ) extractTokenFromHeader (request * http.Request ) string {
115140 canonicalHeaderName := http .CanonicalHeaderKey (plugin .headerName )
116141 token := request .Header .Get (canonicalHeaderName )
@@ -130,14 +155,14 @@ func (plugin *ICalMiddleware) extractTokenFromHeader(request *http.Request) stri
130155}
131156
132157func ReadUserIP (r * http.Request ) string {
133- IPAddress := r .Header .Get ("X-Real-Ip" )
134- if IPAddress == "" {
135- IPAddress = r .Header .Get ("X-Forwarded-For" )
158+ ipAddress := r .Header .Get ("X-Real-Ip" )
159+ if ipAddress == "" {
160+ ipAddress = r .Header .Get ("X-Forwarded-For" )
136161 }
137- if IPAddress == "" {
138- IPAddress , _ , _ = net .SplitHostPort (r .RemoteAddr )
162+ if ipAddress == "" {
163+ ipAddress , _ , _ = net .SplitHostPort (r .RemoteAddr )
139164 }
140- return IPAddress
165+ return ipAddress
141166}
142167
143168func (plugin * ICalMiddleware ) containsSubnet (address string ) bool {
@@ -161,29 +186,51 @@ func (plugin *ICalMiddleware) validate(request *http.Request) (int, error) {
161186 userIP := ReadUserIP (request )
162187 fmt .Printf ("[DEBUG] [%s] Обработка запроса от IP: %s, URL: %s\n " , plugin .name , userIP , request .URL .String ())
163188
164- if ! plugin .containsSubnet (userIP ) {
165- token := plugin .extractTokenFromHeader (request )
166- if token == "" {
167- fmt .Printf ("[ERROR] [%s] Токен не предоставлен в заголовке '%s' для запроса от IP %s\n " , plugin .name , plugin .headerName , userIP )
168- return http .StatusUnauthorized , fmt .Errorf ("no token provided" )
189+ if plugin .containsSubnet (userIP ) {
190+ fmt .Printf ("[DEBUG] [%s] IP %s входит в разрешённую подсет\n " , plugin .name , userIP )
191+ return http .StatusOK , nil
192+ }
193+
194+ if plugin .globalLimiter != nil && ! plugin .globalLimiter .Allow () {
195+ fmt .Printf ("[ERROR] [%s] Превышен глобальный лимит запросов\n " , plugin .name )
196+ return http .StatusTooManyRequests , fmt .Errorf ("global rate limit exceeded" )
197+ }
198+
199+ if plugin .perIPRateLimit > 0 && plugin .rateLimitWindow > 0 {
200+ plugin .ipLimiterMutex .Lock ()
201+ limiter , exists := plugin .ipLimiters [userIP ]
202+ if ! exists {
203+ ratePerSec := float64 (plugin .perIPRateLimit ) / plugin .rateLimitWindow .Seconds ()
204+ limiter = rate .NewLimiter (rate .Limit (ratePerSec ), plugin .perIPRateLimit )
205+ plugin .ipLimiters [userIP ] = limiter
169206 }
170- if len (token ) != 16 {
171- fmt .Printf ("[ERROR] [%s] Неверная длина токена '%s' для запроса от IP %s\n " , plugin .name , token , userIP )
172- return http .StatusUnauthorized , fmt .Errorf ("incorrect token len" )
207+ plugin .ipLimiterMutex .Unlock ()
208+ if ! limiter .Allow () {
209+ fmt .Printf ("[ERROR] [%s] Превышен лимит запросов для IP: %s\n " , plugin .name , userIP )
210+ return http .StatusTooManyRequests , fmt .Errorf ("rate limit exceeded for IP %s" , userIP )
173211 }
174- if ! plugin .cache .Has (token ) {
175- err := plugin .httpRequestAndCache (token )
176- if err != nil {
177- fmt .Printf ("[ERROR] [%s] Проверка токена '%s' не пройдена для запроса от IP %s: %v\n " , plugin .name , token , userIP , err )
178- return http .StatusUnauthorized , err
179- }
180- fmt .Printf ("[DEBUG] [%s] Токен '%s' валидирован и кэширован для IP %s\n " , plugin .name , token , userIP )
181- } else {
182- fmt .Printf ("[DEBUG] [%s] Токен '%s' найден в кэше для IP %s\n " , plugin .name , token , userIP )
212+ }
213+
214+ token := plugin .extractTokenFromHeader (request )
215+ if token == "" {
216+ fmt .Printf ("[ERROR] [%s] Токен не предоставлен в заголовке '%s' для запроса от IP %s\n " , plugin .name , plugin .headerName , userIP )
217+ return http .StatusUnauthorized , fmt .Errorf ("no token provided" )
218+ }
219+ if len (token ) != 16 {
220+ fmt .Printf ("[ERROR] [%s] Неверная длина токена '%s' для запроса от IP %s\n " , plugin .name , token , userIP )
221+ return http .StatusUnauthorized , fmt .Errorf ("incorrect token len" )
222+ }
223+ if ! plugin .cache .Has (token ) {
224+ err := plugin .httpRequestAndCache (token )
225+ if err != nil {
226+ fmt .Printf ("[ERROR] [%s] Проверка токена '%s' не пройдена для запроса от IP %s: %v\n " , plugin .name , token , userIP , err )
227+ return http .StatusUnauthorized , err
183228 }
229+ fmt .Printf ("[DEBUG] [%s] Токен '%s' валидирован и кэширован для IP %s\n " , plugin .name , token , userIP )
184230 } else {
185- fmt .Printf ("[DEBUG] [%s] Запрос от IP %s пропущен без проверки токена (разрешённая подсеть) \n " , plugin .name , userIP )
231+ fmt .Printf ("[DEBUG] [%s] Токен '%s' найден в кэше для IP %s \n " , plugin .name , token , userIP )
186232 }
233+
187234 return http .StatusOK , nil
188235}
189236
0 commit comments