Release: v0.1.0-alpha
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s

This commit is contained in:
2026-04-05 16:56:16 +02:00
parent 23ff731579
commit fd13e67aef
89 changed files with 18786 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
// Keywarden - Centralized SSH Key Management and Deployment
// Copyright (C) 2026 Patrick Asmus (scriptos)
// SPDX-License-Identifier: AGPL-3.0-or-later
package security
import (
"net/http"
"strings"
"sync"
"time"
)
// rateLimiter implements a fixed-window IP-based rate limiter.
type rateLimiter struct {
mu sync.Mutex
clients map[string]*window
limit int
period time.Duration
}
type window struct {
count int
resetAt time.Time
}
func newRateLimiter(limit int, period time.Duration) *rateLimiter {
rl := &rateLimiter{
clients: make(map[string]*window),
limit: limit,
period: period,
}
// Background goroutine to evict expired entries (prevent memory leak)
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
now := time.Now()
rl.mu.Lock()
for ip, w := range rl.clients {
if now.After(w.resetAt) {
delete(rl.clients, ip)
}
}
rl.mu.Unlock()
}
}()
return rl
}
func (rl *rateLimiter) allow(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
w, ok := rl.clients[ip]
if !ok || now.After(w.resetAt) {
rl.clients[ip] = &window{count: 1, resetAt: now.Add(rl.period)}
return true
}
w.count++
return w.count <= rl.limit
}
// RateLimitMiddleware returns middleware that rate-limits requests to the
// login endpoint by client IP address.
//
// loginLimit is the maximum number of login attempts per IP per minute.
// A value of 0 disables rate limiting entirely.
func RateLimitMiddleware(loginLimit int) func(http.Handler) http.Handler {
if loginLimit <= 0 {
// Disabled pass through
return func(next http.Handler) http.Handler { return next }
}
loginRL := newRateLimiter(loginLimit, 1*time.Minute)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Only rate-limit POST to login endpoints
if r.Method == http.MethodPost && isLoginPath(r.URL.Path) {
ip := ClientIP(r)
if !loginRL.allow(ip) {
http.Error(w, "Too Many Requests please try again later", http.StatusTooManyRequests)
return
}
}
next.ServeHTTP(w, r)
})
}
}
// isLoginPath returns true for login-related POST paths.
func isLoginPath(path string) bool {
return path == "/login" || strings.HasPrefix(path, "/login/")
}