Files
SQS-service/app/auth/auth_middleware.go
T
Naeel c3ba2dcae4 chore: initial import from sless/shared-sqs (v0.1.14)
- Standalone SQS-service repository
- Multi-tenant message queue service, AWS SQS compatible
- Based on GoAws, with mutable tenants, auth, WebUI, Redis persistence
- Ready for independent development and deployment
- See doc/ and README.md for architecture and usage
2026-04-10 16:47:27 +03:00

115 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Изменено: 2026-04-09
// Auth middleware для shared-sqs: извлекает AccessKeyId из AWS Authorization header
// и помещает найденного тенанта в context запроса.
package auth
import (
"context"
"encoding/xml"
"net/http"
"strings"
"shared-sqs/app/tenant"
)
// TenantContextKey — ключ для хранения тенанта в request context.
// Тип contextKey предотвращает конфликты с другими пакетами.
type contextKey string
const TenantContextKey contextKey = "tenant"
// AuthMiddleware — middleware: ищет тенанта по AccessKeyId из AWS Authorization header.
// Пропускает /health и /admin/** без tenant-аутентификации.
// Ловушка #4: не ставим короткий таймаут — ReceiveMessage с long polling держит соединение до 20 сек.
func AuthMiddleware(store *tenant.TenantStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// /health — без auth
if r.URL.Path == "/health" {
next.ServeHTTP(w, r)
return
}
// /admin/** — отдельная auth (bearer token, см. admin_handlers.go)
if strings.HasPrefix(r.URL.Path, "/admin/") {
next.ServeHTTP(w, r)
return
}
accessKeyID := extractAccessKeyID(r)
if accessKeyID == "" {
writeSQSAuthError(w, "MissingAuthenticationToken", "Request must contain either AccessKeyId or X-Amz-Credential")
return
}
t, ok := store.GetByAccessKey(accessKeyID)
if !ok || !t.Active {
writeSQSAuthError(w, "InvalidClientTokenId", "The security token included in the request is invalid")
return
}
ctx := context.WithValue(r.Context(), TenantContextKey, t)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// extractAccessKeyID — извлекает AWS AccessKeyId из запроса.
// Поддерживает оба варианта: Authorization header (Signature V4) и X-Amz-Credential query param (presigned URLs).
// Ловушка #3: AWS CLI ВСЕГДА отправляет Signature V4 — нужно парсить, даже не проверяя подпись.
// Ловушка #5: X-Amz-Security-Token (STS) — игнорируем.
func extractAccessKeyID(r *http.Request) string {
// Вариант 1: Authorization header
// Формат: "AWS4-HMAC-SHA256 Credential={AccessKeyId}/{date}/{region}/sqs/aws4_request, ..."
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, "AWS4-HMAC-SHA256") {
idx := strings.Index(auth, "Credential=")
if idx >= 0 {
rest := auth[idx+len("Credential="):]
slashIdx := strings.Index(rest, "/")
if slashIdx > 0 {
return rest[:slashIdx]
}
}
}
// Вариант 2: Query parameter (presigned URLs)
// Формат: X-Amz-Credential={AccessKeyId}/{date}/{region}/sqs/aws4_request
if cred := r.URL.Query().Get("X-Amz-Credential"); cred != "" {
parts := strings.SplitN(cred, "/", 2)
if len(parts) > 0 && parts[0] != "" {
return parts[0]
}
}
return ""
}
// sqsAuthError — AWS-совместимый XML ответ об ошибке аутентификации.
type sqsAuthError struct {
XMLName xml.Name `xml:"ErrorResponse"`
Error sqsErrorBody `xml:"Error"`
RequestID string `xml:"RequestId"`
}
type sqsErrorBody struct {
Type string `xml:"Type"`
Code string `xml:"Code"`
Message string `xml:"Message"`
}
// writeSQSAuthError — отвечает AWS-совместимым XML с кодом 403.
func writeSQSAuthError(w http.ResponseWriter, code, message string) {
w.Header().Set("Content-Type", "application/xml")
w.WriteHeader(http.StatusForbidden)
resp := sqsAuthError{
Error: sqsErrorBody{
Type: "Sender",
Code: code,
Message: message,
},
RequestID: "00000000-0000-0000-0000-000000000000",
}
data, _ := xml.Marshal(resp)
w.Write(data)
}