mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-22 14:52:17 +01:00
Merge pull request #2558 from ywk253100/170618_auth
Add security context based on admiral
This commit is contained in:
commit
d0eec5bb0d
@ -107,6 +107,10 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
|
||||
// in projectIDs
|
||||
func GetTopRepos(projectIDs []int64, n int) ([]*models.RepoRecord, error) {
|
||||
repositories := []*models.RepoRecord{}
|
||||
if len(projectIDs) == 0 {
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
_, err := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||
Filter("project_id__in", projectIDs).
|
||||
OrderBy("-pull_count").
|
||||
|
174
src/common/security/admiral/context.go
Normal file
174
src/common/security/admiral/context.go
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 admiral
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/security/authcontext"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
)
|
||||
|
||||
// SecurityContext implements security.Context interface based on
|
||||
// auth context and project manager
|
||||
type SecurityContext struct {
|
||||
ctx *authcontext.AuthContext
|
||||
pm projectmanager.ProjectManager
|
||||
}
|
||||
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(ctx *authcontext.AuthContext, pm projectmanager.ProjectManager) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
ctx: ctx,
|
||||
pm: pm,
|
||||
}
|
||||
}
|
||||
|
||||
// IsAuthenticated returns true if the user has been authenticated
|
||||
func (s *SecurityContext) IsAuthenticated() bool {
|
||||
if s.ctx == nil {
|
||||
return false
|
||||
}
|
||||
return len(s.ctx.GetUsername()) > 0
|
||||
}
|
||||
|
||||
// GetUsername returns the username of the authenticated user
|
||||
// It returns null if the user has not been authenticated
|
||||
func (s *SecurityContext) GetUsername() string {
|
||||
if !s.IsAuthenticated() {
|
||||
return ""
|
||||
}
|
||||
return s.ctx.GetUsername()
|
||||
}
|
||||
|
||||
// IsSysAdmin returns whether the authenticated user is system admin
|
||||
// It returns false if the user has not been authenticated
|
||||
func (s *SecurityContext) IsSysAdmin() bool {
|
||||
if !s.IsAuthenticated() {
|
||||
return false
|
||||
}
|
||||
return s.ctx.IsSysAdmin()
|
||||
}
|
||||
|
||||
// HasReadPerm returns whether the user has read permission to the project
|
||||
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||
// public project
|
||||
public, err := s.pm.IsPublic(projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the public of project %v: %v",
|
||||
projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
if public {
|
||||
return true
|
||||
}
|
||||
|
||||
// private project
|
||||
if !s.IsAuthenticated() {
|
||||
return false
|
||||
}
|
||||
|
||||
// system admin
|
||||
if s.IsSysAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
if name, ok := projectIDOrName.(string); ok {
|
||||
return s.ctx.HasReadPerm(name)
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasWritePerm returns whether the user has write permission to the project
|
||||
func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||
if !s.IsAuthenticated() {
|
||||
return false
|
||||
}
|
||||
|
||||
// system admin
|
||||
if s.IsSysAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
if name, ok := projectIDOrName.(string); ok {
|
||||
return s.ctx.HasWritePerm(name)
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleDeveloper:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// HasAllPerm returns whether the user has all permissions to the project
|
||||
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
if !s.IsAuthenticated() {
|
||||
return false
|
||||
}
|
||||
|
||||
// system admin
|
||||
if s.IsSysAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
if name, ok := projectIDOrName.(string); ok {
|
||||
return s.ctx.HasAllPerm(name)
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
17
src/common/security/admiral/context_test.go
Normal file
17
src/common/security/admiral/context_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 admiral
|
||||
|
||||
// TODO add test cases
|
129
src/common/security/authcontext/authcontext.go
Normal file
129
src/common/security/authcontext/authcontext.go
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 authcontext
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
const (
|
||||
// AuthTokenHeader is the key of auth token header
|
||||
AuthTokenHeader = "x-xenon-auth-token"
|
||||
sysAdminRole = "CLOUD_ADMIN"
|
||||
projectAdminRole = "DEVOPS_ADMIN"
|
||||
developerRole = "DEVELOPER"
|
||||
guestRole = "GUEST"
|
||||
)
|
||||
|
||||
var client = &http.Client{
|
||||
Transport: &http.Transport{},
|
||||
}
|
||||
|
||||
// AuthContext ...
|
||||
type AuthContext struct {
|
||||
PrincipalID string `json:"principalId"`
|
||||
Name string `json:"name"`
|
||||
Roles []string `json:"projects"`
|
||||
Projects map[string][]string `json:"roles"`
|
||||
}
|
||||
|
||||
// GetUsername ...
|
||||
func (a *AuthContext) GetUsername() string {
|
||||
return a.PrincipalID
|
||||
}
|
||||
|
||||
// IsSysAdmin ...
|
||||
func (a *AuthContext) IsSysAdmin() bool {
|
||||
isSysAdmin := false
|
||||
for _, role := range a.Roles {
|
||||
// TODO update the value of role when admiral API is ready
|
||||
if role == sysAdminRole {
|
||||
isSysAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return isSysAdmin
|
||||
}
|
||||
|
||||
// HasReadPerm ...
|
||||
func (a *AuthContext) HasReadPerm(project string) bool {
|
||||
_, exist := a.Projects[project]
|
||||
return exist
|
||||
}
|
||||
|
||||
// HasWritePerm ...
|
||||
func (a *AuthContext) HasWritePerm(project string) bool {
|
||||
roles, _ := a.Projects[project]
|
||||
for _, role := range roles {
|
||||
if role == projectAdminRole || role == developerRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasAllPerm ...
|
||||
func (a *AuthContext) HasAllPerm(project string) bool {
|
||||
roles, _ := a.Projects[project]
|
||||
for _, role := range roles {
|
||||
if role == projectAdminRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetByToken ...
|
||||
func GetByToken(token string) (*AuthContext, error) {
|
||||
endpoint := config.AdmiralEndpoint()
|
||||
path := strings.TrimRight(endpoint, "/") + "/sso/auth-context"
|
||||
req, err := http.NewRequest(http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add(AuthTokenHeader, token)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get auth context by token: %d %s",
|
||||
resp.StatusCode, string(data))
|
||||
}
|
||||
|
||||
ctx := &AuthContext{
|
||||
Projects: make(map[string][]string),
|
||||
}
|
||||
if err = json.Unmarshal(data, ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
17
src/common/security/authcontext/authcontext_test.go
Normal file
17
src/common/security/authcontext/authcontext_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 authcontext
|
||||
|
||||
// TODO add test cases
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rbac
|
||||
package local
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common"
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rbac
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -39,14 +39,14 @@ type BaseController struct {
|
||||
func (b *BaseController) Prepare() {
|
||||
ctx, err := filter.GetSecurityContext(b.Ctx.Request)
|
||||
if err != nil {
|
||||
log.Error("failed to get security context")
|
||||
log.Errorf("failed to get security context: %v", err)
|
||||
b.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
b.SecurityCtx = ctx
|
||||
|
||||
pm, err := filter.GetProjectManager(b.Ctx.Request)
|
||||
if err != nil {
|
||||
log.Error("failed to get project manager")
|
||||
log.Errorf("failed to get project manager: %v", err)
|
||||
b.CustomAbort(http.StatusInternalServerError, "")
|
||||
}
|
||||
b.ProjectMgr = pm
|
||||
|
@ -87,6 +87,7 @@ func init() {
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.TestBeegoInit(apppath)
|
||||
|
||||
filter.Init()
|
||||
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
|
||||
|
||||
beego.Router("/api/search/", &SearchAPI{})
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/db"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/pms"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -96,10 +97,16 @@ func initSecretStore() {
|
||||
|
||||
func initProjectManager() {
|
||||
if !WithAdmiral() {
|
||||
// standalone
|
||||
log.Info("initializing the project manager based on database...")
|
||||
GlobalProjectMgr = &db.ProjectManager{}
|
||||
return
|
||||
}
|
||||
// TODO create project manager based on pms
|
||||
|
||||
// integration with admiral
|
||||
// TODO create project manager based on pms using service account
|
||||
log.Info("initializing the project manager based on PMS...")
|
||||
GlobalProjectMgr = pms.NewProjectManager(AdmiralEndpoint(), "")
|
||||
}
|
||||
|
||||
// Load configurations
|
||||
|
@ -18,12 +18,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
beegoctx "github.com/astaxie/beego/context"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
secstore "github.com/vmware/harbor/src/common/secret"
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
"github.com/vmware/harbor/src/common/security/rbac"
|
||||
"github.com/vmware/harbor/src/common/security/admiral"
|
||||
"github.com/vmware/harbor/src/common/security/authcontext"
|
||||
"github.com/vmware/harbor/src/common/security/local"
|
||||
"github.com/vmware/harbor/src/common/security/secret"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/auth"
|
||||
@ -35,12 +37,33 @@ import (
|
||||
type key string
|
||||
|
||||
const (
|
||||
// HarborSecurityContext is the name of security context passed to handlers
|
||||
HarborSecurityContext key = "harbor_security_context"
|
||||
// HarborProjectManager is the name of project manager passed to handlers
|
||||
HarborProjectManager key = "harbor_project_manager"
|
||||
securCtxKey key = "harbor_security_context"
|
||||
pmKey key = "harbor_project_manager"
|
||||
)
|
||||
|
||||
var (
|
||||
reqCtxModifiers []ReqCtxModifier
|
||||
)
|
||||
|
||||
// Init ReqCtxMofiers list
|
||||
func Init() {
|
||||
// integration with admiral
|
||||
if config.WithAdmiral() {
|
||||
reqCtxModifiers = []ReqCtxModifier{
|
||||
&secretReqCtxModifier{},
|
||||
&tokenReqCtxModifier{},
|
||||
&unauthorizedReqCtxModifier{}}
|
||||
return
|
||||
}
|
||||
|
||||
// standalone
|
||||
reqCtxModifiers = []ReqCtxModifier{
|
||||
&secretReqCtxModifier{},
|
||||
&basicAuthReqCtxModifier{},
|
||||
&sessionReqCtxModifier{},
|
||||
&unauthorizedReqCtxModifier{}}
|
||||
}
|
||||
|
||||
// SecurityFilter authenticates the request and passes a security context
|
||||
// and a project manager with it which can be used to do some authN & authZ
|
||||
func SecurityFilter(ctx *beegoctx.Context) {
|
||||
@ -53,94 +76,171 @@ func SecurityFilter(ctx *beegoctx.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(req.URL.RequestURI(), "/api/") &&
|
||||
!strings.HasPrefix(req.URL.RequestURI(), "/service/") {
|
||||
return
|
||||
// add security context and project manager to request context
|
||||
for _, modifier := range reqCtxModifiers {
|
||||
if modifier.Modify(ctx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// fill ctx with security context and project manager
|
||||
fillContext(ctx)
|
||||
}
|
||||
|
||||
func fillContext(ctx *beegoctx.Context) {
|
||||
// secret
|
||||
// ReqCtxModifier modifies the context of request
|
||||
type ReqCtxModifier interface {
|
||||
Modify(*beegoctx.Context) bool
|
||||
}
|
||||
|
||||
type secretReqCtxModifier struct {
|
||||
store *secstore.Store
|
||||
}
|
||||
|
||||
func (s *secretReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
scrt := ctx.GetCookie("secret")
|
||||
if len(scrt) != 0 {
|
||||
ct := context.WithValue(ctx.Request.Context(),
|
||||
HarborProjectManager,
|
||||
getProjectManager(ctx))
|
||||
|
||||
log.Info("creating a secret security context...")
|
||||
ct = context.WithValue(ct, HarborSecurityContext,
|
||||
secret.NewSecurityContext(scrt, config.SecretStore))
|
||||
|
||||
ctx.Request = ctx.Request.WithContext(ct)
|
||||
|
||||
return
|
||||
if len(scrt) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
var err error
|
||||
log.Debug("got secret from request")
|
||||
|
||||
// basic auth
|
||||
var pm projectmanager.ProjectManager
|
||||
if config.WithAdmiral() {
|
||||
// TODO project manager with harbor service accout
|
||||
} else {
|
||||
log.Debug("using local database project manager")
|
||||
pm = config.GlobalProjectMgr
|
||||
}
|
||||
|
||||
log.Debug("creating a secret security context...")
|
||||
securCtx := secret.NewSecurityContext(scrt, s.store)
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type basicAuthReqCtxModifier struct{}
|
||||
|
||||
func (b *basicAuthReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
username, password, ok := ctx.Request.BasicAuth()
|
||||
if ok {
|
||||
// TODO the return data contains other params when integrated
|
||||
// with vic
|
||||
user, err = auth.Login(models.AuthModel{
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
user, err := auth.Login(models.AuthModel{
|
||||
Principal: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to authenticate %s: %v", username, err)
|
||||
return false
|
||||
}
|
||||
if user != nil {
|
||||
log.Info("got user information via basic auth")
|
||||
}
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// session
|
||||
if user == nil {
|
||||
var securCtx security.Context
|
||||
var pm projectmanager.ProjectManager
|
||||
log.Debug("got user information via basic auth")
|
||||
if config.WithAdmiral() {
|
||||
// integration with admiral
|
||||
// we can add logic here to support basic auth in integration mode
|
||||
log.Debug("basic auth isn't supported in integration mode")
|
||||
return false
|
||||
}
|
||||
|
||||
// standalone
|
||||
log.Debug("using local database project manager")
|
||||
pm = config.GlobalProjectMgr
|
||||
log.Debug("creating local database security context...")
|
||||
securCtx = local.NewSecurityContext(user, pm)
|
||||
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type sessionReqCtxModifier struct{}
|
||||
|
||||
func (s *sessionReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
username := ctx.Input.Session("username")
|
||||
isSysAdmin := ctx.Input.Session("isSysAdmin")
|
||||
if username != nil {
|
||||
user = &models.User{
|
||||
if username == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Debug("got user information from session")
|
||||
user := &models.User{
|
||||
Username: username.(string),
|
||||
}
|
||||
|
||||
isSysAdmin := ctx.Input.Session("isSysAdmin")
|
||||
if isSysAdmin != nil && isSysAdmin.(bool) {
|
||||
user.HasAdminRole = 1
|
||||
}
|
||||
log.Info("got user information from session")
|
||||
}
|
||||
|
||||
// TODO maybe need to get token from session
|
||||
}
|
||||
log.Debug("using local database project manager")
|
||||
pm := config.GlobalProjectMgr
|
||||
log.Debug("creating local database security context...")
|
||||
securCtx := local.NewSecurityContext(user, pm)
|
||||
|
||||
if user == nil {
|
||||
log.Info("user information is nil")
|
||||
}
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
|
||||
pm := getProjectManager(ctx)
|
||||
ct := context.WithValue(ctx.Request.Context(), HarborProjectManager, pm)
|
||||
|
||||
log.Info("creating a rbac security context...")
|
||||
ct = context.WithValue(ct, HarborSecurityContext,
|
||||
rbac.NewSecurityContext(user, pm))
|
||||
ctx.Request = ctx.Request.WithContext(ct)
|
||||
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
func getProjectManager(ctx *beegoctx.Context) projectmanager.ProjectManager {
|
||||
if !config.WithAdmiral() {
|
||||
log.Info("filling a project manager based on database...")
|
||||
return config.GlobalProjectMgr
|
||||
type tokenReqCtxModifier struct{}
|
||||
|
||||
func (t *tokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
token := ctx.Request.Header.Get(authcontext.AuthTokenHeader)
|
||||
if len(token) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("filling a project manager based on PMS...")
|
||||
// TODO pass the token to the function
|
||||
return pms.NewProjectManager(config.AdmiralEndpoint(), "")
|
||||
log.Debug("got token from request")
|
||||
|
||||
authContext, err := authcontext.GetByToken(token)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get auth context: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
log.Debug("creating PMS project manager...")
|
||||
pm := pms.NewProjectManager(config.AdmiralEndpoint(), token)
|
||||
log.Debug("creating admiral security context...")
|
||||
securCtx := admiral.NewSecurityContext(authContext, pm)
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// use this one as the last modifier in the modifier list for unauthorized request
|
||||
type unauthorizedReqCtxModifier struct{}
|
||||
|
||||
func (u *unauthorizedReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
|
||||
log.Debug("user information is nil")
|
||||
|
||||
var securCtx security.Context
|
||||
var pm projectmanager.ProjectManager
|
||||
if config.WithAdmiral() {
|
||||
// integration with admiral
|
||||
log.Debug("creating PMS project manager...")
|
||||
pm = pms.NewProjectManager(config.AdmiralEndpoint(), "")
|
||||
log.Debug("creating admiral security context...")
|
||||
securCtx = admiral.NewSecurityContext(nil, pm)
|
||||
} else {
|
||||
// standalone
|
||||
log.Debug("using local database project manager")
|
||||
pm = config.GlobalProjectMgr
|
||||
log.Debug("creating local database security context...")
|
||||
securCtx = local.NewSecurityContext(nil, pm)
|
||||
}
|
||||
setSecurCtxAndPM(ctx.Request, securCtx, pm)
|
||||
return true
|
||||
}
|
||||
|
||||
func setSecurCtxAndPM(req *http.Request, ctx security.Context, pm projectmanager.ProjectManager) {
|
||||
addToReqContext(req, securCtxKey, ctx)
|
||||
addToReqContext(req, pmKey, pm)
|
||||
}
|
||||
|
||||
func addToReqContext(req *http.Request, key, value interface{}) {
|
||||
*req = *(req.WithContext(context.WithValue(req.Context(), key, value)))
|
||||
}
|
||||
|
||||
// GetSecurityContext tries to get security context from request and returns it
|
||||
@ -149,7 +249,7 @@ func GetSecurityContext(req *http.Request) (security.Context, error) {
|
||||
return nil, fmt.Errorf("request is nil")
|
||||
}
|
||||
|
||||
ctx := req.Context().Value(HarborSecurityContext)
|
||||
ctx := req.Context().Value(securCtxKey)
|
||||
if ctx == nil {
|
||||
return nil, fmt.Errorf("the security context got from request is nil")
|
||||
}
|
||||
@ -168,7 +268,7 @@ func GetProjectManager(req *http.Request) (projectmanager.ProjectManager, error)
|
||||
return nil, fmt.Errorf("request is nil")
|
||||
}
|
||||
|
||||
pm := req.Context().Value(HarborProjectManager)
|
||||
pm := req.Context().Value(pmKey)
|
||||
if pm == nil {
|
||||
return nil, fmt.Errorf("the project manager got from request is nil")
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/security"
|
||||
"github.com/vmware/harbor/src/common/security/rbac"
|
||||
"github.com/vmware/harbor/src/common/security/local"
|
||||
"github.com/vmware/harbor/src/common/security/secret"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/db"
|
||||
_ "github.com/vmware/harbor/src/ui/auth/ldap"
|
||||
@ -73,6 +73,8 @@ func TestMain(m *testing.M) {
|
||||
log.Fatalf("failed to initialize database: %v", err)
|
||||
}
|
||||
|
||||
Init()
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@ -80,28 +82,14 @@ func TestSecurityFilter(t *testing.T) {
|
||||
// nil request
|
||||
ctx, err := newContext(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
SecurityFilter(ctx)
|
||||
assert.Nil(t, securityContext(ctx))
|
||||
assert.Nil(t, projectManager(ctx))
|
||||
|
||||
// the pattern of request does not need security check
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/static/index.html", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
t.Fatalf("failed to create context: %v", err)
|
||||
}
|
||||
SecurityFilter(ctx)
|
||||
assert.Nil(t, securityContext(ctx))
|
||||
assert.Nil(t, projectManager(ctx))
|
||||
|
||||
// the pattern of request needs security check
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
@ -116,8 +104,7 @@ func TestSecurityFilter(t *testing.T) {
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
func TestFillContext(t *testing.T) {
|
||||
// secret
|
||||
func TestSecretReqCtxModifier(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
@ -133,13 +120,40 @@ func TestFillContext(t *testing.T) {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
|
||||
fillContext(ctx)
|
||||
modifier := &secretReqCtxModifier{}
|
||||
modified := modifier.Modify(ctx)
|
||||
assert.True(t, modified)
|
||||
assert.IsType(t, &secret.SecurityContext{},
|
||||
securityContext(ctx))
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
// session
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
func TestBasicAuthReqCtxModifier(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
req.SetBasicAuth("admin", "Harbor12345")
|
||||
|
||||
ctx, err := newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
|
||||
modifier := &basicAuthReqCtxModifier{}
|
||||
modified := modifier.Modify(ctx)
|
||||
assert.True(t, modified)
|
||||
|
||||
sc := securityContext(ctx)
|
||||
assert.IsType(t, &local.SecurityContext{}, sc)
|
||||
s := sc.(security.Context)
|
||||
assert.Equal(t, "admin", s.GetUsername())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
func TestSessionReqCtxModifier(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
@ -162,52 +176,47 @@ func TestFillContext(t *testing.T) {
|
||||
}
|
||||
addSessionIDToCookie(req, store.SessionID())
|
||||
|
||||
ctx, err = newContext(req)
|
||||
ctx, err := newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
fillContext(ctx)
|
||||
|
||||
modifier := &sessionReqCtxModifier{}
|
||||
modified := modifier.Modify(ctx)
|
||||
|
||||
assert.True(t, modified)
|
||||
sc := securityContext(ctx)
|
||||
assert.IsType(t, &rbac.SecurityContext{}, sc)
|
||||
assert.IsType(t, &local.SecurityContext{}, sc)
|
||||
s := sc.(security.Context)
|
||||
assert.Equal(t, "admin", s.GetUsername())
|
||||
assert.True(t, s.IsSysAdmin())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
// basic auth
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
req.SetBasicAuth("admin", "Harbor12345")
|
||||
// TODO add test case
|
||||
func TestTokenReqCtxModifier(t *testing.T) {
|
||||
|
||||
ctx, err = newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
fillContext(ctx)
|
||||
sc = securityContext(ctx)
|
||||
assert.IsType(t, &rbac.SecurityContext{}, sc)
|
||||
s = sc.(security.Context)
|
||||
assert.Equal(t, "admin", s.GetUsername())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
|
||||
// no credential
|
||||
req, err = http.NewRequest(http.MethodGet,
|
||||
func TestUnauthorizedReqCtxModifier(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
"http://127.0.0.1/api/projects/", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create request: %v", req)
|
||||
}
|
||||
|
||||
ctx, err = newContext(req)
|
||||
ctx, err := newContext(req)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to crate context: %v", err)
|
||||
}
|
||||
fillContext(ctx)
|
||||
sc = securityContext(ctx)
|
||||
assert.IsType(t, &rbac.SecurityContext{}, sc)
|
||||
s = sc.(security.Context)
|
||||
|
||||
modifier := &unauthorizedReqCtxModifier{}
|
||||
modified := modifier.Modify(ctx)
|
||||
assert.True(t, modified)
|
||||
|
||||
sc := securityContext(ctx)
|
||||
assert.NotNil(t, sc)
|
||||
s := sc.(security.Context)
|
||||
assert.False(t, s.IsAuthenticated())
|
||||
assert.NotNil(t, projectManager(ctx))
|
||||
}
|
||||
@ -241,6 +250,7 @@ func addSessionIDToCookie(req *http.Request, sessionID string) {
|
||||
func securityContext(ctx *beegoctx.Context) interface{} {
|
||||
c, err := GetSecurityContext(ctx.Request)
|
||||
if err != nil {
|
||||
log.Printf("failed to get security context: %v \n", err)
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
@ -250,7 +260,7 @@ func projectManager(ctx *beegoctx.Context) interface{} {
|
||||
if ctx.Request == nil {
|
||||
return nil
|
||||
}
|
||||
return ctx.Request.Context().Value(HarborProjectManager)
|
||||
return ctx.Request.Context().Value(pmKey)
|
||||
}
|
||||
|
||||
func TestGetSecurityContext(t *testing.T) {
|
||||
@ -268,7 +278,7 @@ func TestGetSecurityContext(t *testing.T) {
|
||||
req, err = http.NewRequest("", "", nil)
|
||||
assert.Nil(t, err)
|
||||
req = req.WithContext(context.WithValue(req.Context(),
|
||||
HarborSecurityContext, "test"))
|
||||
securCtxKey, "test"))
|
||||
ctx, err = GetSecurityContext(req)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
@ -276,7 +286,7 @@ func TestGetSecurityContext(t *testing.T) {
|
||||
req, err = http.NewRequest("", "", nil)
|
||||
assert.Nil(t, err)
|
||||
req = req.WithContext(context.WithValue(req.Context(),
|
||||
HarborSecurityContext, rbac.NewSecurityContext(nil, nil)))
|
||||
securCtxKey, local.NewSecurityContext(nil, nil)))
|
||||
ctx, err = GetSecurityContext(req)
|
||||
assert.Nil(t, err)
|
||||
_, ok := ctx.(security.Context)
|
||||
@ -298,7 +308,7 @@ func TestGetProjectManager(t *testing.T) {
|
||||
req, err = http.NewRequest("", "", nil)
|
||||
assert.Nil(t, err)
|
||||
req = req.WithContext(context.WithValue(req.Context(),
|
||||
HarborProjectManager, "test"))
|
||||
pmKey, "test"))
|
||||
pm, err = GetProjectManager(req)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
@ -306,7 +316,7 @@ func TestGetProjectManager(t *testing.T) {
|
||||
req, err = http.NewRequest("", "", nil)
|
||||
assert.Nil(t, err)
|
||||
req = req.WithContext(context.WithValue(req.Context(),
|
||||
HarborProjectManager, &db.ProjectManager{}))
|
||||
pmKey, &db.ProjectManager{}))
|
||||
pm, err = GetProjectManager(req)
|
||||
assert.Nil(t, err)
|
||||
_, ok := pm.(projectmanager.ProjectManager)
|
||||
|
@ -98,6 +98,7 @@ func main() {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
filter.Init()
|
||||
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
|
||||
|
||||
initRouters()
|
||||
|
Loading…
Reference in New Issue
Block a user