diff --git a/src/common/security/v2token/context.go b/src/common/security/v2token/context.go index 03f682674..b26f2b356 100644 --- a/src/common/security/v2token/context.go +++ b/src/common/security/v2token/context.go @@ -99,12 +99,14 @@ func New(ctx context.Context, name string, access []*registry_token.ResourceActi actionMap[rbac.ActionPull] = struct{}{} case "push": actionMap[rbac.ActionPush] = struct{}{} + case "delete": + actionMap[rbac.ActionDelete] = struct{}{} + case "scanner-pull": + actionMap[rbac.ActionScannerPull] = struct{}{} case "*": actionMap[rbac.ActionPull] = struct{}{} actionMap[rbac.ActionPush] = struct{}{} actionMap[rbac.ActionDelete] = struct{}{} - case "scanner-pull": - actionMap[rbac.ActionScannerPull] = struct{}{} } } m[l[0]] = actionMap diff --git a/src/core/service/token/authutils.go b/src/core/service/token/authutils.go index 5cba3e1bc..c99fa1599 100644 --- a/src/core/service/token/authutils.go +++ b/src/core/service/token/authutils.go @@ -154,20 +154,3 @@ func MakeToken(username, service string, access []*token.ResourceActions) (*mode IssuedAt: now.Format(time.RFC3339), }, nil } - -func permToActions(p string) []string { - res := make([]string, 0) - if strings.Contains(p, "W") { - res = append(res, "push") - } - if strings.Contains(p, "M") { - res = append(res, "*") - } - if strings.Contains(p, "R") { - res = append(res, "pull") - } - if strings.Contains(p, "S") { - res = append(res, "scanner-pull") - } - return res -} diff --git a/src/core/service/token/creator.go b/src/core/service/token/creator.go index 319844d7f..1b76e6f3a 100644 --- a/src/core/service/token/creator.go +++ b/src/core/service/token/creator.go @@ -34,6 +34,14 @@ import ( var creatorMap map[string]Creator var registryFilterMap map[string]accessFilter var notaryFilterMap map[string]accessFilter +var actionScopeMap = map[rbac.Action]string{ + // Scopes checked by distribution, see: https://github.com/docker/distribution/blob/master/registry/handlers/app.go + rbac.ActionPull: "pull", + rbac.ActionPush: "push", + rbac.ActionDelete: "delete", + // For skipping policy check when scanner pulls artifacts + rbac.ActionScannerPull: "scanner-pull", +} const ( // Notary service @@ -163,7 +171,6 @@ func (rep repositoryFilter) filter(ctx context.Context, ctl project.Controller, return err } projectName := img.namespace - permission := "" project, err := ctl.GetByName(ctx, projectName) if err != nil { @@ -175,23 +182,26 @@ func (rep repositoryFilter) filter(ctx context.Context, ctl project.Controller, return err } - secCtx, _ := security.FromContext(ctx) - resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository) - if secCtx.Can(ctx, rbac.ActionPush, resource) && secCtx.Can(ctx, rbac.ActionPull, resource) { - permission = "RWM" - } else if secCtx.Can(ctx, rbac.ActionPush, resource) { - permission = "RW" - } else if secCtx.Can(ctx, rbac.ActionScannerPull, resource) { - permission = "RS" - } else if secCtx.Can(ctx, rbac.ActionPull, resource) { - permission = "R" + scopeList := make([]string, 0) + for s := range resourceScopes(ctx, resource) { + scopeList = append(scopeList, s) } - - a.Actions = permToActions(permission) + a.Actions = scopeList return nil } +func resourceScopes(ctx context.Context, rc rbac.Resource) map[string]struct{} { + sCtx, _ := security.FromContext(ctx) + res := map[string]struct{}{} + for a, s := range actionScopeMap { + if sCtx.Can(ctx, a, rc) { + res[s] = struct{}{} + } + } + return res +} + type generalCreator struct { service string filterMap map[string]accessFilter diff --git a/src/core/service/token/token_test.go b/src/core/service/token/token_test.go index bff9c6ef4..8a526a3c5 100644 --- a/src/core/service/token/token_test.go +++ b/src/core/service/token/token_test.go @@ -156,21 +156,6 @@ func TestMakeToken(t *testing.T) { assert.Equal(t, claims.Audience, svc, "Audience mismatch") } -func TestPermToActions(t *testing.T) { - perm1 := "RWM" - perm2 := "MRR" - perm3 := "" - expect1 := []string{"push", "*", "pull"} - expect2 := []string{"*", "pull"} - expect3 := make([]string, 0) - res1 := permToActions(perm1) - res2 := permToActions(perm2) - res3 := permToActions(perm3) - assert.Equal(t, res1, expect1, fmt.Sprintf("actions mismatch for permission: %s", perm1)) - assert.Equal(t, res2, expect2, fmt.Sprintf("actions mismatch for permission: %s", perm2)) - assert.Equal(t, res3, expect3, fmt.Sprintf("actions mismatch for permission: %s", perm3)) -} - type parserTestRec struct { input string expect image @@ -223,7 +208,8 @@ func TestEndpointParser(t *testing.T) { } type fakeSecurityContext struct { - isAdmin bool + isAdmin bool + rcActions map[rbac.Resource][]rbac.Action } func (f *fakeSecurityContext) Name() string { @@ -245,8 +231,16 @@ func (f *fakeSecurityContext) IsSolutionUser() bool { return false } func (f *fakeSecurityContext) Can(ctx context.Context, action rbac.Action, resource rbac.Resource) bool { + if actions, ok := f.rcActions[resource]; ok { + for _, a := range actions { + if a == action { + return true + } + } + } return false } + func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) { return nil, nil } @@ -303,3 +297,56 @@ func TestParseScopes(t *testing.T) { l1 := parseScopes(r1) assert.Equal([]string{"repository:library/registry:push,pull", "repository:hello-world/registry:pull"}, l1) } + +func TestResourceScopes(t *testing.T) { + sctx := &fakeSecurityContext{ + isAdmin: false, + rcActions: map[rbac.Resource][]rbac.Action{ + rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository): {rbac.ActionPull, rbac.ActionScannerPull}, + rbac.NewProjectNamespace(2).Resource(rbac.ResourceRepository): {rbac.ActionPull, rbac.ActionScannerPull, rbac.ActionPush}, + rbac.NewProjectNamespace(3).Resource(rbac.ResourceRepository): {rbac.ActionPull, rbac.ActionScannerPull, rbac.ActionPush, rbac.ActionDelete}, + rbac.NewProjectNamespace(4).Resource(rbac.ResourceRepository): {}, + }, + } + ctx := security.NewContext(context.TODO(), sctx) + cases := []struct { + rc rbac.Resource + expect map[string]struct{} + }{ + { + rc: rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository), + expect: map[string]struct{}{ + "pull": {}, + "scanner-pull": {}, + }, + }, + { + rc: rbac.NewProjectNamespace(2).Resource(rbac.ResourceRepository), + expect: map[string]struct{}{ + "pull": {}, + "scanner-pull": {}, + "push": {}, + }, + }, + { + rc: rbac.NewProjectNamespace(3).Resource(rbac.ResourceRepository), + expect: map[string]struct{}{ + "pull": {}, + "scanner-pull": {}, + "push": {}, + "delete": {}, + }, + }, + { + rc: rbac.NewProjectNamespace(4).Resource(rbac.ResourceRepository), + expect: map[string]struct{}{}, + }, + { + rc: rbac.NewProjectNamespace(5).Resource(rbac.ResourceRepository), + expect: map[string]struct{}{}, + }, + } + for _, c := range cases { + assert.Equal(t, c.expect, resourceScopes(ctx, c.rc)) + } +}