fix(storage): deleting manifests with identical digests (#951)

Suppose we push two identical manifests (sharing same digest) but with
different tags, then deleting by digest should throw an error otherwise
we end up deleting all image tags (with gc) or dangling references
(without gc)

This behaviour is controlled via Authorization, added a new policy
action named detectManifestsCollision which enables this behaviour

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>

Co-authored-by: Ramkumar Chinchani <rchincha@cisco.com>
This commit is contained in:
peusebiu
2022-11-18 19:35:28 +02:00
committed by GitHub
parent 4e13619dc8
commit 168d21da1e
22 changed files with 507 additions and 141 deletions
+8 -28
View File
@@ -12,7 +12,6 @@ import (
"strings"
"github.com/99designs/gqlgen/graphql"
glob "github.com/bmatcuk/doublestar/v4"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/vektah/gqlparser/v2/gqlerror"
@@ -734,40 +733,21 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
return imageInfo
}
// returns either a user has or not rights on 'repository'.
func matchesRepo(globPatterns map[string]bool, repository string) bool {
var longestMatchedPattern string
// because of the longest path matching rule, we need to check all patterns from config
for pattern := range globPatterns {
matched, err := glob.Match(pattern, repository)
if err == nil {
if matched && len(pattern) > len(longestMatchedPattern) {
longestMatchedPattern = pattern
}
}
}
allowed := globPatterns[longestMatchedPattern]
return allowed
}
// get passed context from authzHandler and filter out repos based on permissions.
func userAvailableRepos(ctx context.Context, repoList []string) ([]string, error) {
var availableRepos []string
authzCtxKey := localCtx.GetContextKey()
if authCtx := ctx.Value(authzCtxKey); authCtx != nil {
acCtx, ok := authCtx.(localCtx.AccessControlContext)
if !ok {
err := errors.ErrBadType
// authz request context (set in authz middleware)
acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil {
err := errors.ErrBadType
return []string{}, err
}
return []string{}, err
}
if acCtx != nil {
for _, r := range repoList {
if acCtx.IsAdmin || matchesRepo(acCtx.GlobPatterns, r) {
if acCtx.IsAdmin || acCtx.CanReadRepo(r) {
availableRepos = append(availableRepos, r)
}
}
+4 -4
View File
@@ -337,8 +337,8 @@ func TestExtractImageDetails(t *testing.T) {
authzCtxKey := localCtx.GetContextKey()
ctx = context.WithValue(ctx, authzCtxKey,
localCtx.AccessControlContext{
GlobPatterns: map[string]bool{"*": true, "**": true},
Username: "jane_doe",
ReadGlobPatterns: map[string]bool{"*": true, "**": true},
Username: "jane_doe",
})
configBlobContent, _ := json.MarshalIndent(&config, "", "\t")
configDigest := godigest.FromBytes(configBlobContent)
@@ -429,8 +429,8 @@ func TestExtractImageDetails(t *testing.T) {
Convey("extractImageDetails without proper authz", func() {
ctx = context.WithValue(ctx, authzCtxKey,
localCtx.AccessControlContext{
GlobPatterns: map[string]bool{},
Username: "jane_doe",
ReadGlobPatterns: map[string]bool{},
Username: "jane_doe",
})
mockOlum := mocks.OciLayoutUtilsMock{
GetImageConfigInfoFn: func(repo string, digest godigest.Digest) (
+1 -1
View File
@@ -1408,7 +1408,7 @@ func TestBasicAuth(t *testing.T) {
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
err = dctlr.StoreController.DefaultStore.DeleteImageManifest(testImage, testImageTag)
err = dctlr.StoreController.DefaultStore.DeleteImageManifest(testImage, testImageTag, false)
So(err, ShouldBeNil)
resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + "1.1.1")