fix: add request body size limit

This commit is contained in:
Julian Sommer
2026-04-01 15:36:06 +10:00
parent 91e4efb0ef
commit 0c160d7f45
4 changed files with 44 additions and 0 deletions
+2
View File
@@ -343,6 +343,7 @@ var (
ErrorPendingVpcPeeringConnectionLimitExceeded = "PendingVpcPeeringConnectionLimitExceeded"
ErrorPlacementGroupLimitExceeded = "PlacementGroupLimitExceeded"
ErrorPrivateIpAddressLimitExceeded = "PrivateIpAddressLimitExceeded"
ErrorRequestEntityTooLarge = "RequestEntityTooLarge"
ErrorRequestExpired = "RequestExpired"
ErrorRequestLimitExceeded = "RequestLimitExceeded"
ErrorRequestResourceCountExceeded = "RequestResourceCountExceeded"
@@ -800,6 +801,7 @@ var ErrorLookup = map[string]ErrorMessage{
ErrorPendingVpcPeeringConnectionLimitExceeded: {HTTPCode: 409, Message: "You've reached the limit on the number of pending VPC peering connections that you can have."},
ErrorPlacementGroupLimitExceeded: {HTTPCode: 400, Message: "You've reached the limit on the number of placement groups that you can have."},
ErrorPrivateIpAddressLimitExceeded: {HTTPCode: 400, Message: "You've reached the limit on the number of private IP addresses that you can assign to the specified network interface for that type of instance. For more information, see IP addresses per network interface."},
ErrorRequestEntityTooLarge: {HTTPCode: 413, Message: "Request body exceeds the maximum allowed size."},
ErrorRequestExpired: {HTTPCode: 403, Message: "The request reached the service more than 15 minutes after the date stamp on the request or more than 15 minutes after the request expiration date (such as for presigned URLs), or the date stamp on the request is more than 15 minutes in the future. If you're using temporary security credentials, this error can also occur if the credentials have expired. For more information, see Temporary security credentials in the IAM User Guide."},
ErrorRequestLimitExceeded: {HTTPCode: 500, Message: "The maximum request rate permitted by the Amazon EC2 APIs has been exceeded for your account. For best results, use an increasing or variable sleep interval between requests. For more information, see Query API request rate."},
ErrorRequestResourceCountExceeded: {HTTPCode: 409, Message: "Details in your Spot request exceed the numbers allowed by the Spot service in one of the following ways, depending on the action that generated the error: \u2014If you get this error when you submitted a request for Spot Instances, check the number of Spot Instances specified in your request. The number shouldn't exceed the 3,000 maximum allowed per request. Resend your Spot Instance request and specify a number less than 3,000. If your account's regional Spot request limit is greater than 3,000 instances, you can access these instances by submitting multiple smaller requests. \u2014If you get this error when you sent Describe Spot Instance requests, check the number of requests for Spot Instance data, the amount of data you requested, and how often you sent the request. The frequency with which you requested the data combined with the amount of data exceeds the levels allowed by the Spot service. Try again and submit fewer large Describe requests over longer intervals."},
+1
View File
@@ -345,6 +345,7 @@ func TestErrorLookup(t *testing.T) {
{code: "PendingVpcPeeringConnectionLimitExceeded", http: 409, message: "You've reached the limit on the number of pending VPC peering connections that you can have."},
{code: "PlacementGroupLimitExceeded", http: 400, message: "You've reached the limit on the number of placement groups that you can have."},
{code: "PrivateIpAddressLimitExceeded", http: 400, message: "You've reached the limit on the number of private IP addresses that you can assign to the specified network interface for that type of instance. For more information, see IP addresses per network interface."},
{code: "RequestEntityTooLarge", http: 413, message: "Request body exceeds the maximum allowed size."},
{code: "RequestExpired", http: 403, message: "The request reached the service more than 15 minutes after the date stamp on the request or more than 15 minutes after the request expiration date (such as for presigned URLs), or the date stamp on the request is more than 15 minutes in the future. If you're using temporary security credentials, this error can also occur if the credentials have expired. For more information, see Temporary security credentials in the IAM User Guide."},
{code: "RequestLimitExceeded", http: 500, message: "The maximum request rate permitted by the Amazon EC2 APIs has been exceeded for your account. For best results, use an increasing or variable sleep interval between requests. For more information, see Query API request rate."},
{code: "RequestResourceCountExceeded", http: 409, message: "Details in your Spot request exceed the numbers allowed by the Spot service in one of the following ways, depending on the action that generated the error: \u2014If you get this error when you submitted a request for Spot Instances, check the number of Spot Instances specified in your request. The number shouldn't exceed the 3,000 maximum allowed per request. Resend your Spot Instance request and specify a number less than 3,000. If your account's regional Spot request limit is greater than 3,000 instances, you can access these instances by submitting multiple smaller requests. \u2014If you get this error when you sent Describe Spot Instance requests, check the number of requests for Spot Instance data, the amount of data you requested, and how often you sent the request. The frequency with which you requested the data combined with the amount of data exceeds the levels allowed by the Spot service. Try again and submit fewer large Describe requests over longer intervals."},
+13
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"crypto/subtle"
"errors"
"fmt"
"io"
"log/slog"
@@ -21,6 +22,9 @@ import (
const (
// Maximum allowed clock skew for signature validation (5 minutes)
maxClockSkew = 5 * time.Minute
// Maximum request body size for signature validation (10 MB)
maxBodySize = 10 * 1024 * 1024
)
// SigV4AuthMiddleware returns stdlib middleware that validates AWS Signature V4 authentication.
@@ -139,9 +143,18 @@ func (gw *GatewayConfig) SigV4AuthMiddleware() func(http.Handler) http.Handler {
return
}
// Limit request body size to prevent OOM from unauthenticated requests
r.Body = http.MaxBytesReader(w, r.Body, maxBodySize)
// Read body once and re-buffer for downstream handlers
body, err := io.ReadAll(r.Body)
if err != nil {
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
slog.Warn("Request body too large", "limit", maxBodySize)
gw.writeSigV4Error(w, r, awserrors.ErrorRequestEntityTooLarge)
return
}
slog.Error("Failed to read request body", "err", err)
gw.writeSigV4Error(w, r, awserrors.ErrorInternalError)
return
+28
View File
@@ -667,6 +667,34 @@ func TestSigV4Auth_DecryptFailure(t *testing.T) {
}
}
func TestSigV4Auth_RequestBodyTooLarge(t *testing.T) {
handler := setupTestApp(testAccessKey, testSecretKey)
// Create a body that exceeds maxBodySize (10 MB + 1 byte)
oversizedBody := strings.Repeat("x", maxBodySize+1)
authHeader, timestamp := generateTestAuthHeader(
"POST", "/", "", oversizedBody,
testAccessKey, testSecretKey, testRegion, testService,
)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(oversizedBody))
req.Host = "localhost:9999"
req.Header.Set("Authorization", authHeader)
req.Header.Set("X-Amz-Date", timestamp)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp := doRequest(handler, req)
if resp.StatusCode != http.StatusRequestEntityTooLarge {
t.Errorf("Expected status 413, got %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
if !strings.Contains(string(body), "RequestEntityTooLarge") {
t.Errorf("Expected RequestEntityTooLarge error, got: %s", string(body))
}
}
func TestCanonicalHeaderName(t *testing.T) {
testCases := []struct {
input string