Refine the token scope generation

This commit directly maps the actoin permission in security context to
the scope generated by the token service in harbor-core.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2021-01-03 01:39:58 +08:00
parent bd46af691c
commit eb75123638
4 changed files with 90 additions and 48 deletions

View File

@ -99,12 +99,14 @@ func New(ctx context.Context, name string, access []*registry_token.ResourceActi
actionMap[rbac.ActionPull] = struct{}{} actionMap[rbac.ActionPull] = struct{}{}
case "push": case "push":
actionMap[rbac.ActionPush] = struct{}{} actionMap[rbac.ActionPush] = struct{}{}
case "delete":
actionMap[rbac.ActionDelete] = struct{}{}
case "scanner-pull":
actionMap[rbac.ActionScannerPull] = struct{}{}
case "*": case "*":
actionMap[rbac.ActionPull] = struct{}{} actionMap[rbac.ActionPull] = struct{}{}
actionMap[rbac.ActionPush] = struct{}{} actionMap[rbac.ActionPush] = struct{}{}
actionMap[rbac.ActionDelete] = struct{}{} actionMap[rbac.ActionDelete] = struct{}{}
case "scanner-pull":
actionMap[rbac.ActionScannerPull] = struct{}{}
} }
} }
m[l[0]] = actionMap m[l[0]] = actionMap

View File

@ -154,20 +154,3 @@ func MakeToken(username, service string, access []*token.ResourceActions) (*mode
IssuedAt: now.Format(time.RFC3339), IssuedAt: now.Format(time.RFC3339),
}, nil }, 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
}

View File

@ -34,6 +34,14 @@ import (
var creatorMap map[string]Creator var creatorMap map[string]Creator
var registryFilterMap map[string]accessFilter var registryFilterMap map[string]accessFilter
var notaryFilterMap 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 ( const (
// Notary service // Notary service
@ -163,7 +171,6 @@ func (rep repositoryFilter) filter(ctx context.Context, ctl project.Controller,
return err return err
} }
projectName := img.namespace projectName := img.namespace
permission := ""
project, err := ctl.GetByName(ctx, projectName) project, err := ctl.GetByName(ctx, projectName)
if err != nil { if err != nil {
@ -175,21 +182,24 @@ func (rep repositoryFilter) filter(ctx context.Context, ctl project.Controller,
return err return err
} }
secCtx, _ := security.FromContext(ctx)
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository) resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
if secCtx.Can(ctx, rbac.ActionPush, resource) && secCtx.Can(ctx, rbac.ActionPull, resource) { scopeList := make([]string, 0)
permission = "RWM" for s := range resourceScopes(ctx, resource) {
} else if secCtx.Can(ctx, rbac.ActionPush, resource) { scopeList = append(scopeList, s)
permission = "RW" }
} else if secCtx.Can(ctx, rbac.ActionScannerPull, resource) { a.Actions = scopeList
permission = "RS" return nil
} else if secCtx.Can(ctx, rbac.ActionPull, resource) {
permission = "R"
} }
a.Actions = permToActions(permission) func resourceScopes(ctx context.Context, rc rbac.Resource) map[string]struct{} {
return nil 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 { type generalCreator struct {

View File

@ -156,21 +156,6 @@ func TestMakeToken(t *testing.T) {
assert.Equal(t, claims.Audience, svc, "Audience mismatch") 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 { type parserTestRec struct {
input string input string
expect image expect image
@ -224,6 +209,7 @@ func TestEndpointParser(t *testing.T) {
type fakeSecurityContext struct { type fakeSecurityContext struct {
isAdmin bool isAdmin bool
rcActions map[rbac.Resource][]rbac.Action
} }
func (f *fakeSecurityContext) Name() string { func (f *fakeSecurityContext) Name() string {
@ -245,8 +231,16 @@ func (f *fakeSecurityContext) IsSolutionUser() bool {
return false return false
} }
func (f *fakeSecurityContext) Can(ctx context.Context, action rbac.Action, resource rbac.Resource) bool { 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 return false
} }
func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) { func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) {
return nil, nil return nil, nil
} }
@ -303,3 +297,56 @@ func TestParseScopes(t *testing.T) {
l1 := parseScopes(r1) l1 := parseScopes(r1)
assert.Equal([]string{"repository:library/registry:push,pull", "repository:hello-world/registry:pull"}, l1) 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))
}
}