Accountant: Inventar, where, Checkmk, Abrechnung
This commit is contained in:
1 parent
9f16148cde
commit
dd93277750
59 files changed
+7522
-113
No files matched your search
+16
-14
@@ -1,15 +1,17 @@
|
||||
package server
|
||||
// Package ratelimit begrenzt Versuche je Schlüssel (z. B. Client-IP) –
|
||||
// genutzt für die Enrollment-Endpunkte von Server und Accountant.
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// rateLimiter begrenzt Versuche je Schlüssel (Client-IP) auf limit pro
|
||||
// window (gleitendes Fenster). limit < 0 = unbegrenzt.
|
||||
type rateLimiter struct {
|
||||
limit int
|
||||
window time.Duration
|
||||
// Limiter begrenzt Versuche je Schlüssel (Client-IP) auf Limit pro Window
|
||||
// (gleitendes Fenster). Limit < 0 = unbegrenzt. Parallel nutzbar.
|
||||
type Limiter struct {
|
||||
Limit int
|
||||
Window time.Duration
|
||||
now func() time.Time
|
||||
|
||||
mu sync.Mutex
|
||||
@@ -17,22 +19,22 @@ type rateLimiter struct {
|
||||
lastPurge time.Time
|
||||
}
|
||||
|
||||
// allow verbucht einen Versuch; bei Ablehnung liefert es die Wartezeit.
|
||||
func (l *rateLimiter) allow(key string) (bool, time.Duration) {
|
||||
if l.limit < 0 {
|
||||
// Allow verbucht einen Versuch; bei Ablehnung liefert es die Wartezeit.
|
||||
func (l *Limiter) Allow(key string) (bool, time.Duration) {
|
||||
if l.Limit < 0 {
|
||||
return true, 0
|
||||
}
|
||||
now := time.Now()
|
||||
if l.now != nil {
|
||||
now = l.now()
|
||||
}
|
||||
cutoff := now.Add(-l.window)
|
||||
cutoff := now.Add(-l.Window)
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.hits == nil {
|
||||
l.hits = map[string][]time.Time{}
|
||||
}
|
||||
if now.Sub(l.lastPurge) >= l.window { // vergessen, was nicht mehr zählt
|
||||
if now.Sub(l.lastPurge) >= l.Window { // vergessen, was nicht mehr zählt
|
||||
for k, ts := range l.hits {
|
||||
if !ts[len(ts)-1].After(cutoff) {
|
||||
delete(l.hits, k)
|
||||
@@ -46,15 +48,15 @@ func (l *rateLimiter) allow(key string) (bool, time.Duration) {
|
||||
i++
|
||||
}
|
||||
ts = ts[i:]
|
||||
if len(ts) >= l.limit {
|
||||
if len(ts) >= l.Limit {
|
||||
l.hits[key] = ts
|
||||
return false, ts[0].Add(l.window).Sub(now)
|
||||
return false, ts[0].Add(l.Window).Sub(now)
|
||||
}
|
||||
l.hits[key] = append(ts, now)
|
||||
return true, 0
|
||||
}
|
||||
|
||||
func (l *rateLimiter) size() int {
|
||||
func (l *Limiter) size() int {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return len(l.hits)
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
package server
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRateLimiter(t *testing.T) {
|
||||
func TestLimiter(t *testing.T) {
|
||||
now := time.Date(2026, 10, 2, 12, 0, 0, 0, time.UTC)
|
||||
l := &rateLimiter{limit: 3, window: time.Minute, now: func() time.Time { return now }}
|
||||
l := &Limiter{Limit: 3, Window: time.Minute, now: func() time.Time { return now }}
|
||||
|
||||
for i := range 3 {
|
||||
if ok, _ := l.allow("1.2.3.4"); !ok {
|
||||
if ok, _ := l.Allow("1.2.3.4"); !ok {
|
||||
t.Fatalf("Versuch %d abgelehnt", i+1)
|
||||
}
|
||||
}
|
||||
ok, retry := l.allow("1.2.3.4")
|
||||
ok, retry := l.Allow("1.2.3.4")
|
||||
if ok || retry != time.Minute {
|
||||
t.Errorf("4. Versuch: ok=%v retry=%v, erwartet abgelehnt mit 1 min", ok, retry)
|
||||
}
|
||||
if ok, _ := l.allow("5.6.7.8"); !ok {
|
||||
if ok, _ := l.Allow("5.6.7.8"); !ok {
|
||||
t.Error("andere IP mitbetroffen")
|
||||
}
|
||||
|
||||
now = now.Add(40 * time.Second)
|
||||
if _, retry := l.allow("1.2.3.4"); retry != 20*time.Second {
|
||||
if _, retry := l.Allow("1.2.3.4"); retry != 20*time.Second {
|
||||
t.Errorf("Retry-After nach 40 s: %v, erwartet 20 s", retry)
|
||||
}
|
||||
now = now.Add(20 * time.Second)
|
||||
if ok, _ := l.allow("1.2.3.4"); !ok {
|
||||
if ok, _ := l.Allow("1.2.3.4"); !ok {
|
||||
t.Error("nach Ablauf des Fensters weiter gesperrt")
|
||||
}
|
||||
}
|
||||
|
||||
// Die Zählerliste darf bei vielen IPs nicht unbegrenzt wachsen.
|
||||
func TestRateLimiter_ForgetsOldEntries(t *testing.T) {
|
||||
func TestLimiter_ForgetsOldEntries(t *testing.T) {
|
||||
now := time.Date(2026, 10, 2, 12, 0, 0, 0, time.UTC)
|
||||
l := &rateLimiter{limit: 1, window: time.Minute, now: func() time.Time { return now }}
|
||||
l := &Limiter{Limit: 1, Window: time.Minute, now: func() time.Time { return now }}
|
||||
for i := range 1000 {
|
||||
l.allow(string(rune('a'+i%26)) + time.Duration(i).String())
|
||||
l.Allow(string(rune('a'+i%26)) + time.Duration(i).String())
|
||||
}
|
||||
now = now.Add(2 * time.Minute)
|
||||
l.allow("neu")
|
||||
l.Allow("neu")
|
||||
if n := l.size(); n != 1 {
|
||||
t.Errorf("%d Einträge nach Ablauf, erwartet 1", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimiter_Disabled(t *testing.T) {
|
||||
l := &rateLimiter{limit: -1, window: time.Minute}
|
||||
func TestLimiter_Disabled(t *testing.T) {
|
||||
l := &Limiter{Limit: -1, Window: time.Minute}
|
||||
for range 100 {
|
||||
if ok, _ := l.allow("x"); !ok {
|
||||
if ok, _ := l.Allow("x"); !ok {
|
||||
t.Fatal("Limit -1 muss unbegrenzt sein")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user