fix: валидация JWT перебором всех стендов gateway (токен любого стенда принимается)

This commit is contained in:
“Naeel”
2026-08-14 08:05:16 +04:00
parent de72fe5c8f
commit 166baa9a98
3 changed files with 48 additions and 22 deletions
+1 -2
View File
@@ -16,8 +16,7 @@ EXPOSE 4100
# Нечувствительные значения зашиты в образ. Секреты (SHARED_SQS_ADMIN_TOKEN, REDIS_PASSWORD) — env'ом контейнера в deck. # Нечувствительные значения зашиты в образ. Секреты (SHARED_SQS_ADMIN_TOKEN, REDIS_PASSWORD) — env'ом контейнера в deck.
ENV REDIS_ADDR=redisk8s.4ff5678b-b683-4aef-91da-22d89dec8a25.svc.cluster.local:6379 \ ENV REDIS_ADDR=redisk8s.4ff5678b-b683-4aef-91da-22d89dec8a25.svc.cluster.local:6379 \
REDIS_USER=default \ REDIS_USER=default \
SHARED_SQS_SEED_DEMO=false \ SHARED_SQS_SEED_DEMO=false
NUBES_ENDPOINT=https://lk-api-gateway.ngcloud.ru/api/v1/svc
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \ HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
CMD wget -q -O - http://localhost:4100/health || exit 1 CMD wget -q -O - http://localhost:4100/health || exit 1
ENTRYPOINT ["shared-sqs", "--config", "/conf/goaws.yaml"] ENTRYPOINT ["shared-sqs", "--config", "/conf/goaws.yaml"]
+1 -3
View File
@@ -64,9 +64,7 @@ type Handler struct {
// NewHandler — создаёт admin handler // NewHandler — создаёт admin handler
func NewHandler(store *tenant.TenantStore, adminToken string) *Handler { func NewHandler(store *tenant.TenantStore, adminToken string) *Handler {
nubesEndpoint := os.Getenv("NUBES_ENDPOINT") nubesEndpoint := os.Getenv("NUBES_ENDPOINT")
if nubesEndpoint == "" { // Если не задан — пустой: PingNubesAPI переберёт все стенды сам.
nubesEndpoint = "https://lk-api-gateway.ngcloud.ru/api/v1/svc"
}
return &Handler{store: store, adminToken: adminToken, nubesEndpoint: nubesEndpoint} return &Handler{store: store, adminToken: adminToken, nubesEndpoint: nubesEndpoint}
} }
+46 -17
View File
@@ -74,27 +74,56 @@ func TenantIDFromSub(sub string) string {
return fmt.Sprintf("sless-%x", hash[:8]) return fmt.Sprintf("sless-%x", hash[:8])
} }
// PingNubesAPI — проверяет валидность токена запросом к nubes API. // nubesAPIEndpoints — стенды Nubes API Gateway, по которым валидируется токен.
// endpoint — базовый URL (например "https://deck-api-test.ngcloud.ru/api/v1"). // Токен принимается, если ХОТЯ БЫ ОДИН стенд его принял.
// Логика: 401/403 → токен отклонён. Ошибка соединения → API недоступен. // Юзер может вводить токен от любого стенда — главное, что токен действительный.
var nubesAPIEndpoints = []string{
"https://lk-api-gateway.ngcloud.ru/api/v1/svc",
"https://lk-api-gateway-test.ngcloud.ru/api/v1/svc",
"https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc",
}
// PingNubesAPI — проверяет валидность токена перебором стендов Nubes API.
// endpoint (если задан через NUBES_ENDPOINT) пробуется первым.
// Логика: 401/403 → токен отклонён ЭТИМ стендом, пробуем следующий.
// Любой другой HTTP статус → токен принят. // Любой другой HTTP статус → токен принят.
func PingNubesAPI(ctx context.Context, endpoint, token string) error { func PingNubesAPI(ctx context.Context, endpoint, token string) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) candidates := make([]string, 0, len(nubesAPIEndpoints)+1)
if err != nil { if endpoint != "" {
return fmt.Errorf("build nubes ping request: %w", err) candidates = append(candidates, endpoint)
}
for _, ep := range nubesAPIEndpoints {
if ep != endpoint {
candidates = append(candidates, ep)
}
} }
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", "Mozilla/5.0")
client := &http.Client{Timeout: 10 * time.Second} var lastErr error
resp, err := client.Do(req) for _, ep := range candidates {
if err != nil { req, err := http.NewRequestWithContext(ctx, http.MethodGet, ep, nil)
return fmt.Errorf("nubes API unreachable at %s: %w", endpoint, err) if err != nil {
} lastErr = fmt.Errorf("build nubes ping request: %w", err)
defer resp.Body.Close() continue
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", "Mozilla/5.0")
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { client := &http.Client{Timeout: 10 * time.Second}
return fmt.Errorf("nubes API rejected token (HTTP %d)", resp.StatusCode) resp, err := client.Do(req)
if err != nil {
lastErr = fmt.Errorf("nubes API unreachable at %s: %w", ep, err)
continue
}
resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
lastErr = fmt.Errorf("nubes API rejected token (HTTP %d)", resp.StatusCode)
continue
}
return nil
} }
return nil if lastErr == nil {
lastErr = fmt.Errorf("nubes API rejected token")
}
return lastErr
} }