SQS: ValidateQueueName в createTenantQueue, JWT требует exp, leader сбрасывает лидерство при длительном сбое refresh

This commit is contained in:
Repinoid
2026-10-11 08:50:12 +03:00
parent 582ad4a832
commit e8e0879914
3 changed files with 40 additions and 6 deletions
+6
View File
@@ -510,6 +510,12 @@ func (h *Handler) createTenantQueue(w http.ResponseWriter, r *http.Request) {
jsonErr(w, http.StatusBadRequest, "name is required")
return
}
// Та же валидация имени, что и в SQS CreateQueue (иначе имена с ':'/'/'
// ломали бы ключ "{accessKey}:{name}").
if err := gosqs.ValidateQueueName(req.Name); err != nil {
jsonErr(w, http.StatusBadRequest, "invalid queue name")
return
}
key := t.AccessKey + ":" + req.Name
models.SyncQueues.Lock()
if _, exists := models.SyncQueues.Queues[key]; exists {
+4 -1
View File
@@ -59,7 +59,10 @@ func ParseJWTClaims(token string) (*JWTClaims, error) {
if claims.Sub == "" {
return nil, fmt.Errorf("JWT missing 'sub' claim")
}
if claims.Exp > 0 && claims.Exp < time.Now().Unix() {
if claims.Exp == 0 {
return nil, fmt.Errorf("JWT missing 'exp' claim")
}
if claims.Exp < time.Now().Unix() {
return nil, fmt.Errorf("JWT token expired")
}
return &claims, nil
+30 -5
View File
@@ -33,16 +33,21 @@ type Elector struct {
isLeader bool
stopCh chan struct{}
stopped bool
// lastRefresh — время последнего успешного refresh/acquire лока.
// Нужен для сброса лидерства при длительном сбое refresh (split-brain).
lastRefresh time.Time
}
// New — создаёт Elector. key — ключ лока, id — уникальный id инстанса.
func New(key, id string, ttl, interval time.Duration) *Elector {
return &Elector{
key: key,
id: id,
ttl: ttl,
interval: interval,
stopCh: make(chan struct{}),
key: key,
id: id,
ttl: ttl,
interval: interval,
stopCh: make(chan struct{}),
lastRefresh: time.Now(),
}
}
@@ -108,8 +113,15 @@ func (e *Elector) step() {
ok, err := persistence.RefreshLock(e.key, e.id, e.ttl)
if err != nil {
log.Errorf("leader: refresh: %v", err)
// Refresh не удаётся: если с последнего успеха прошло > TTL — лок
// гарантированно истёк, и другой инстанс мог стать лидером (split-brain).
// Сбрасываем лидерство (fail-safe), а не остаёмся «лидером» без лока.
if time.Since(e.lastRefreshAt()) > e.ttl {
e.setLeader(false)
}
return
}
e.markRefreshed()
if !ok {
e.setLeader(false)
}
@@ -122,5 +134,18 @@ func (e *Elector) step() {
}
if ok {
e.setLeader(true)
e.markRefreshed()
}
}
func (e *Elector) lastRefreshAt() time.Time {
e.mu.RLock()
defer e.mu.RUnlock()
return e.lastRefresh
}
func (e *Elector) markRefreshed() {
e.mu.Lock()
e.lastRefresh = time.Now()
e.mu.Unlock()
}