Skip to content

Commit 452ef83

Browse files
committed
fix: harden TLS config and add rate limiting
- Admin/gRPC TLS: restrict to ECDHE GCM cipher suites - Admin TLS: add client CA and mTLS support - Chatbot: log warning when CHATBOT_INSECURE_TLS is enabled - Admin API: add per-IP sliding window rate limiter
1 parent 15fa962 commit 452ef83

4 files changed

Lines changed: 87 additions & 6 deletions

File tree

internal/admin/chatbot/client.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"io"
12+
"log/slog"
1213
"net/http"
1314
"os"
1415
"strings"
@@ -58,9 +59,14 @@ type openAIStreamDelta struct {
5859
func NewOpenAIAdapter(endpoint, apiKey, model string, temperature float64) LLMClient {
5960
endpoint = strings.TrimRight(endpoint, "/")
6061

62+
insecureSkipVerify := os.Getenv("CHATBOT_INSECURE_TLS") == "true"
63+
if insecureSkipVerify {
64+
slog.Warn("CHATBOT_INSECURE_TLS is enabled — TLS certificate verification is disabled for LLM API calls")
65+
}
66+
6167
transport := &http.Transport{
6268
TLSClientConfig: &tls.Config{
63-
InsecureSkipVerify: os.Getenv("CHATBOT_INSECURE_TLS") == "true",
69+
InsecureSkipVerify: insecureSkipVerify,
6470
},
6571
}
6672

internal/admin/rate_limiter.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package admin
2+
3+
import (
4+
"net/http"
5+
"sync"
6+
"sync/atomic"
7+
"time"
8+
)
9+
10+
type rateLimiter struct {
11+
maxRequests int64
12+
window time.Duration
13+
14+
mu sync.Mutex
15+
lastReset time.Time
16+
counters map[string]*int64
17+
}
18+
19+
func newRateLimiter(maxRequests int64, window time.Duration) *rateLimiter {
20+
if maxRequests <= 0 || window <= 0 {
21+
return nil
22+
}
23+
return &rateLimiter{
24+
maxRequests: maxRequests,
25+
window: window,
26+
counters: make(map[string]*int64),
27+
}
28+
}
29+
30+
func (rl *rateLimiter) middleware(next http.Handler) http.Handler {
31+
if rl == nil {
32+
return next
33+
}
34+
35+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36+
key := r.RemoteAddr
37+
38+
rl.mu.Lock()
39+
now := time.Now().UTC()
40+
if now.Sub(rl.lastReset) >= rl.window {
41+
rl.counters = make(map[string]*int64)
42+
rl.lastReset = now
43+
}
44+
45+
counter, ok := rl.counters[key]
46+
if !ok {
47+
var c int64 = 1
48+
rl.counters[key] = &c
49+
rl.mu.Unlock()
50+
next.ServeHTTP(w, r)
51+
return
52+
}
53+
54+
count := atomic.AddInt64(counter, 1)
55+
rl.mu.Unlock()
56+
57+
if count > rl.maxRequests {
58+
http.Error(w, "too many requests", http.StatusTooManyRequests)
59+
return
60+
}
61+
62+
next.ServeHTTP(w, r)
63+
})
64+
}

internal/config/config.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,14 @@ type LeaderElectionConfig struct {
7070
type AdminAuthConfig struct {
7171
BearerToken string `yaml:"bearerToken"`
7272
BearerTokenFile string `yaml:"bearerTokenFile"`
73+
RateLimitRPS int64 `yaml:"rateLimitRPS"`
7374
}
7475

7576
type PprofConfig struct {
76-
Enabled bool `yaml:"enabled"`
77-
Addr string `yaml:"addr"`
77+
Enabled bool `yaml:"enabled"`
78+
Addr string `yaml:"addr"`
79+
BearerToken string `yaml:"bearerToken"`
80+
BearerTokenFile string `yaml:"bearerTokenFile"`
7881
}
7982

8083
type NodeStatusConfig struct {
@@ -125,9 +128,11 @@ type NodeDriftConfig struct {
125128
}
126129

127130
type AdminTLSConfig struct {
128-
Enabled bool `yaml:"enabled"`
129-
CertPath string `yaml:"certPath"`
130-
KeyPath string `yaml:"keyPath"`
131+
Enabled bool `yaml:"enabled"`
132+
CertPath string `yaml:"certPath"`
133+
KeyPath string `yaml:"keyPath"`
134+
ClientCAPath string `yaml:"clientCAPath"`
135+
RequireClientCert bool `yaml:"requireClientCert"`
131136
}
132137

133138
type GRPCTLSConfig struct {

internal/grpcserver/tls.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func loadStaticServerTLSConfig(cfg config.GRPCTLSConfig) (*tls.Config, error) {
5757
MinVersion: tls.VersionTLS12,
5858
Certificates: []tls.Certificate{certificate},
5959
NextProtos: []string{"h2"},
60+
CipherSuites: []uint16{
61+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
62+
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
63+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
64+
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
65+
},
6066
}
6167

6268
if cfg.RequireClientCert && strings.TrimSpace(cfg.ClientCAPath) == "" {

0 commit comments

Comments
 (0)