mirror of
https://github.com/versity/versitygw.git
synced 2026-08-20 14:16:28 +00:00
fix: enforce required SignedHeaders validation for SigV4 requests
Validate required signed headers for both Authorization-header SigV4 requests and presigned URLs. The required signed header set is now `host` plus every incoming header with the `x-amz-` prefix. During request reconstruction, signed headers and explicitly ignored headers are copied into the generated request used for signature verification. If an incoming `x-amz-*` header is present but missing from the client-provided `SignedHeaders`, return `AccessDenied` with a `HeadersNotSigned` field. The `host` header remains part of the canonical request and signed header calculation. Previously, a client could sign a request without an S3 control header and then add that header after signing. For example, a presigned `PUT` URL could be generated with only `host` signed, then the actual request could include an unsigned `X-Amz-Tagging` or `X-Amz-Copy-Source` header. Because the verifier reconstructed the request only from `SignedHeaders`, that extra header was omitted from signature calculation and could pass authentication even though it changed the request semantics. This is now rejected with `AccessDenied`. Expose v4 helper methods for checking required and ignored headers, and update canonical header signing so ignored headers can still be included when a client explicitly lists them in `SignedHeaders`, while `Authorization` remains excluded from signature calculation.
This commit is contained in:
+29
-16
@@ -25,6 +25,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -33,6 +34,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
signerV4 "github.com/versity/versitygw/aws/signer/v4"
|
||||
"github.com/versity/versitygw/debuglogger"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
"github.com/versity/versitygw/s3response"
|
||||
@@ -133,12 +135,8 @@ func createHttpRequestFromCtx(ctx *fiber.Ctx, signedHdrs []string, contentLength
|
||||
return nil, errors.New("error in creating an http request")
|
||||
}
|
||||
|
||||
// Set the request headers
|
||||
for key, value := range req.Header.All() {
|
||||
keyStr := string(key)
|
||||
if includeHeader(keyStr, signedHdrs) {
|
||||
httpReq.Header.Add(keyStr, string(value))
|
||||
}
|
||||
if err := addRequestHeadersFromCtx(ctx, httpReq, signedHdrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make sure all headers in the signed headers are present
|
||||
@@ -195,12 +193,8 @@ func createPresignedHttpRequestFromCtx(ctx *fiber.Ctx, signedHdrs []string, cont
|
||||
if err != nil {
|
||||
return nil, errors.New("error in creating an http request")
|
||||
}
|
||||
// Set the request headers
|
||||
for key, value := range req.Header.All() {
|
||||
keyStr := string(key)
|
||||
if includeHeader(keyStr, signedHdrs) {
|
||||
httpReq.Header.Add(keyStr, string(value))
|
||||
}
|
||||
if err := addRequestHeadersFromCtx(ctx, httpReq, signedHdrs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if Content-Length in signed headers
|
||||
@@ -344,12 +338,31 @@ func IsValidBucketName(bucket string) bool {
|
||||
}
|
||||
|
||||
func includeHeader(hdr string, signedHdrs []string) bool {
|
||||
for _, shdr := range signedHdrs {
|
||||
if strings.EqualFold(hdr, shdr) {
|
||||
return true
|
||||
return slices.ContainsFunc(signedHdrs, func(shdr string) bool {
|
||||
return strings.EqualFold(hdr, shdr)
|
||||
})
|
||||
}
|
||||
|
||||
func addRequestHeadersFromCtx(ctx *fiber.Ctx, httpReq *http.Request, signedHdrs []string) error {
|
||||
headersNotSigned := []string{}
|
||||
for key, value := range ctx.Request().Header.All() {
|
||||
keyStr := string(key)
|
||||
if includeHeader(keyStr, signedHdrs) || signerV4.IsIgnoredHeader(keyStr) {
|
||||
httpReq.Header.Add(keyStr, string(value))
|
||||
continue
|
||||
}
|
||||
if signerV4.IsRequiredSignedHeader(keyStr) {
|
||||
lowerKey := strings.ToLower(keyStr)
|
||||
headersNotSigned = append(headersNotSigned, lowerKey)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
if len(headersNotSigned) != 0 {
|
||||
debuglogger.Logf("headers present in request but not included in SignedHeaders: %q", strings.Join(headersNotSigned, ", "))
|
||||
return s3err.GetHeadersNotSignedErr(headersNotSigned)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// expiration time window
|
||||
|
||||
Reference in New Issue
Block a user