mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 22:57:38 +01:00
update per comments and fix govet error
Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
22b4ea0f89
commit
5996189bb0
@ -28,7 +28,8 @@ const (
|
||||
ActionDelete = Action("delete")
|
||||
ActionList = Action("list")
|
||||
|
||||
ActionOperate = Action("operate")
|
||||
ActionOperate = Action("operate")
|
||||
ActionScannerPull = Action("scannerpull") // for robot account created by scanner to pull image, bypass the policy check
|
||||
)
|
||||
|
||||
// const resource variables
|
||||
|
@ -64,11 +64,6 @@ func (s *SecurityContext) IsSysAdmin() bool {
|
||||
return s.ctx.IsSysAdmin()
|
||||
}
|
||||
|
||||
// PolicyCheck ...
|
||||
func (s *SecurityContext) PolicyCheck() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSolutionUser ...
|
||||
func (s *SecurityContext) IsSolutionUser() bool {
|
||||
return false
|
||||
|
@ -27,8 +27,6 @@ type Context interface {
|
||||
GetUsername() string
|
||||
// IsSysAdmin returns whether the user is system admin
|
||||
IsSysAdmin() bool
|
||||
// PolicyCheck returns whether the middlerwares apply to the request
|
||||
PolicyCheck() bool
|
||||
// IsSolutionUser returns whether the user is solution user
|
||||
IsSolutionUser() bool
|
||||
// Get current user's all project
|
||||
|
@ -61,11 +61,6 @@ func (s *SecurityContext) IsSysAdmin() bool {
|
||||
return s.user.HasAdminRole
|
||||
}
|
||||
|
||||
// PolicyCheck ...
|
||||
func (s *SecurityContext) PolicyCheck() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSolutionUser ...
|
||||
func (s *SecurityContext) IsSolutionUser() bool {
|
||||
return false
|
||||
|
@ -23,19 +23,17 @@ import (
|
||||
|
||||
// SecurityContext implements security.Context interface based on database
|
||||
type SecurityContext struct {
|
||||
robot *model.Robot
|
||||
pm promgr.ProjectManager
|
||||
policy []*rbac.Policy
|
||||
polichCheck bool
|
||||
robot *model.Robot
|
||||
pm promgr.ProjectManager
|
||||
policy []*rbac.Policy
|
||||
}
|
||||
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(robot *model.Robot, pm promgr.ProjectManager, policy []*rbac.Policy, polichCheck bool) *SecurityContext {
|
||||
func NewSecurityContext(robot *model.Robot, pm promgr.ProjectManager, policy []*rbac.Policy) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
robot: robot,
|
||||
pm: pm,
|
||||
policy: policy,
|
||||
polichCheck: polichCheck,
|
||||
robot: robot,
|
||||
pm: pm,
|
||||
policy: policy,
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,11 +56,6 @@ func (s *SecurityContext) IsSysAdmin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PolicyCheck ...
|
||||
func (s *SecurityContext) PolicyCheck() bool {
|
||||
return s.polichCheck
|
||||
}
|
||||
|
||||
// IsSolutionUser robot cannot be a system admin
|
||||
func (s *SecurityContext) IsSolutionUser() bool {
|
||||
return false
|
||||
|
@ -93,45 +93,45 @@ func TestMain(m *testing.M) {
|
||||
|
||||
func TestIsAuthenticated(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, nil, nil, true)
|
||||
ctx := NewSecurityContext(nil, nil, nil)
|
||||
assert.False(t, ctx.IsAuthenticated())
|
||||
|
||||
// authenticated
|
||||
ctx = NewSecurityContext(&model.Robot{
|
||||
Name: "test",
|
||||
Disabled: false,
|
||||
}, nil, nil, true)
|
||||
}, nil, nil)
|
||||
assert.True(t, ctx.IsAuthenticated())
|
||||
}
|
||||
|
||||
func TestGetUsername(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, nil, nil, true)
|
||||
ctx := NewSecurityContext(nil, nil, nil)
|
||||
assert.Equal(t, "", ctx.GetUsername())
|
||||
|
||||
// authenticated
|
||||
ctx = NewSecurityContext(&model.Robot{
|
||||
Name: "test",
|
||||
Disabled: false,
|
||||
}, nil, nil, true)
|
||||
}, nil, nil)
|
||||
assert.Equal(t, "test", ctx.GetUsername())
|
||||
}
|
||||
|
||||
func TestIsSysAdmin(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, nil, nil, true)
|
||||
ctx := NewSecurityContext(nil, nil, nil)
|
||||
assert.False(t, ctx.IsSysAdmin())
|
||||
|
||||
// authenticated, non admin
|
||||
ctx = NewSecurityContext(&model.Robot{
|
||||
Name: "test",
|
||||
Disabled: false,
|
||||
}, nil, nil, true)
|
||||
}, nil, nil)
|
||||
assert.False(t, ctx.IsSysAdmin())
|
||||
}
|
||||
|
||||
func TestIsSolutionUser(t *testing.T) {
|
||||
ctx := NewSecurityContext(nil, nil, nil, true)
|
||||
ctx := NewSecurityContext(nil, nil, nil)
|
||||
assert.False(t, ctx.IsSolutionUser())
|
||||
}
|
||||
|
||||
@ -147,7 +147,7 @@ func TestHasPullPerm(t *testing.T) {
|
||||
Description: "desc",
|
||||
}
|
||||
|
||||
ctx := NewSecurityContext(robot, pm, policies, true)
|
||||
ctx := NewSecurityContext(robot, pm, policies)
|
||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||
assert.True(t, ctx.Can(rbac.ActionPull, resource))
|
||||
}
|
||||
@ -164,7 +164,7 @@ func TestHasPushPerm(t *testing.T) {
|
||||
Description: "desc",
|
||||
}
|
||||
|
||||
ctx := NewSecurityContext(robot, pm, policies, true)
|
||||
ctx := NewSecurityContext(robot, pm, policies)
|
||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||
assert.True(t, ctx.Can(rbac.ActionPush, resource))
|
||||
}
|
||||
@ -185,20 +185,20 @@ func TestHasPushPullPerm(t *testing.T) {
|
||||
Description: "desc",
|
||||
}
|
||||
|
||||
ctx := NewSecurityContext(robot, pm, policies, true)
|
||||
ctx := NewSecurityContext(robot, pm, policies)
|
||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||
assert.True(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
|
||||
}
|
||||
|
||||
func TestGetMyProjects(t *testing.T) {
|
||||
ctx := NewSecurityContext(nil, nil, nil, true)
|
||||
ctx := NewSecurityContext(nil, nil, nil)
|
||||
projects, err := ctx.GetMyProjects()
|
||||
require.Nil(t, err)
|
||||
assert.Nil(t, projects)
|
||||
}
|
||||
|
||||
func TestGetProjectRoles(t *testing.T) {
|
||||
ctx := NewSecurityContext(nil, nil, nil, true)
|
||||
ctx := NewSecurityContext(nil, nil, nil)
|
||||
roles := ctx.GetProjectRoles("test")
|
||||
assert.Nil(t, roles)
|
||||
}
|
||||
|
@ -66,11 +66,6 @@ func (s *SecurityContext) IsSysAdmin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PolicyCheck ...
|
||||
func (s *SecurityContext) PolicyCheck() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSolutionUser ...
|
||||
func (s *SecurityContext) IsSolutionUser() bool {
|
||||
return s.IsAuthenticated()
|
||||
|
@ -76,7 +76,7 @@ func GetTargets(notaryEndpoint string, username string, fqRepo string) ([]model.
|
||||
Type: "repository",
|
||||
Name: fqRepo,
|
||||
Actions: []string{"pull"},
|
||||
}}, true)
|
||||
}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ func RefreshToken(ctx context.Context, token *Token) (*Token, error) {
|
||||
return &Token{Token: *t, IDToken: it}, nil
|
||||
}
|
||||
|
||||
// GroupsFromToken returns the list of group name in the token, the claim of the group list is set in OIDCSetting.
|
||||
// GroupsFromToken returns the list of group name in the token, the claims of the group list is set in OIDCSetting.
|
||||
// It's designed not to return errors, in case of unexpected situation it will log and return empty list.
|
||||
func GroupsFromToken(token *gooidc.IDToken) []string {
|
||||
if token == nil {
|
||||
@ -217,7 +217,7 @@ func GroupsFromToken(token *gooidc.IDToken) []string {
|
||||
}
|
||||
setting := provider.setting.Load().(models.OIDCSetting)
|
||||
if len(setting.GroupsClaim) == 0 {
|
||||
log.Warning("Group claim is not set in OIDC setting returning empty group list.")
|
||||
log.Warning("Group claims is not set in OIDC setting returning empty group list.")
|
||||
return []string{}
|
||||
}
|
||||
var c map[string]interface{}
|
||||
@ -233,7 +233,7 @@ func groupsFromClaim(claimMap map[string]interface{}, k string) []string {
|
||||
var res []string
|
||||
g, ok := claimMap[k].([]interface{})
|
||||
if !ok {
|
||||
log.Warningf("Unable to get groups from claims, claims: %+v, groups claim key: %s", claimMap, k)
|
||||
log.Warningf("Unable to get groups from claims, claims: %+v, groups claims key: %s", claimMap, k)
|
||||
return res
|
||||
}
|
||||
for _, e := range g {
|
||||
|
@ -346,7 +346,7 @@ type rawTokenGenerator struct {
|
||||
|
||||
// generate token directly
|
||||
func (r *rawTokenGenerator) generate(scopes []*token.ResourceActions, endpoint string) (*models.Token, error) {
|
||||
return token_util.MakeToken(r.username, r.service, scopes, true)
|
||||
return token_util.MakeToken(r.username, r.service, scopes)
|
||||
}
|
||||
|
||||
func buildPingURL(endpoint string) string {
|
||||
|
@ -108,7 +108,7 @@ func (r *RobotAPI) Post() {
|
||||
return
|
||||
}
|
||||
robotReq.Visible = true
|
||||
robotReq.PolicyCheck = true
|
||||
robotReq.ByPassPolicyCheck = false
|
||||
robotReq.ProjectID = r.project.ProjectID
|
||||
|
||||
if err := validateRobotReq(r.project, &robotReq); err != nil {
|
||||
|
@ -44,7 +44,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/pkg/authproxy"
|
||||
"github.com/goharbor/harbor/src/pkg/robot"
|
||||
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
||||
"github.com/goharbor/harbor/src/pkg/token/claim"
|
||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
||||
)
|
||||
|
||||
// ContextValueKey for content value
|
||||
@ -189,17 +189,16 @@ func (r *robotAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
if !strings.HasPrefix(robotName, common.RobotPrefix) {
|
||||
return false
|
||||
}
|
||||
rClaims := &claim.Robot{}
|
||||
rClaims := &robot_claim.Claim{}
|
||||
opt := pkg_token.DefaultTokenOptions()
|
||||
rtk, err := pkg_token.Parse(opt, robotTk, rClaims)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt robot token, %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Do authn for robot account, as Harbor only stores the token ID, just validate the ID and disable.
|
||||
ctr := robot.RobotCtr
|
||||
robot, err := ctr.GetRobotAccount(rtk.Claims.(*claim.Robot).TokenID)
|
||||
robot, err := ctr.GetRobotAccount(rtk.Claims.(*robot_claim.Claim).TokenID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get robot %s: %v", robotName, err)
|
||||
return false
|
||||
@ -218,7 +217,7 @@ func (r *robotAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
}
|
||||
log.Debug("creating robot account security context...")
|
||||
pm := config.GlobalProjectMgr
|
||||
securCtx := robotCtx.NewSecurityContext(robot, pm, rtk.Claims.(*claim.Robot).Access, rClaims.PolicyCheck)
|
||||
securCtx := robotCtx.NewSecurityContext(robot, pm, rtk.Claims.(*robot_claim.Claim).Access)
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
return true
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const (
|
||||
SIZEQUOTA = "sizequota"
|
||||
COUNTQUOTA = "countquota"
|
||||
IMMUTABLE = "immutable"
|
||||
REGTOKEN = "REGTOKEN"
|
||||
REGTOKEN = "regtoken"
|
||||
)
|
||||
|
||||
// ChartMiddlewares middlewares for chart server
|
||||
|
@ -50,10 +50,12 @@ func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Reque
|
||||
return
|
||||
}
|
||||
// Token bypass policy check
|
||||
policyCheck := req.Context().Value(util.PolicyCheckCtxKey)
|
||||
if policyCheck != nil && !policyCheck.(bool) {
|
||||
cth.next.ServeHTTP(rw, req)
|
||||
return
|
||||
if bypassPC := req.Context().Value(util.ByPassPolicyCheckCtxKey); bypassPC != nil {
|
||||
bypassPolicyCheck, ok := bypassPC.(bool)
|
||||
if ok && bypassPolicyCheck {
|
||||
cth.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !util.GetPolicyChecker().ContentTrustEnabled(img.ProjectName) {
|
||||
cth.next.ServeHTTP(rw, req)
|
||||
|
@ -2,15 +2,20 @@ package regtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/docker/distribution/registry/auth"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
||||
"github.com/goharbor/harbor/src/pkg/token/claim"
|
||||
"github.com/goharbor/harbor/src/pkg/token/claims/registry"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// regTokenHandler is responsible for decoding the registry token in the docker pull request header,
|
||||
// as harbor adds customized claims action into registry auth token, the middlerware is for decode it and write it into
|
||||
// request context, then for other middlerwares in chain to use it to bypass request validation.
|
||||
type regTokenHandler struct {
|
||||
next http.Handler
|
||||
}
|
||||
@ -24,6 +29,7 @@ func New(next http.Handler) http.Handler {
|
||||
|
||||
// ServeHTTP ...
|
||||
func (r *regTokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
imgRaw := req.Context().Value(util.ImageInfoCtxKey)
|
||||
if imgRaw == nil || !config.WithClair() {
|
||||
r.next.ServeHTTP(rw, req)
|
||||
@ -42,14 +48,30 @@ func (r *regTokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
rawToken := parts[1]
|
||||
opt := pkg_token.DefaultTokenOptions()
|
||||
rClaims := &claim.Registry{}
|
||||
rtk, err := pkg_token.Parse(opt, rawToken, rClaims)
|
||||
regTK, err := pkg_token.Parse(opt, rawToken, ®istry.Claim{})
|
||||
if err != nil {
|
||||
log.Debug("failed to decode reg token: %v, the error is skipped and round the request to native registry.", err)
|
||||
log.Debugf("failed to decode reg token: %v, the error is skipped and round the request to native registry.", err)
|
||||
r.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(req.Context(), util.PolicyCheckCtxKey, rtk.Claims.(*claim.Registry).PolicyCheck)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
accessItems := []auth.Access{}
|
||||
accessItems = append(accessItems, auth.Access{
|
||||
Resource: auth.Resource{
|
||||
Type: "repository",
|
||||
Name: img.Repository,
|
||||
},
|
||||
Action: rbac.ActionScannerPull.String(),
|
||||
})
|
||||
|
||||
accessSet := regTK.Claims.(*registry.Claim).GetAccessSet()
|
||||
for _, access := range accessItems {
|
||||
if accessSet.Contains(access) {
|
||||
ctx := context.WithValue(req.Context(), util.ByPassPolicyCheckCtxKey, true)
|
||||
req = req.WithContext(ctx)
|
||||
r.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
r.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
@ -49,8 +49,8 @@ type contextKey string
|
||||
const (
|
||||
// ImageInfoCtxKey the context key for image information
|
||||
ImageInfoCtxKey = contextKey("ImageInfo")
|
||||
// PolicyCheckCtxKey the context key for image information
|
||||
PolicyCheckCtxKey = contextKey("PolicyCheck")
|
||||
// ByPassPolicyCheckCtxKey the context key for robot account to bypass the pull policy check.
|
||||
ByPassPolicyCheckCtxKey = contextKey("ByPassPolicyCheck")
|
||||
// TokenUsername ...
|
||||
// TODO: temp solution, remove after vmware/harbor#2242 is resolved.
|
||||
TokenUsername = "harbor-core"
|
||||
|
@ -53,10 +53,12 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
|
||||
// Token bypass policy check
|
||||
policyCheck := req.Context().Value(util.PolicyCheckCtxKey)
|
||||
if policyCheck != nil && !policyCheck.(bool) {
|
||||
vh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
if bypassPC := req.Context().Value(util.ByPassPolicyCheckCtxKey); bypassPC != nil {
|
||||
bypassPolicyCheck, ok := bypassPC.(bool)
|
||||
if ok && bypassPolicyCheck {
|
||||
vh.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Is vulnerable policy set?
|
||||
|
@ -42,12 +42,6 @@ func init() {
|
||||
privateKey = config.TokenPrivateKeyPath()
|
||||
}
|
||||
|
||||
// ClaimSet ...
|
||||
type ClaimSet struct {
|
||||
token.ClaimSet
|
||||
PolicyCheck bool `json:"policy_check"`
|
||||
}
|
||||
|
||||
// GetResourceActions ...
|
||||
func GetResourceActions(scopes []string) []*token.ResourceActions {
|
||||
log.Debugf("scopes: %+v", scopes)
|
||||
@ -106,7 +100,7 @@ func filterAccess(access []*token.ResourceActions, ctx security.Context,
|
||||
}
|
||||
|
||||
// MakeToken makes a valid jwt token based on parms.
|
||||
func MakeToken(username, service string, access []*token.ResourceActions, policyCheck bool) (*models.Token, error) {
|
||||
func MakeToken(username, service string, access []*token.ResourceActions) (*models.Token, error) {
|
||||
pk, err := libtrust.LoadKeyFile(privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -116,7 +110,7 @@ func MakeToken(username, service string, access []*token.ResourceActions, policy
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk, policyCheck)
|
||||
tk, expiresIn, issuedAt, err := makeTokenCore(issuer, username, service, expiration, access, pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -139,12 +133,15 @@ func permToActions(p string) []string {
|
||||
if strings.Contains(p, "R") {
|
||||
res = append(res, "pull")
|
||||
}
|
||||
if strings.Contains(p, "S") {
|
||||
res = append(res, "scannerpull")
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// make token core
|
||||
func makeTokenCore(issuer, subject, audience string, expiration int,
|
||||
access []*token.ResourceActions, signingKey libtrust.PrivateKey, policyCheck bool) (t *token.Token, expiresIn int, issuedAt *time.Time, err error) {
|
||||
access []*token.ResourceActions, signingKey libtrust.PrivateKey) (t *token.Token, expiresIn int, issuedAt *time.Time, err error) {
|
||||
|
||||
joseHeader := &token.Header{
|
||||
Type: "JWT",
|
||||
@ -161,18 +158,15 @@ func makeTokenCore(issuer, subject, audience string, expiration int,
|
||||
issuedAt = &now
|
||||
expiresIn = expiration * 60
|
||||
|
||||
claimSet := &ClaimSet{
|
||||
ClaimSet: token.ClaimSet{
|
||||
Issuer: issuer,
|
||||
Subject: subject,
|
||||
Audience: audience,
|
||||
Expiration: now.Add(time.Duration(expiration) * time.Minute).Unix(),
|
||||
NotBefore: now.Unix(),
|
||||
IssuedAt: now.Unix(),
|
||||
JWTID: jwtID,
|
||||
Access: access,
|
||||
},
|
||||
PolicyCheck: policyCheck,
|
||||
claimSet := &token.ClaimSet{
|
||||
Issuer: issuer,
|
||||
Subject: subject,
|
||||
Audience: audience,
|
||||
Expiration: now.Add(time.Duration(expiration) * time.Minute).Unix(),
|
||||
NotBefore: now.Unix(),
|
||||
IssuedAt: now.Unix(),
|
||||
JWTID: jwtID,
|
||||
Access: access,
|
||||
}
|
||||
|
||||
var joseHeaderBytes, claimSetBytes []byte
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/filter"
|
||||
"github.com/goharbor/harbor/src/core/promgr"
|
||||
"net/http/httputil"
|
||||
)
|
||||
|
||||
var creatorMap map[string]Creator
|
||||
@ -154,7 +155,7 @@ type repositoryFilter struct {
|
||||
|
||||
func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
|
||||
a *token.ResourceActions) error {
|
||||
// clear action list to assign to new acess element after perm check.
|
||||
// clear action list to assign to new access element after perm check.
|
||||
img, err := rep.parser.parse(a.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -180,6 +181,9 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManage
|
||||
} else if ctx.Can(rbac.ActionPull, resource) {
|
||||
permission = "R"
|
||||
}
|
||||
if ctx.Can(rbac.ActionScannerPull, resource) {
|
||||
permission = "S"
|
||||
}
|
||||
|
||||
a.Actions = permToActions(permission)
|
||||
return nil
|
||||
@ -200,6 +204,7 @@ func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
|
||||
var err error
|
||||
scopes := parseScopes(r.URL)
|
||||
log.Debugf("scopes: %v", scopes)
|
||||
httputil.DumpRequest(r, true)
|
||||
|
||||
ctx, err := filter.GetSecurityContext(r)
|
||||
if err != nil {
|
||||
@ -222,7 +227,7 @@ func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MakeToken(ctx.GetUsername(), g.service, access, ctx.PolicyCheck())
|
||||
return MakeToken(ctx.GetUsername(), g.service, access)
|
||||
}
|
||||
|
||||
func parseScopes(u *url.URL) []string {
|
||||
|
@ -119,8 +119,7 @@ func getPublicKey(crtPath string) (*rsa.PublicKey, error) {
|
||||
type harborClaims struct {
|
||||
jwt.StandardClaims
|
||||
// Private claims
|
||||
Access []*token.ResourceActions `json:"access"`
|
||||
PolicyCheck bool `json:"policy_check"`
|
||||
Access []*token.ResourceActions `json:"access"`
|
||||
}
|
||||
|
||||
func TestMakeToken(t *testing.T) {
|
||||
@ -134,7 +133,7 @@ func TestMakeToken(t *testing.T) {
|
||||
}}
|
||||
svc := "harbor-registry"
|
||||
u := "tester"
|
||||
tokenJSON, err := MakeToken(u, svc, ra, true)
|
||||
tokenJSON, err := MakeToken(u, svc, ra)
|
||||
if err != nil {
|
||||
t.Errorf("Error while making token: %v", err)
|
||||
}
|
||||
@ -155,7 +154,6 @@ func TestMakeToken(t *testing.T) {
|
||||
t.Errorf("Error while parsing the token: %v", err)
|
||||
}
|
||||
claims := tok.Claims.(*harborClaims)
|
||||
assert.True(t, claims.PolicyCheck)
|
||||
assert.Equal(t, *(claims.Access[0]), *(ra[0]), "Access mismatch")
|
||||
assert.Equal(t, claims.Audience, svc, "Audience mismatch")
|
||||
}
|
||||
@ -244,9 +242,6 @@ func (f *fakeSecurityContext) IsSysAdmin() bool {
|
||||
func (f *fakeSecurityContext) IsSolutionUser() bool {
|
||||
return false
|
||||
}
|
||||
func (f *fakeSecurityContext) PolicyCheck() bool {
|
||||
return false
|
||||
}
|
||||
func (f *fakeSecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
|
||||
return false
|
||||
}
|
||||
|
@ -4,12 +4,13 @@ import (
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/token"
|
||||
"github.com/goharbor/harbor/src/pkg/token/claim"
|
||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
@ -71,6 +72,12 @@ func (d *DefaultAPIController) CreateRobotAccount(robotReq *model.RobotCreate) (
|
||||
ExpiresAt: expiresAt,
|
||||
Visible: robotReq.Visible,
|
||||
}
|
||||
if robotReq.ByPassPolicyCheck {
|
||||
robotReq.Access = append(robotReq.Access, &rbac.Policy{
|
||||
Resource: rbac.NewProjectNamespace(robotReq.ProjectID).Resource(rbac.ResourceRepository),
|
||||
Action: rbac.ActionScannerPull,
|
||||
})
|
||||
}
|
||||
id, err := d.manager.CreateRobotAccount(robot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -79,11 +86,10 @@ func (d *DefaultAPIController) CreateRobotAccount(robotReq *model.RobotCreate) (
|
||||
// generate the token, and return it with response data.
|
||||
// token is not stored in the database.
|
||||
opt := token.DefaultTokenOptions()
|
||||
rClaims := &claim.Robot{
|
||||
TokenID: id,
|
||||
ProjectID: robotReq.ProjectID,
|
||||
Access: robotReq.Access,
|
||||
PolicyCheck: robotReq.PolicyCheck,
|
||||
rClaims := &robot_claim.Claim{
|
||||
TokenID: id,
|
||||
ProjectID: robotReq.ProjectID,
|
||||
Access: robotReq.Access,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
IssuedAt: time.Now().UTC().Unix(),
|
||||
ExpiresAt: expiresAt,
|
||||
|
@ -45,13 +45,13 @@ type RobotQuery struct {
|
||||
|
||||
// RobotCreate ...
|
||||
type RobotCreate struct {
|
||||
Name string `json:"name"`
|
||||
ProjectID int64 `json:"pid"`
|
||||
Description string `json:"description"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Visible bool `json:"-"`
|
||||
PolicyCheck bool `json:"-"`
|
||||
Access []*rbac.Policy `json:"access"`
|
||||
Name string `json:"name"`
|
||||
ProjectID int64 `json:"pid"`
|
||||
Description string `json:"description"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Visible bool `json:"-"`
|
||||
ByPassPolicyCheck bool `json:"-"`
|
||||
Access []*rbac.Policy `json:"access"`
|
||||
}
|
||||
|
||||
// Pagination ...
|
||||
|
@ -1,22 +0,0 @@
|
||||
package claim
|
||||
|
||||
import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
)
|
||||
|
||||
// Registry implements the interface of jwt.Claims
|
||||
type Registry struct {
|
||||
jwt.StandardClaims
|
||||
PolicyCheck bool `json:"policy_check"`
|
||||
Access []*token.ResourceActions `json:"access"`
|
||||
}
|
||||
|
||||
// Valid valid the standard claims
|
||||
func (rc *Registry) Valid() error {
|
||||
stdErr := rc.StandardClaims.Valid()
|
||||
if stdErr != nil {
|
||||
return stdErr
|
||||
}
|
||||
return nil
|
||||
}
|
18
src/pkg/token/claims/registry/accessset.go
Normal file
18
src/pkg/token/claims/registry/accessset.go
Normal file
@ -0,0 +1,18 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"github.com/docker/distribution/registry/auth"
|
||||
)
|
||||
|
||||
// AccessSet ...
|
||||
type AccessSet map[auth.Resource]actionSet
|
||||
|
||||
// Contains ...
|
||||
func (s AccessSet) Contains(access auth.Access) bool {
|
||||
actionSet, ok := s[access.Resource]
|
||||
if ok {
|
||||
return actionSet.contains(access.Action)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
14
src/pkg/token/claims/registry/actionset.go
Normal file
14
src/pkg/token/claims/registry/actionset.go
Normal file
@ -0,0 +1,14 @@
|
||||
package registry
|
||||
|
||||
// actionSet is a special type of stringSet.
|
||||
type actionSet struct {
|
||||
stringSet
|
||||
}
|
||||
|
||||
func newActionSet(actions ...string) actionSet {
|
||||
return actionSet{newStringSet(actions...)}
|
||||
}
|
||||
|
||||
func (s actionSet) contains(action string) bool {
|
||||
return s.stringSet.contains(action)
|
||||
}
|
42
src/pkg/token/claims/registry/registry.go
Normal file
42
src/pkg/token/claims/registry/registry.go
Normal file
@ -0,0 +1,42 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/docker/distribution/registry/auth"
|
||||
"github.com/docker/distribution/registry/auth/token"
|
||||
)
|
||||
|
||||
// Claim implements the interface of jwt.Claims
|
||||
type Claim struct {
|
||||
jwt.StandardClaims
|
||||
Access []*token.ResourceActions `json:"access"`
|
||||
}
|
||||
|
||||
// Valid valid the standard claims
|
||||
func (rc *Claim) Valid() error {
|
||||
stdErr := rc.StandardClaims.Valid()
|
||||
if stdErr != nil {
|
||||
return stdErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAccessSet ...
|
||||
func (rc *Claim) GetAccessSet() AccessSet {
|
||||
accessSet := make(AccessSet, len(rc.Access))
|
||||
for _, resourceActions := range rc.Access {
|
||||
resource := auth.Resource{
|
||||
Type: resourceActions.Type,
|
||||
Name: resourceActions.Name,
|
||||
}
|
||||
set, exists := accessSet[resource]
|
||||
if !exists {
|
||||
set = newActionSet()
|
||||
accessSet[resource] = set
|
||||
}
|
||||
for _, action := range resourceActions.Actions {
|
||||
set.add(action)
|
||||
}
|
||||
}
|
||||
return accessSet
|
||||
}
|
21
src/pkg/token/claims/registry/stringset.go
Normal file
21
src/pkg/token/claims/registry/stringset.go
Normal file
@ -0,0 +1,21 @@
|
||||
package registry
|
||||
|
||||
// StringSet is a useful type for looking up strings.
|
||||
type stringSet map[string]struct{}
|
||||
|
||||
func newStringSet(keys ...string) stringSet {
|
||||
ss := make(stringSet, len(keys))
|
||||
ss.add(keys...)
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss stringSet) add(keys ...string) {
|
||||
for _, key := range keys {
|
||||
ss[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss stringSet) contains(key string) bool {
|
||||
_, ok := ss[key]
|
||||
return ok
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package claim
|
||||
package robot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -6,17 +6,16 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
)
|
||||
|
||||
// Robot implements the interface of jwt.Claims
|
||||
type Robot struct {
|
||||
// Claim implements the interface of jwt.Claims
|
||||
type Claim struct {
|
||||
jwt.StandardClaims
|
||||
TokenID int64 `json:"id"`
|
||||
ProjectID int64 `json:"pid"`
|
||||
PolicyCheck bool `json:"policy_check"`
|
||||
Access []*rbac.Policy `json:"access"`
|
||||
TokenID int64 `json:"id"`
|
||||
ProjectID int64 `json:"pid"`
|
||||
Access []*rbac.Policy `json:"access"`
|
||||
}
|
||||
|
||||
// Valid valid the claims "tokenID, projectID and access".
|
||||
func (rc Robot) Valid() error {
|
||||
func (rc Claim) Valid() error {
|
||||
if rc.TokenID < 0 {
|
||||
return errors.New("Token id must an valid INT")
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package claim
|
||||
package robot
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
@ -15,7 +15,7 @@ func TestValid(t *testing.T) {
|
||||
policies := []*rbac.Policy{}
|
||||
policies = append(policies, rbacPolicy)
|
||||
|
||||
rClaims := &Robot{
|
||||
rClaims := &Claim{
|
||||
TokenID: 1,
|
||||
ProjectID: 2,
|
||||
Access: policies,
|
||||
@ -32,7 +32,7 @@ func TestUnValidTokenID(t *testing.T) {
|
||||
policies := []*rbac.Policy{}
|
||||
policies = append(policies, rbacPolicy)
|
||||
|
||||
rClaims := &Robot{
|
||||
rClaims := &Claim{
|
||||
TokenID: -1,
|
||||
ProjectID: 2,
|
||||
Access: policies,
|
||||
@ -49,7 +49,7 @@ func TestUnValidProjectID(t *testing.T) {
|
||||
policies := []*rbac.Policy{}
|
||||
policies = append(policies, rbacPolicy)
|
||||
|
||||
rClaims := &Robot{
|
||||
rClaims := &Claim{
|
||||
TokenID: 1,
|
||||
ProjectID: -2,
|
||||
Access: policies,
|
||||
@ -59,7 +59,7 @@ func TestUnValidProjectID(t *testing.T) {
|
||||
|
||||
func TestUnValidPolicy(t *testing.T) {
|
||||
|
||||
rClaims := &Robot{
|
||||
rClaims := &Claim{
|
||||
TokenID: 1,
|
||||
ProjectID: 2,
|
||||
Access: nil,
|
@ -11,7 +11,7 @@ func TestNewOptions(t *testing.T) {
|
||||
defaultOpt := DefaultTokenOptions()
|
||||
assert.NotNil(t, defaultOpt)
|
||||
assert.Equal(t, defaultOpt.SignMethod, jwt.GetSigningMethod("RS256"))
|
||||
assert.Equal(t, defaultOpt.Issuer, "harbor-token-issuer")
|
||||
assert.Equal(t, defaultOpt.Issuer, "harbor-token-defaultIssuer")
|
||||
assert.Equal(t, defaultOpt.TTL, 60*time.Minute)
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/pkg/token/claim"
|
||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -35,7 +35,7 @@ func TestNew(t *testing.T) {
|
||||
projectID := int64(321)
|
||||
tokenExpiration := time.Duration(10) * 24 * time.Hour
|
||||
expiresAt := time.Now().UTC().Add(tokenExpiration).Unix()
|
||||
robot := claim.Robot{
|
||||
robot := robot_claim.Claim{
|
||||
TokenID: tokenID,
|
||||
ProjectID: projectID,
|
||||
Access: policies,
|
||||
@ -64,7 +64,7 @@ func TestRaw(t *testing.T) {
|
||||
|
||||
tokenExpiration := time.Duration(10) * 24 * time.Hour
|
||||
expiresAt := time.Now().UTC().Add(tokenExpiration).Unix()
|
||||
robot := claim.Robot{
|
||||
robot := robot_claim.Claim{
|
||||
TokenID: tokenID,
|
||||
ProjectID: projectID,
|
||||
Access: policies,
|
||||
@ -82,7 +82,7 @@ func TestRaw(t *testing.T) {
|
||||
|
||||
func TestParseWithClaims(t *testing.T) {
|
||||
rawTk := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MTIzLCJQcm9qZWN0SUQiOjAsIkFjY2VzcyI6W3siUmVzb3VyY2UiOiIvcHJvamVjdC9saWJyYXkvcmVwb3NpdG9yeSIsIkFjdGlvbiI6InB1bGwiLCJFZmZlY3QiOiIifV0sIlN0YW5kYXJkQ2xhaW1zIjp7ImV4cCI6MTU0ODE0MDIyOSwiaXNzIjoiaGFyYm9yLXRva2VuLWlzc3VlciJ9fQ.Jc3qSKN4SJVUzAvBvemVpRcSOZaHlu0Avqms04qzPm4ru9-r9IRIl3mnSkI6m9XkzLUeJ7Kiwyw63ghngnVKw_PupeclOGC6s3TK5Cfmo4h-lflecXjZWwyy-dtH_e7Us_ItS-R3nXDJtzSLEpsGHCcAj-1X2s93RB2qD8LNSylvYeDezVkTzqRzzfawPJheKKh9JTrz-3eUxCwQard9-xjlwvfUYULoHTn9npNAUq4-jqhipW4uE8HL-ym33AGF57la8U0RO11hmDM5K8-PiYknbqJ_oONeS3HBNym2pEFeGjtTv2co213wl4T5lemlg4SGolMBuJ03L7_beVZ0o-MKTkKDqDwJalb6_PM-7u3RbxC9IzJMiwZKIPnD3FvV10iPxUUQHaH8Jz5UZ2pFIhi_8BNnlBfT0JOPFVYATtLjHMczZelj2YvAeR1UHBzq3E0jPpjjwlqIFgaHCaN_KMwEvadTo_Fi2sEH4pNGP7M3yehU_72oLJQgF4paJarsmEoij6ZtPs6xekBz1fccVitq_8WNIz9aeCUdkUBRwI5QKw1RdW4ua-w74ld5MZStWJA8veyoLkEb_Q9eq2oAj5KWFjJbW5-ltiIfM8gxKflsrkWAidYGcEIYcuXr7UdqEKXxtPiWM0xb3B91ovYvO5402bn3f9-UGtlcestxNHA"
|
||||
rClaims := &claim.Robot{}
|
||||
rClaims := &robot_claim.Claim{}
|
||||
_, _ = Parse(DefaultTokenOptions(), rawTk, rClaims)
|
||||
assert.Equal(t, int64(123), rClaims.TokenID)
|
||||
assert.Equal(t, int64(0), rClaims.ProjectID)
|
||||
|
Loading…
Reference in New Issue
Block a user