diff --git a/pkg/api/constants/consts.go b/pkg/api/constants/consts.go index 93c00883..37660942 100644 --- a/pkg/api/constants/consts.go +++ b/pkg/api/constants/consts.go @@ -22,7 +22,9 @@ const ( MaxManifestDigestQueryTags = (8192 - 2048) / (len("tag=") + 128 + 1) // MaxManifestBodySize is the maximum number of bytes accepted for a manifest PUT request body. // OCI manifest JSON is always small metadata; 4 MiB is well above any realistic manifest. - MaxManifestBodySize = 4 * 1024 * 1024 + MaxManifestBodySize = 4 * 1024 * 1024 + // MaxAPIKeyBodySize is the maximum number of bytes accepted for an API-key creation request body. + MaxAPIKeyBodySize = 4 * 1024 BlobUploadUUID = "Blob-Upload-UUID" DefaultMediaType = "application/json" BinaryMediaType = "application/octet-stream" diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 5c2e431f..c86e840c 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -2342,15 +2342,21 @@ func (rh *RouteHandler) GetAPIKeys(resp http.ResponseWriter, req *http.Request) // @Success 201 {string} string "created" // @Failure 400 {string} string "bad request" // @Failure 401 {string} string "unauthorized" +// @Failure 413 {string} string "request entity too large" // @Failure 500 {string} string "internal server error" // @Router /zot/auth/apikey [post]. func (rh *RouteHandler) CreateAPIKey(resp http.ResponseWriter, req *http.Request) { var payload APIKeyPayload - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(http.MaxBytesReader(resp, req.Body, constants.MaxAPIKeyBodySize)) if err != nil { - rh.c.Log.Error().Msg("failed to read request body") - resp.WriteHeader(http.StatusInternalServerError) + var mbe *http.MaxBytesError + if errors.As(err, &mbe) { + resp.WriteHeader(http.StatusRequestEntityTooLarge) + } else { + rh.c.Log.Error().Msg("failed to read request body") + resp.WriteHeader(http.StatusInternalServerError) + } return } diff --git a/pkg/api/routes_test.go b/pkg/api/routes_test.go index d65af6fe..2d706cd7 100644 --- a/pkg/api/routes_test.go +++ b/pkg/api/routes_test.go @@ -1604,6 +1604,22 @@ func TestRoutes(t *testing.T) { So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError) }) + Convey("CreateAPIKey body exceeds MaxAPIKeyBodySize returns 413", func() { + userAc := reqCtx.NewUserAccessControl() + userAc.SetUsername("test") + ctx := userAc.DeriveContext(context.Background()) + + oversized := make([]byte, constants.MaxAPIKeyBodySize+1) + request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(oversized)) + response := httptest.NewRecorder() + + rthdlr.CreateAPIKey(response, request) + + resp := response.Result() + defer resp.Body.Close() + So(resp.StatusCode, ShouldEqual, http.StatusRequestEntityTooLarge) + }) + Convey("CreateAPIKey bad request body", func() { userAc := reqCtx.NewUserAccessControl() userAc.SetUsername("test") diff --git a/swagger/docs.go b/swagger/docs.go index af166c0b..518e8535 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -1099,6 +1099,12 @@ const docTemplate = `{ "type": "string" } }, + "413": { + "description": "request entity too large", + "schema": { + "type": "string" + } + }, "500": { "description": "internal server error", "schema": { diff --git a/swagger/swagger.json b/swagger/swagger.json index 15077763..87f9ce57 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -1091,6 +1091,12 @@ "type": "string" } }, + "413": { + "description": "request entity too large", + "schema": { + "type": "string" + } + }, "500": { "description": "internal server error", "schema": { diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 6bffaab4..f1b41a9b 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -1004,6 +1004,10 @@ paths: description: unauthorized schema: type: string + "413": + description: request entity too large + schema: + type: string "500": description: internal server error schema: