Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check quota limits for container uploads #22450

Merged
merged 6 commits into from
Jan 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions routers/api/packages/container/blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ var uploadVersionMutex sync.Mutex

// saveAsPackageBlob creates a package blob from an upload
// The uploaded blob gets stored in a special upload version to link them to the package/image
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pi *packages_service.PackageInfo) (*packages_model.PackageBlob, error) {
func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) {
if err := packages_service.CheckSizeQuotaExceeded(db.DefaultContext, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil {
return nil, err
}

pb := packages_service.NewPackageBlob(hsr)

exists := false

contentStore := packages_module.NewContentStore()

uploadVersion, err := getOrCreateUploadVersion(pi)
uploadVersion, err := getOrCreateUploadVersion(&pci.PackageInfo)
if err != nil {
return nil, err
}
Expand Down
43 changes: 38 additions & 5 deletions routers/api/packages/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,22 @@ func InitiateUploadBlob(ctx *context.Context) {
return
}

if _, err := saveAsPackageBlob(buf, &packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
if _, err := saveAsPackageBlob(
buf,
&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: ctx.Package.Owner,
Name: image,
},
Creator: ctx.Doer,
},
); err != nil {
switch err {
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}

Expand Down Expand Up @@ -358,8 +372,22 @@ func EndUploadBlob(ctx *context.Context) {
return
}

if _, err := saveAsPackageBlob(uploader, &packages_service.PackageInfo{Owner: ctx.Package.Owner, Name: image}); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
if _, err := saveAsPackageBlob(
uploader,
&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: ctx.Package.Owner,
Name: image,
},
Creator: ctx.Doer,
},
); err != nil {
switch err {
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}

Expand Down Expand Up @@ -526,7 +554,12 @@ func UploadManifest(ctx *context.Context) {
} else if errors.Is(err, container_model.ErrContainerBlobNotExist) {
apiErrorDefined(ctx, errBlobUnknown)
} else {
apiError(ctx, http.StatusInternalServerError, err)
switch err {
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
}
return
}
Expand Down
4 changes: 4 additions & 0 deletions routers/api/packages/container/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
}
}

if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil {
return nil, err
}

if mci.IsTagged {
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil {
log.Error("Error setting package version property: %v", err)
Expand Down
12 changes: 8 additions & 4 deletions services/packages/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
}

if versionCreated {
if err := checkCountQuotaExceeded(ctx, pvci.Creator, pvci.Owner); err != nil {
if err := CheckCountQuotaExceeded(ctx, pvci.Creator, pvci.Owner); err != nil {
return nil, false, err
}

Expand Down Expand Up @@ -240,7 +240,7 @@ func NewPackageBlob(hsr packages_module.HashedSizeReader) *packages_model.Packag
func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) {
log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename)

if err := checkSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil {
if err := CheckSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil {
return nil, nil, false, err
}

Expand Down Expand Up @@ -302,7 +302,9 @@ func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVers
return pf, pb, !exists, nil
}

func checkCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User) error {
// CheckCountQuotaExceeded checks if the owner has more than the allowed packages
// The check is skipped if the doer is an admin.
func CheckCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User) error {
if doer.IsAdmin {
return nil
}
Expand All @@ -324,7 +326,9 @@ func checkCountQuotaExceeded(ctx context.Context, doer, owner *user_model.User)
return nil
}

func checkSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, packageType packages_model.Type, uploadSize int64) error {
// CheckSizeQuotaExceeded checks if the upload size is bigger than the allowed size
// The check is skipped if the doer is an admin.
func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, packageType packages_model.Type, uploadSize int64) error {
if doer.IsAdmin {
return nil
}
Expand Down
70 changes: 50 additions & 20 deletions tests/integration/api_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package integration

import (
"bytes"
"crypto/sha256"
"fmt"
"net/http"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -171,34 +173,62 @@ func TestPackageAccess(t *testing.T) {
func TestPackageQuota(t *testing.T) {
defer tests.PrepareTestEnv(t)()

limitTotalOwnerCount, limitTotalOwnerSize, limitSizeGeneric := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize, setting.Packages.LimitSizeGeneric
limitTotalOwnerCount, limitTotalOwnerSize := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize

// Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload.
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})

uploadPackage := func(doer *user_model.User, version string, expectedStatus int) {
url := fmt.Sprintf("/api/packages/%s/generic/test-package/%s/file.bin", user.Name, version)
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
AddBasicAuthHeader(req, doer.Name)
MakeRequest(t, req, expectedStatus)
}
t.Run("Common", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

// Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload.
limitSizeGeneric := setting.Packages.LimitSizeGeneric

uploadPackage := func(doer *user_model.User, version string, expectedStatus int) {
url := fmt.Sprintf("/api/packages/%s/generic/test-package/%s/file.bin", user.Name, version)
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
AddBasicAuthHeader(req, doer.Name)
MakeRequest(t, req, expectedStatus)
}

setting.Packages.LimitTotalOwnerCount = 0
uploadPackage(user, "1.0", http.StatusForbidden)
uploadPackage(admin, "1.0", http.StatusCreated)
setting.Packages.LimitTotalOwnerCount = limitTotalOwnerCount
setting.Packages.LimitTotalOwnerCount = 0
uploadPackage(user, "1.0", http.StatusForbidden)
uploadPackage(admin, "1.0", http.StatusCreated)
setting.Packages.LimitTotalOwnerCount = limitTotalOwnerCount

setting.Packages.LimitTotalOwnerSize = 0
uploadPackage(user, "1.1", http.StatusForbidden)
uploadPackage(admin, "1.1", http.StatusCreated)
setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize
setting.Packages.LimitTotalOwnerSize = 0
uploadPackage(user, "1.1", http.StatusForbidden)
uploadPackage(admin, "1.1", http.StatusCreated)
setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize

setting.Packages.LimitSizeGeneric = 0
uploadPackage(user, "1.2", http.StatusForbidden)
uploadPackage(admin, "1.2", http.StatusCreated)
setting.Packages.LimitSizeGeneric = limitSizeGeneric
setting.Packages.LimitSizeGeneric = 0
uploadPackage(user, "1.2", http.StatusForbidden)
uploadPackage(admin, "1.2", http.StatusCreated)
setting.Packages.LimitSizeGeneric = limitSizeGeneric
})

t.Run("Container", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

limitSizeContainer := setting.Packages.LimitSizeContainer

uploadBlob := func(doer *user_model.User, data string, expectedStatus int) {
url := fmt.Sprintf("/v2/%s/quota-test/blobs/uploads?digest=sha256:%x", user.Name, sha256.Sum256([]byte(data)))
req := NewRequestWithBody(t, "POST", url, strings.NewReader(data))
AddBasicAuthHeader(req, doer.Name)
MakeRequest(t, req, expectedStatus)
}

setting.Packages.LimitTotalOwnerSize = 0
uploadBlob(user, "2", http.StatusForbidden)
uploadBlob(admin, "2", http.StatusCreated)
setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize

setting.Packages.LimitSizeContainer = 0
uploadBlob(user, "3", http.StatusForbidden)
uploadBlob(admin, "3", http.StatusCreated)
setting.Packages.LimitSizeContainer = limitSizeContainer
})
}

func TestPackageCleanup(t *testing.T) {
Expand Down