diff --git a/src/common/rbac/const.go b/src/common/rbac/const.go index 6b2ff85f2d6dad90fb80ba49c901cb9b31221df5..0e96d4cd8760d04226448ec2306b39e0429d3357 100755 --- a/src/common/rbac/const.go +++ b/src/common/rbac/const.go @@ -28,8 +28,7 @@ const ( ActionDelete = Action("delete") ActionList = Action("list") - ActionOperate = Action("operate") - ActionScannerPull = Action("scanner-pull") // for robot account created by scanner to pull image, bypass the policy check + ActionOperate = Action("operate") ) // const resource variables diff --git a/src/common/security/robot/robot.go b/src/common/security/robot/robot.go index d01dc1055e6fcb3ca86d350503c7a125ebd1bf6c..c900f672d652a38d23988f3d7236b49f4cb62af2 100644 --- a/src/common/security/robot/robot.go +++ b/src/common/security/robot/robot.go @@ -47,22 +47,16 @@ func filterPolicies(namespace rbac.Namespace, policies []*rbac.Policy) []*rbac.P return results } - mp := getAllPolicies(namespace) + mp := map[string]bool{} + for _, policy := range project.GetAllPolicies(namespace) { + mp[policy.String()] = true + } + for _, policy := range policies { if mp[policy.String()] { results = append(results, policy) } } - return results -} -// getAllPolicies gets all of supported policies supported in project and external policies supported for robot account -func getAllPolicies(namespace rbac.Namespace) map[string]bool { - mp := map[string]bool{} - for _, policy := range project.GetAllPolicies(namespace) { - mp[policy.String()] = true - } - scannerPull := &rbac.Policy{Resource: namespace.Resource(rbac.ResourceRepository), Action: rbac.ActionScannerPull} - mp[scannerPull.String()] = true - return mp + return results } diff --git a/src/common/security/robot/robot_test.go b/src/common/security/robot/robot_test.go index 16b57735b7489bb70314412dfaef41c9fa143a6d..617c4c3fac25df67fd6262f6fca386730fc8808f 100644 --- a/src/common/security/robot/robot_test.go +++ b/src/common/security/robot/robot_test.go @@ -44,11 +44,10 @@ func TestGetPolicies(t *testing.T) { func TestNewRobot(t *testing.T) { policies := []*rbac.Policy{ {Resource: "/project/1/repository", Action: "pull"}, - {Resource: "/project/1/repository", Action: "scanner-pull"}, {Resource: "/project/library/repository", Action: "pull"}, {Resource: "/project/library/repository", Action: "push"}, } robot := NewRobot("test", rbac.NewProjectNamespace(1, false), policies) - assert.Len(t, robot.GetPolicies(), 2) + assert.Len(t, robot.GetPolicies(), 1) } diff --git a/src/pkg/token/claims/robot/robot.go b/src/common/token/claims.go similarity index 82% rename from src/pkg/token/claims/robot/robot.go rename to src/common/token/claims.go index 44ffa2633112f543ef92d183e7ef855048fb0be1..bec9f5f2f7f15f4ae2ae3126551baf3876aa56d5 100644 --- a/src/pkg/token/claims/robot/robot.go +++ b/src/common/token/claims.go @@ -1,4 +1,4 @@ -package robot +package token import ( "errors" @@ -6,8 +6,8 @@ import ( "github.com/goharbor/harbor/src/common/rbac" ) -// Claim implements the interface of jwt.Claims -type Claim struct { +// RobotClaims implements the interface of jwt.Claims +type RobotClaims struct { jwt.StandardClaims TokenID int64 `json:"id"` ProjectID int64 `json:"pid"` @@ -15,7 +15,7 @@ type Claim struct { } // Valid valid the claims "tokenID, projectID and access". -func (rc Claim) Valid() error { +func (rc RobotClaims) Valid() error { if rc.TokenID < 0 { return errors.New("Token id must an valid INT") } diff --git a/src/pkg/token/claims/robot/robot_test.go b/src/common/token/claims_test.go similarity index 90% rename from src/pkg/token/claims/robot/robot_test.go rename to src/common/token/claims_test.go index 054f4b4a84c3da97a1d6437d0e9c717f73934a56..dc25a120a1f60eb8eacc69737523a3cc016abddb 100644 --- a/src/pkg/token/claims/robot/robot_test.go +++ b/src/common/token/claims_test.go @@ -1,4 +1,4 @@ -package robot +package token 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 := &Claim{ + rClaims := &RobotClaims{ TokenID: 1, ProjectID: 2, Access: policies, @@ -32,7 +32,7 @@ func TestUnValidTokenID(t *testing.T) { policies := []*rbac.Policy{} policies = append(policies, rbacPolicy) - rClaims := &Claim{ + rClaims := &RobotClaims{ TokenID: -1, ProjectID: 2, Access: policies, @@ -49,7 +49,7 @@ func TestUnValidProjectID(t *testing.T) { policies := []*rbac.Policy{} policies = append(policies, rbacPolicy) - rClaims := &Claim{ + rClaims := &RobotClaims{ TokenID: 1, ProjectID: -2, Access: policies, @@ -59,7 +59,7 @@ func TestUnValidProjectID(t *testing.T) { func TestUnValidPolicy(t *testing.T) { - rClaims := &Claim{ + rClaims := &RobotClaims{ TokenID: 1, ProjectID: 2, Access: nil, diff --git a/src/common/token/htoken.go b/src/common/token/htoken.go new file mode 100644 index 0000000000000000000000000000000000000000..897c504672a82021f5ce262f665855fcb8eb0af2 --- /dev/null +++ b/src/common/token/htoken.go @@ -0,0 +1,87 @@ +package token + +import ( + "crypto/ecdsa" + "crypto/rsa" + "errors" + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/goharbor/harbor/src/common/rbac" + "github.com/goharbor/harbor/src/common/utils/log" + "time" +) + +// HToken htoken is a jwt token for harbor robot account, +// which contains the robot ID, project ID and the access permission for the project. +// It used for authn/authz for robot account in Harbor. +type HToken struct { + jwt.Token +} + +// New ... +func New(tokenID, projectID, expiresAt int64, access []*rbac.Policy) (*HToken, error) { + rClaims := &RobotClaims{ + TokenID: tokenID, + ProjectID: projectID, + Access: access, + StandardClaims: jwt.StandardClaims{ + IssuedAt: time.Now().UTC().Unix(), + ExpiresAt: expiresAt, + Issuer: DefaultOptions().Issuer, + }, + } + err := rClaims.Valid() + if err != nil { + return nil, err + } + return &HToken{ + Token: *jwt.NewWithClaims(DefaultOptions().SignMethod, rClaims), + }, nil +} + +// Raw get the Raw string of token +func (htk *HToken) Raw() (string, error) { + key, err := DefaultOptions().GetKey() + if err != nil { + return "", nil + } + raw, err := htk.Token.SignedString(key) + if err != nil { + log.Debugf(fmt.Sprintf("failed to issue token %v", err)) + return "", err + } + return raw, err +} + +// ParseWithClaims ... +func ParseWithClaims(rawToken string, claims jwt.Claims) (*HToken, error) { + key, err := DefaultOptions().GetKey() + if err != nil { + return nil, err + } + token, err := jwt.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) { + if token.Method.Alg() != DefaultOptions().SignMethod.Alg() { + return nil, errors.New("invalid signing method") + } + switch k := key.(type) { + case *rsa.PrivateKey: + return &k.PublicKey, nil + case *ecdsa.PrivateKey: + return &k.PublicKey, nil + default: + return key, nil + } + }) + if err != nil { + log.Errorf(fmt.Sprintf("parse token error, %v", err)) + return nil, err + } + + if !token.Valid { + log.Errorf(fmt.Sprintf("invalid jwt token, %v", token)) + return nil, errors.New("invalid jwt token") + } + return &HToken{ + Token: *token, + }, nil +} diff --git a/src/pkg/token/token_test.go b/src/common/token/htoken_test.go similarity index 79% rename from src/pkg/token/token_test.go rename to src/common/token/htoken_test.go index eab75a4743be660ff2e04b751abab0cee3e37e04..38e187ef74967ab1dd0776678ba86bfef69dd891 100644 --- a/src/pkg/token/token_test.go +++ b/src/common/token/htoken_test.go @@ -5,10 +5,8 @@ import ( "testing" "time" - "github.com/dgrijalva/jwt-go" "github.com/goharbor/harbor/src/common/rbac" "github.com/goharbor/harbor/src/core/config" - robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot" "github.com/stretchr/testify/assert" ) @@ -35,15 +33,7 @@ func TestNew(t *testing.T) { projectID := int64(321) tokenExpiration := time.Duration(10) * 24 * time.Hour expiresAt := time.Now().UTC().Add(tokenExpiration).Unix() - robot := robot_claim.Claim{ - TokenID: tokenID, - ProjectID: projectID, - Access: policies, - StandardClaims: jwt.StandardClaims{ - ExpiresAt: expiresAt, - }, - } - token, err := New(DefaultTokenOptions(), robot) + token, err := New(tokenID, projectID, expiresAt, policies) assert.Nil(t, err) assert.Equal(t, token.Header["alg"], "RS256") @@ -64,15 +54,7 @@ func TestRaw(t *testing.T) { tokenExpiration := time.Duration(10) * 24 * time.Hour expiresAt := time.Now().UTC().Add(tokenExpiration).Unix() - robot := robot_claim.Claim{ - TokenID: tokenID, - ProjectID: projectID, - Access: policies, - StandardClaims: jwt.StandardClaims{ - ExpiresAt: expiresAt, - }, - } - token, err := New(DefaultTokenOptions(), robot) + token, err := New(tokenID, projectID, expiresAt, policies) assert.Nil(t, err) rawTk, err := token.Raw() @@ -82,8 +64,8 @@ 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 := &robot_claim.Claim{} - _, _ = Parse(DefaultTokenOptions(), rawTk, rClaims) + rClaims := &RobotClaims{} + _, _ = ParseWithClaims(rawTk, rClaims) assert.Equal(t, int64(123), rClaims.TokenID) assert.Equal(t, int64(0), rClaims.ProjectID) assert.Equal(t, "/project/libray/repository", rClaims.Access[0].Resource.String()) diff --git a/src/pkg/token/options.go b/src/common/token/options.go similarity index 83% rename from src/pkg/token/options.go rename to src/common/token/options.go index e1d96d47642942f30a573349b85446cfc9001160..747d7435d57694cd95bcbd07c3f4c7d8717f797e 100644 --- a/src/pkg/token/options.go +++ b/src/common/token/options.go @@ -11,9 +11,9 @@ import ( ) const ( - defaultTTL = 60 * time.Minute - defaultIssuer = "harbor-token-defaultIssuer" - defaultSignedMethod = "RS256" + ttl = 60 * time.Minute + issuer = "harbor-token-issuer" + signedMethod = "RS256" ) // Options ... @@ -25,6 +25,23 @@ type Options struct { Issuer string } +// DefaultOptions ... +func DefaultOptions() *Options { + privateKeyFile := config.TokenPrivateKeyPath() + privateKey, err := ioutil.ReadFile(privateKeyFile) + if err != nil { + log.Errorf(fmt.Sprintf("failed to read private key %v", err)) + return nil + } + opt := &Options{ + SignMethod: jwt.GetSigningMethod(signedMethod), + PrivateKey: privateKey, + Issuer: issuer, + TTL: ttl, + } + return opt +} + // GetKey ... func (o *Options) GetKey() (interface{}, error) { var err error @@ -59,20 +76,3 @@ func (o *Options) GetKey() (interface{}, error) { return nil, fmt.Errorf(fmt.Sprintf("unsupported sign method, %s", o.SignMethod)) } } - -// DefaultTokenOptions ... -func DefaultTokenOptions() *Options { - privateKeyFile := config.TokenPrivateKeyPath() - privateKey, err := ioutil.ReadFile(privateKeyFile) - if err != nil { - log.Errorf(fmt.Sprintf("failed to read private key %v", err)) - return nil - } - opt := &Options{ - SignMethod: jwt.GetSigningMethod(defaultSignedMethod), - PrivateKey: privateKey, - Issuer: defaultIssuer, - TTL: defaultTTL, - } - return opt -} diff --git a/src/pkg/token/option_test.go b/src/common/token/options_test.go similarity index 74% rename from src/pkg/token/option_test.go rename to src/common/token/options_test.go index 421bf0cbbfb677532e39625875b4856d1503d709..5f64fb3802b9766ce3cb7c6f4b8df56cbdd444fc 100644 --- a/src/pkg/token/option_test.go +++ b/src/common/token/options_test.go @@ -8,15 +8,15 @@ import ( ) func TestNewOptions(t *testing.T) { - defaultOpt := DefaultTokenOptions() + defaultOpt := DefaultOptions() assert.NotNil(t, defaultOpt) assert.Equal(t, defaultOpt.SignMethod, jwt.GetSigningMethod("RS256")) - assert.Equal(t, defaultOpt.Issuer, "harbor-token-defaultIssuer") + assert.Equal(t, defaultOpt.Issuer, "harbor-token-issuer") assert.Equal(t, defaultOpt.TTL, 60*time.Minute) } func TestGetKey(t *testing.T) { - defaultOpt := DefaultTokenOptions() + defaultOpt := DefaultOptions() key, err := defaultOpt.GetKey() assert.Nil(t, err) assert.NotNil(t, key) diff --git a/src/common/utils/oidc/helper.go b/src/common/utils/oidc/helper.go index 7cbec4aa96a932f08d0a421317f4b734f746822c..7de72a8b87405fba8304bb6f908e9f93427800c2 100644 --- a/src/common/utils/oidc/helper.go +++ b/src/common/utils/oidc/helper.go @@ -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 claims of the group list is set in OIDCSetting. +// GroupsFromToken returns the list of group name in the token, the claim 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 claims is not set in OIDC setting returning empty group list.") + log.Warning("Group claim 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 claims key: %s", claimMap, k) + log.Warningf("Unable to get groups from claims, claims: %+v, groups claim key: %s", claimMap, k) return res } for _, e := range g { diff --git a/src/core/filter/security.go b/src/core/filter/security.go index 891fcb5e465c4fceb03f7c186a7d15442eeb2062..b8bbbac5b08d933bb14ce99e7acb292292c1d2fe 100644 --- a/src/core/filter/security.go +++ b/src/core/filter/security.go @@ -34,6 +34,7 @@ import ( "github.com/goharbor/harbor/src/common/security/local" robotCtx "github.com/goharbor/harbor/src/common/security/robot" "github.com/goharbor/harbor/src/common/security/secret" + "github.com/goharbor/harbor/src/common/token" "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/core/auth" "github.com/goharbor/harbor/src/core/config" @@ -43,8 +44,6 @@ import ( "github.com/goharbor/harbor/src/pkg/authproxy" "github.com/goharbor/harbor/src/pkg/robot" - pkg_token "github.com/goharbor/harbor/src/pkg/token" - robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot" ) // ContextValueKey for content value @@ -189,16 +188,15 @@ func (r *robotAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool { if !strings.HasPrefix(robotName, common.RobotPrefix) { return false } - rClaims := &robot_claim.Claim{} - opt := pkg_token.DefaultTokenOptions() - rtk, err := pkg_token.Parse(opt, robotTk, rClaims) + rClaims := &token.RobotClaims{} + htk, err := token.ParseWithClaims(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.(*robot_claim.Claim).TokenID) + robot, err := ctr.GetRobotAccount(htk.Claims.(*token.RobotClaims).TokenID) if err != nil { log.Errorf("failed to get robot %s: %v", robotName, err) return false @@ -217,7 +215,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.(*robot_claim.Claim).Access) + securCtx := robotCtx.NewSecurityContext(robot, pm, htk.Claims.(*token.RobotClaims).Access) setSecurCtxAndPM(ctx.Request, securCtx, pm) return true } diff --git a/src/core/middlewares/chain.go b/src/core/middlewares/chain.go index 630a8e8d45549c0314a5b2f7edb316723109afde..79617c73568972fd3f2c50a361810fd6cdbf0a4f 100644 --- a/src/core/middlewares/chain.go +++ b/src/core/middlewares/chain.go @@ -25,7 +25,6 @@ import ( "github.com/goharbor/harbor/src/core/middlewares/listrepo" "github.com/goharbor/harbor/src/core/middlewares/multiplmanifest" "github.com/goharbor/harbor/src/core/middlewares/readonly" - "github.com/goharbor/harbor/src/core/middlewares/regtoken" "github.com/goharbor/harbor/src/core/middlewares/sizequota" "github.com/goharbor/harbor/src/core/middlewares/url" "github.com/goharbor/harbor/src/core/middlewares/vulnerable" @@ -73,7 +72,6 @@ func (b *DefaultCreator) geMiddleware(mName string) alice.Constructor { SIZEQUOTA: func(next http.Handler) http.Handler { return sizequota.New(next) }, COUNTQUOTA: func(next http.Handler) http.Handler { return countquota.New(next) }, IMMUTABLE: func(next http.Handler) http.Handler { return immutable.New(next) }, - REGTOKEN: func(next http.Handler) http.Handler { return regtoken.New(next) }, } return middlewares[mName] } diff --git a/src/core/middlewares/config.go b/src/core/middlewares/config.go index f8147b49dbbc9f01a32a07618f69fe5beab80dba..fa2b536f5bf4ec20ed046d61168b34d157927bc4 100644 --- a/src/core/middlewares/config.go +++ b/src/core/middlewares/config.go @@ -26,14 +26,13 @@ const ( SIZEQUOTA = "sizequota" COUNTQUOTA = "countquota" IMMUTABLE = "immutable" - REGTOKEN = "regtoken" ) // ChartMiddlewares middlewares for chart server var ChartMiddlewares = []string{CHART} // Middlewares with sequential organization -var Middlewares = []string{READONLY, URL, REGTOKEN, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA} +var Middlewares = []string{READONLY, URL, MUITIPLEMANIFEST, LISTREPO, CONTENTTRUST, VULNERABLE, SIZEQUOTA, IMMUTABLE, COUNTQUOTA} // MiddlewaresLocal ... var MiddlewaresLocal = []string{SIZEQUOTA, IMMUTABLE, COUNTQUOTA} diff --git a/src/core/middlewares/contenttrust/handler.go b/src/core/middlewares/contenttrust/handler.go index 049a2a88b673d554633bd6e27510ff291edd8855..bcc4de44a736501488dc8fad26fcfed24ac7a292 100644 --- a/src/core/middlewares/contenttrust/handler.go +++ b/src/core/middlewares/contenttrust/handler.go @@ -49,10 +49,6 @@ func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Reque cth.next.ServeHTTP(rw, req) return } - if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull { - cth.next.ServeHTTP(rw, req) - return - } if !util.GetPolicyChecker().ContentTrustEnabled(img.ProjectName) { cth.next.ServeHTTP(rw, req) return diff --git a/src/core/middlewares/regtoken/handler.go b/src/core/middlewares/regtoken/handler.go deleted file mode 100644 index d1f561e123666896b3c0b2601a4e0d042c1cb179..0000000000000000000000000000000000000000 --- a/src/core/middlewares/regtoken/handler.go +++ /dev/null @@ -1,71 +0,0 @@ -package regtoken - -import ( - "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/middlewares/util" - pkg_token "github.com/goharbor/harbor/src/pkg/token" - "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 -} - -// New ... -func New(next http.Handler) http.Handler { - return ®TokenHandler{ - next: next, - } -} - -// ServeHTTP ... -func (r *regTokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - imgRaw := req.Context().Value(util.ImageInfoCtxKey) - if imgRaw == nil { - r.next.ServeHTTP(rw, req) - return - } - img, _ := req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo) - if img.Digest == "" { - r.next.ServeHTTP(rw, req) - return - } - - parts := strings.Split(req.Header.Get("Authorization"), " ") - if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { - r.next.ServeHTTP(rw, req) - return - } - rawToken := parts[1] - opt := pkg_token.DefaultTokenOptions() - regTK, err := pkg_token.Parse(opt, rawToken, ®istry.Claim{}) - if err != nil { - log.Errorf("failed to decode reg token: %v, the error is skipped and round the request to native registry.", err) - r.next.ServeHTTP(rw, req) - return - } - - accessItems := []auth.Access{} - accessItems = append(accessItems, auth.Access{ - Resource: auth.Resource{ - Type: rbac.ResourceRepository.String(), - Name: img.Repository, - }, - Action: rbac.ActionScannerPull.String(), - }) - - accessSet := regTK.Claims.(*registry.Claim).GetAccess() - for _, access := range accessItems { - if accessSet.Contains(access) { - *req = *(req.WithContext(util.NewScannerPullContext(req.Context(), true))) - } - } - r.next.ServeHTTP(rw, req) -} diff --git a/src/core/middlewares/regtoken/handler_test.go b/src/core/middlewares/regtoken/handler_test.go deleted file mode 100644 index 5736781a2f46d75d1c89cf10ed73833246863490..0000000000000000000000000000000000000000 --- a/src/core/middlewares/regtoken/handler_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package regtoken - -import ( - "fmt" - "github.com/goharbor/harbor/src/core/middlewares/util" - "github.com/stretchr/testify/suite" - "net/http" - "net/http/httptest" - "os" - "testing" -) - -type HandlerSuite struct { - suite.Suite -} - -func doPullManifestRequest(projectName, name, tag string, next ...http.HandlerFunc) int { - repository := fmt.Sprintf("%s/%s", projectName, name) - - url := fmt.Sprintf("/v2/%s/manifests/%s", repository, tag) - req, _ := http.NewRequest("GET", url, nil) - - token := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkNWUTc6REM3NTpHVEROOkxTTUs6VUFJTjpIUUVWOlZVSDQ6Q0lRRDpRV01COlM0Qzc6U0c0STpGRUhYIn0.eyJpc3MiOiJoYXJib3ItdG9rZW4taXNzdWVyIiwic3ViIjoicm9ib3QkZGVtbzExIiwiYXVkIjoiaGFyYm9yLXJlZ2lzdHJ5IiwiZXhwIjoxNTcxNzYzOTI2LCJuYmYiOjE1NzE3NjM4NjYsImlhdCI6MTU3MTc2Mzg2NiwianRpIjoiTnRaZWx4Z01KTUU1MXlEMCIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoibGlicmFyeS9oZWxsby13b3JsZCIsImFjdGlvbnMiOlsicHVzaCIsIioiLCJwdWxsIiwic2Nhbm5lcnB1bGwiXX1dfQ.GlWuvtoxmChnpvbWaG5901Z9-g63DrzyNUREWlDbR5gnNeuOKjLNyE4QpogAQKx2yYtcGxbqNL3VfJkExJ_gMS0Qw8e10utGOawwqD4oqf_J06eKq4HzpZJengZfcjMA4g2RoeOlqdVdwimB_PdX9vkBO1od0wX0Cc2v0p2w5TkibcThKRoeLeVs2oRewkKLuVHNSM8wwRIlAvpWJuNnvRCFlHRkLcZM_KpGXqT7H-PZETTisWCi1pMxeYEwIsDFLlTKdV8LaiDeDmH-RaLOsuyAySYEW9Ynk5K3P_dUl2c_SYQXloPyi0MvXxSn6EWE4eHF2oQDM_SvIzR9sOVB8TtjMjKKMQ4yr_mqgMcfEpnInJATExBR56wmxNdLESncHl8rUYCe2jCjQFuR9NGQA1tGdjI4NoBN-OVD0dBs9rm_mkb2tgD-3gEhyzAw6hg0uzDsF7bj5Aq8scoi42UurhX2bZM89s4-TWBp4DWuBG0HDiwpOiBvB3RMm6MpQxsqrl0hQm_WH18L6QCknAW2e3d_6DJWJ0eBzISrhDr7LkqJKl1J8pv4zqoh_EUVeLyzTmjEULm-VbnpVF4wW5yTLF3S6F7Ox4vwWtVfi1XQNVOcJDB3VPUsRgiTTuCW-ZGcBLw-OdIcwaJ3T_QZkEjUw1f6i1JcGa0Mpgl83aLiSdQ 0xc0003c77c0 map[alg:RS256 kid:CVQ7:DC75:GTDN:LSMK:UAIN:HQEV:VUH4:CIQD:QWMB:S4C7:SG4I:FEHX typ:JWT] 0xc000496000 GlWuvtoxmChnpvbWaG5901Z9-g63DrzyNUREWlDbR5gnNeuOKjLNyE4QpogAQKx2yYtcGxbqNL3VfJkExJ_gMS0Qw8e10utGOawwqD4oqf_J06eKq4HzpZJengZfcjMA4g2RoeOlqdVdwimB_PdX9vkBO1od0wX0Cc2v0p2w5TkibcThKRoeLeVs2oRewkKLuVHNSM8wwRIlAvpWJuNnvRCFlHRkLcZM_KpGXqT7H-PZETTisWCi1pMxeYEwIsDFLlTKdV8LaiDeDmH-RaLOsuyAySYEW9Ynk5K3P_dUl2c_SYQXloPyi0MvXxSn6EWE4eHF2oQDM_SvIzR9sOVB8TtjMjKKMQ4yr_mqgMcfEpnInJATExBR56wmxNdLESncHl8rUYCe2jCjQFuR9NGQA1tGdjI4NoBN-OVD0dBs9rm_mkb2tgD-3gEhyzAw6hg0uzDsF7bj5Aq8scoi42UurhX2bZM89s4-TWBp4DWuBG0HDiwpOiBvB3RMm6MpQxsqrl0hQm_WH18L6QCknAW2e3d_6DJWJ0eBzISrhDr7LkqJKl1J8pv4zqoh_EUVeLyzTmjEULm-VbnpVF4wW5yTLF3S6F7Ox4vwWtVfi1XQNVOcJDB3VPUsRgiTTuCW-ZGcBLw-OdIcwaJ3T_QZkEjUw1f6i1JcGa0Mpgl83aLiSdQ" - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - rr := httptest.NewRecorder() - - var n http.HandlerFunc - if len(next) > 0 { - n = next[0] - } else { - n = func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusNotFound) - } - } - - h := New(http.HandlerFunc(n)) - h.ServeHTTP(util.NewCustomResponseWriter(rr), req) - - return rr.Code -} - -func (suite *HandlerSuite) TestPullManifest() { - code1 := doPullManifestRequest("library", "photon", "release-1.10") - suite.Equal(http.StatusNotFound, code1) -} - -func TestMain(m *testing.M) { - if result := m.Run(); result != 0 { - os.Exit(result) - } -} - -func TestRunHandlerSuite(t *testing.T) { - suite.Run(t, new(HandlerSuite)) -} diff --git a/src/core/middlewares/util/util.go b/src/core/middlewares/util/util.go index e023fc8021082fbc93c3a8fdc7bcde0f87f15c51..9c05b7599ff9f0efaa12ade6de8100ef620afb3e 100644 --- a/src/core/middlewares/util/util.go +++ b/src/core/middlewares/util/util.go @@ -49,8 +49,6 @@ type contextKey string const ( // ImageInfoCtxKey the context key for image information ImageInfoCtxKey = contextKey("ImageInfo") - // ScannerPullCtxKey the context key for robot account to bypass the pull policy check. - ScannerPullCtxKey = contextKey("ScannerPullCheck") // TokenUsername ... // TODO: temp solution, remove after vmware/harbor#2242 is resolved. TokenUsername = "harbor-core" @@ -445,17 +443,6 @@ func ManifestInfoFromContext(ctx context.Context) (*ManifestInfo, bool) { return info, ok } -// NewScannerPullContext returns context with policy check info -func NewScannerPullContext(ctx context.Context, scannerPull bool) context.Context { - return context.WithValue(ctx, ScannerPullCtxKey, scannerPull) -} - -// ScannerPullFromContext returns whether to bypass policy check -func ScannerPullFromContext(ctx context.Context) (bool, bool) { - info, ok := ctx.Value(ScannerPullCtxKey).(bool) - return info, ok -} - // NewBlobInfoContext returns context with blob info func NewBlobInfoContext(ctx context.Context, info *BlobInfo) context.Context { return context.WithValue(ctx, blobInfoKey, info) diff --git a/src/core/middlewares/vulnerable/handler.go b/src/core/middlewares/vulnerable/handler.go index 108d683f4b32421522b0586b270fd25d1c0bcae3..a1ba6bda98cacd687de807403461492baffdb3d8 100644 --- a/src/core/middlewares/vulnerable/handler.go +++ b/src/core/middlewares/vulnerable/handler.go @@ -52,11 +52,6 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) return } - if scannerPull, ok := util.ScannerPullFromContext(req.Context()); ok && scannerPull { - vh.next.ServeHTTP(rw, req) - return - } - // Is vulnerable policy set? projectVulnerableEnabled, projectVulnerableSeverity, wl := util.GetPolicyChecker().VulnerablePolicy(img.ProjectName) if !projectVulnerableEnabled { @@ -114,10 +109,10 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) return } - // Print scannerPull CVE list + // Print bypass CVE list if len(summary.CVEBypassed) > 0 { for _, cve := range summary.CVEBypassed { - log.Infof("Vulnerable policy check: scannerPull CVE %s", cve) + log.Infof("Vulnerable policy check: bypass CVE %s", cve) } } diff --git a/src/core/service/token/authutils.go b/src/core/service/token/authutils.go index d04fe264d0cc35c8cca2280abdc209dd5cbd35cd..8de8a50ca50d105b1b9759d09cb8060dd7932335 100644 --- a/src/core/service/token/authutils.go +++ b/src/core/service/token/authutils.go @@ -133,9 +133,6 @@ func permToActions(p string) []string { 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 ad002e51132897027f9dc95dcdc8525e8a9df291..feca191e6e20c5b36a7930e4bf6cf26b2f12e9d1 100644 --- a/src/core/service/token/creator.go +++ b/src/core/service/token/creator.go @@ -177,8 +177,6 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManage permission = "RWM" } else if ctx.Can(rbac.ActionPush, resource) { permission = "RW" - } else if ctx.Can(rbac.ActionScannerPull, resource) { - permission = "RS" } else if ctx.Can(rbac.ActionPull, resource) { permission = "R" } diff --git a/src/pkg/robot/controller.go b/src/pkg/robot/controller.go index 1d5acc170c2561060390e64f925a1e3b2f7d3244..5a65657116feaba019a223ae4d4854f0fe176e5b 100644 --- a/src/pkg/robot/controller.go +++ b/src/pkg/robot/controller.go @@ -2,14 +2,12 @@ package robot import ( "fmt" - "github.com/dgrijalva/jwt-go" "github.com/goharbor/harbor/src/common" + "github.com/goharbor/harbor/src/common/token" "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" - robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot" "github.com/pkg/errors" "time" ) @@ -78,23 +76,13 @@ 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 := &robot_claim.Claim{ - TokenID: id, - ProjectID: robotReq.ProjectID, - Access: robotReq.Access, - StandardClaims: jwt.StandardClaims{ - IssuedAt: time.Now().UTC().Unix(), - ExpiresAt: expiresAt, - Issuer: opt.Issuer, - }, - } - tk, err := token.New(opt, rClaims) + jwtToken, err := token.New(id, robotReq.ProjectID, expiresAt, robotReq.Access) if err != nil { deferDel = err return nil, fmt.Errorf("failed to valid parameters to generate token for robot account, %v", err) } - rawTk, err := tk.Raw() + + rawTk, err := jwtToken.Raw() if err != nil { deferDel = err return nil, fmt.Errorf("failed to sign token for robot account, %v", err) diff --git a/src/pkg/token/claims/registry/accesses.go b/src/pkg/token/claims/registry/accesses.go deleted file mode 100644 index 3665a415d5e00adf76cd0bdf9ff858028b9b97a2..0000000000000000000000000000000000000000 --- a/src/pkg/token/claims/registry/accesses.go +++ /dev/null @@ -1,49 +0,0 @@ -package registry - -import ( - "github.com/docker/distribution/registry/auth" -) - -// Accesses ... -type Accesses map[auth.Resource]actions - -// Contains ... -func (s Accesses) Contains(access auth.Access) bool { - actionSet, ok := s[access.Resource] - if ok { - return actionSet.contains(access.Action) - } - - return false -} - -type actions struct { - stringSet -} - -func newActions(set ...string) actions { - return actions{newStringSet(set...)} -} - -func (s actions) contains(action string) bool { - return s.stringSet.contains(action) -} - -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 -} diff --git a/src/pkg/token/claims/registry/registry.go b/src/pkg/token/claims/registry/registry.go deleted file mode 100644 index 07d18ce58debb8be832ea642cc98796db7c2ad0f..0000000000000000000000000000000000000000 --- a/src/pkg/token/claims/registry/registry.go +++ /dev/null @@ -1,38 +0,0 @@ -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 { - return rc.StandardClaims.Valid() -} - -// GetAccess ... -func (rc *Claim) GetAccess() Accesses { - accesses := make(Accesses, len(rc.Access)) - for _, resourceActions := range rc.Access { - resource := auth.Resource{ - Type: resourceActions.Type, - Name: resourceActions.Name, - } - set, exists := accesses[resource] - if !exists { - set = newActions() - accesses[resource] = set - } - for _, action := range resourceActions.Actions { - set.add(action) - } - } - return accesses -} diff --git a/src/pkg/token/claims/registry/registry_test.go b/src/pkg/token/claims/registry/registry_test.go deleted file mode 100644 index 600a07edd7b50b5aa5ce13817176c80ef7284004..0000000000000000000000000000000000000000 --- a/src/pkg/token/claims/registry/registry_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package registry - -import ( - "github.com/docker/distribution/registry/auth" - "github.com/docker/distribution/registry/auth/token" - "github.com/goharbor/harbor/src/common/rbac" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestValid(t *testing.T) { - access := &token.ResourceActions{ - Type: "type", - Name: "repository", - Actions: []string{"pull", "push"}, - } - accesses := []*token.ResourceActions{} - accesses = append(accesses, access) - rClaims := &Claim{ - Access: accesses, - } - assert.Nil(t, rClaims.Valid()) -} - -func TestGetAccessSet(t *testing.T) { - access := &token.ResourceActions{ - Type: "repository", - Name: "hello-world", - Actions: []string{"pull", "push", "scanner-pull"}, - } - accesses := []*token.ResourceActions{} - accesses = append(accesses, access) - rClaims := &Claim{ - Access: accesses, - } - - auth1 := auth.Access{ - Resource: auth.Resource{ - Type: "repository", - Name: "hello-world", - }, - Action: rbac.ActionScannerPull.String(), - } - auth2 := auth.Access{ - Resource: auth.Resource{ - Type: "repository", - Name: "busubox", - }, - Action: rbac.ActionScannerPull.String(), - } - set := rClaims.GetAccess() - assert.True(t, set.Contains(auth1)) - assert.False(t, set.Contains(auth2)) -} diff --git a/src/pkg/token/token.go b/src/pkg/token/token.go deleted file mode 100644 index 62e1e3fec76203cd8bcc3b4746a666fc762ab7fc..0000000000000000000000000000000000000000 --- a/src/pkg/token/token.go +++ /dev/null @@ -1,77 +0,0 @@ -package token - -import ( - "crypto/ecdsa" - "crypto/rsa" - "errors" - "fmt" - "github.com/dgrijalva/jwt-go" - "github.com/goharbor/harbor/src/common/utils/log" -) - -// Token is a jwt token for harbor robot account, -type Token struct { - jwt.Token - Opt *Options - Claim jwt.Claims -} - -// New ... -func New(opt *Options, claims jwt.Claims) (*Token, error) { - err := claims.Valid() - if err != nil { - return nil, err - } - return &Token{ - Token: *jwt.NewWithClaims(opt.SignMethod, claims), - Opt: opt, - Claim: claims, - }, nil -} - -// Raw get the Raw string of token -func (tk *Token) Raw() (string, error) { - key, err := tk.Opt.GetKey() - if err != nil { - return "", nil - } - raw, err := tk.Token.SignedString(key) - if err != nil { - log.Debugf(fmt.Sprintf("failed to issue token %v", err)) - return "", err - } - return raw, err -} - -// Parse ... -func Parse(opt *Options, rawToken string, claims jwt.Claims) (*Token, error) { - key, err := opt.GetKey() - if err != nil { - return nil, err - } - token, err := jwt.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (interface{}, error) { - if token.Method.Alg() != opt.SignMethod.Alg() { - return nil, errors.New("invalid signing method") - } - switch k := key.(type) { - case *rsa.PrivateKey: - return &k.PublicKey, nil - case *ecdsa.PrivateKey: - return &k.PublicKey, nil - default: - return key, nil - } - }) - if err != nil { - log.Errorf(fmt.Sprintf("parse token error, %v", err)) - return nil, err - } - - if !token.Valid { - log.Errorf(fmt.Sprintf("invalid jwt token, %v", token)) - return nil, errors.New("invalid jwt token") - } - return &Token{ - Token: *token, - }, nil -}