SQS: ValidateQueueName в createTenantQueue, JWT требует exp, leader сбрасывает лидерство при длительном сбое refresh
This commit is contained in:
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user