// Copyright Project Harbor Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package token import ( "context" "fmt" "strings" "time" "github.com/distribution/distribution/v3/registry/auth/token" "github.com/docker/libtrust" "github.com/golang-jwt/jwt/v4" "github.com/goharbor/harbor/src/common/models" "github.com/goharbor/harbor/src/common/security" "github.com/goharbor/harbor/src/common/utils" "github.com/goharbor/harbor/src/controller/project" "github.com/goharbor/harbor/src/lib/config" "github.com/goharbor/harbor/src/lib/log" tokenpkg "github.com/goharbor/harbor/src/pkg/token" v2 "github.com/goharbor/harbor/src/pkg/token/claims/v2" ) const ( signingMethod = "RS256" ) var ( privateKey string ) func init() { privateKey = config.TokenPrivateKeyPath() } // GetResourceActions ... func GetResourceActions(scopes []string) []*token.ResourceActions { log.Debugf("scopes: %+v", scopes) var res []*token.ResourceActions for _, s := range scopes { if s == "" { continue } items := strings.Split(s, ":") length := len(items) typee := "" name := "" actions := make([]string, 0) if length == 1 { typee = items[0] } else if length == 2 { typee = items[0] name = items[1] } else { typee = items[0] name = strings.Join(items[1:length-1], ":") if len(items[length-1]) > 0 { actions = strings.Split(items[length-1], ",") } } res = append(res, &token.ResourceActions{ Type: typee, Name: name, Actions: actions, }) } return res } // filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions. func filterAccess(ctx context.Context, access []*token.ResourceActions, ctl project.Controller, filters map[string]accessFilter) error { secCtx, ok := security.FromContext(ctx) if !ok { return fmt.Errorf("failed to get security context from request") } var err error for _, a := range access { f, ok := filters[a.Type] if !ok { a.Actions = []string{} log.Warningf("No filter found for access type: %s, skip filter, the access of resource '%s' will be set empty.", a.Type, a.Name) continue } err = f.filter(ctx, ctl, a) log.Debugf("user: %s, access: %v", secCtx.GetUsername(), a) if err != nil { log.Errorf("Failed to handle the resource %s:%s, due to error %v, returning empty access for it.", a.Type, a.Name, err) a.Actions = []string{} } } return nil } // MakeToken makes a valid jwt token based on parms. func MakeToken(ctx context.Context, username, service string, access []*token.ResourceActions) (*models.Token, error) { options, err := tokenpkg.NewOptions(signingMethod, v2.Issuer, privateKey) if err != nil { return nil, err } expiration, err := config.TokenExpiration(ctx) if err != nil { return nil, err } now := time.Now().UTC() claims := &v2.Claims{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: options.Issuer, Subject: username, Audience: jwt.ClaimStrings([]string{service}), ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expiration) * time.Minute)), NotBefore: jwt.NewNumericDate(now), IssuedAt: jwt.NewNumericDate(now), ID: utils.GenerateRandomStringWithLen(16), }, Access: access, } tok, err := tokenpkg.New(options, claims) if err != nil { return nil, err } // Add kid to token header for compatibility with docker distribution's code // see https://github.com/distribution/distribution/v3/blob/release/2.7/registry/auth/token/token.go#L197 k, err := libtrust.UnmarshalPrivateKeyPEM(options.PrivateKey) if err != nil { return nil, err } tok.Header["kid"] = k.KeyID() rawToken, err := tok.Raw() if err != nil { return nil, err } return &models.Token{ Token: rawToken, ExpiresIn: expiration * 60, IssuedAt: now.Format(time.RFC3339), }, nil }