enable system resource access (#13826)

1, introduce & define the system resources.
2, replace the IsSysAdmin judge method.
3, give the robot the system access capability.

Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2021-01-07 15:45:04 +08:00 committed by GitHub
parent be98748ca7
commit 0cf43d766c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 516 additions and 125 deletions

View File

@ -56,5 +56,20 @@ const (
ResourceArtifactAddition = Resource("artifact-addition")
ResourceArtifactLabel = Resource("artifact-label")
ResourcePreatPolicy = Resource("preheat-policy")
ResourcePreatInstance = Resource("preheat-instance")
ResourceSelf = Resource("") // subresource for self
ResourceAuditLog = Resource("audit-log")
ResourceCatalog = Resource("catalog")
ResourceProject = Resource("project")
ResourceUser = Resource("user")
ResourceUserGroup = Resource("user-group")
ResourceRegistry = Resource("registry")
ResourceReplication = Resource("replication")
ResourceDistribution = Resource("distribution")
ResourceGarbageCollection = Resource("garbage-collection")
ResourceReplicationAdapter = Resource("replication-adapter")
ResourceReplicationPolicy = Resource("replication-policy")
ResourceScanAll = Resource("scan-all")
ResourceSystemVolumes = Resource("system-volumes")
)

View File

@ -0,0 +1,19 @@
package system
import (
"context"
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
"github.com/goharbor/harbor/src/pkg/permission/evaluator/namespace"
"github.com/goharbor/harbor/src/pkg/permission/evaluator/rbac"
"github.com/goharbor/harbor/src/pkg/permission/types"
)
// NewEvaluator create evaluator for the system
func NewEvaluator(username string, policies []*types.Policy) evaluator.Evaluator {
return namespace.New(NamespaceKind, func(ctx context.Context, ns types.Namespace) evaluator.Evaluator {
return rbac.New(&rbacUser{
username: username,
policies: policies,
})
})
}

View File

@ -0,0 +1,50 @@
package system
import (
"fmt"
"github.com/goharbor/harbor/src/pkg/permission/types"
"strings"
)
const (
// NamespaceKind kind for system namespace
NamespaceKind = "system"
// NamespacePrefix for system namespace
NamespacePrefix = "/system"
)
type systemNamespace struct {
}
func (ns *systemNamespace) Kind() string {
return NamespaceKind
}
func (ns *systemNamespace) Resource(subresources ...types.Resource) types.Resource {
return types.Resource(fmt.Sprintf("/system/")).Subresource(subresources...)
}
func (ns *systemNamespace) Identity() interface{} {
return nil
}
func (ns *systemNamespace) GetPolicies() []*types.Policy {
return policies
}
// NewNamespace returns namespace for project
func NewNamespace() types.Namespace {
return &systemNamespace{}
}
// NamespaceParse ...
func NamespaceParse(resource types.Resource) (types.Namespace, bool) {
if strings.HasPrefix(resource.String(), NamespacePrefix) {
return NewNamespace(), true
}
return nil, false
}
func init() {
types.RegistryNamespaceParse(NamespaceKind, NamespaceParse)
}

View File

@ -0,0 +1,64 @@
package system
import (
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/pkg/permission/types"
)
var (
policies = []*types.Policy{
{Resource: rbac.ResourceCatalog, Action: rbac.ActionRead},
{Resource: rbac.ResourceAuditLog, Action: rbac.ActionList},
{Resource: rbac.ResourceProject, Action: rbac.ActionCreate},
{Resource: rbac.ResourceProject, Action: rbac.ActionRead},
{Resource: rbac.ResourceProject, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceProject, Action: rbac.ActionDelete},
{Resource: rbac.ResourceProject, Action: rbac.ActionList},
{Resource: rbac.ResourceUser, Action: rbac.ActionCreate},
{Resource: rbac.ResourceUser, Action: rbac.ActionRead},
{Resource: rbac.ResourceUser, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceUser, Action: rbac.ActionDelete},
{Resource: rbac.ResourceUser, Action: rbac.ActionList},
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionCreate},
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionRead},
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionDelete},
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionList},
{Resource: rbac.ResourceRegistry, Action: rbac.ActionCreate},
{Resource: rbac.ResourceRegistry, Action: rbac.ActionRead},
{Resource: rbac.ResourceRegistry, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceRegistry, Action: rbac.ActionDelete},
{Resource: rbac.ResourceRegistry, Action: rbac.ActionList},
{Resource: rbac.ResourceReplication, Action: rbac.ActionCreate},
{Resource: rbac.ResourceReplication, Action: rbac.ActionRead},
{Resource: rbac.ResourceReplication, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceReplication, Action: rbac.ActionList},
{Resource: rbac.ResourceReplication, Action: rbac.ActionDelete},
{Resource: rbac.ResourceDistribution, Action: rbac.ActionCreate},
{Resource: rbac.ResourceDistribution, Action: rbac.ActionRead},
{Resource: rbac.ResourceDistribution, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceDistribution, Action: rbac.ActionDelete},
{Resource: rbac.ResourceDistribution, Action: rbac.ActionList},
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionCreate},
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionRead},
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionDelete},
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionList},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionCreate},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionRead},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionUpdate},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionDelete},
{Resource: rbac.ResourceScanAll, Action: rbac.ActionList},
{Resource: rbac.ResourceSystemVolumes, Action: rbac.ActionRead},
}
)

View File

@ -0,0 +1,39 @@
// 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 system
import (
"github.com/goharbor/harbor/src/pkg/permission/types"
)
type rbacUser struct {
username string
policies []*types.Policy
}
// GetUserName returns username of the visitor
func (sru *rbacUser) GetUserName() string {
return sru.username
}
// GetPolicies returns policies of the visitor
func (sru *rbacUser) GetPolicies() []*types.Policy {
return sru.policies
}
// GetRoles returns roles of the visitor
func (sru *rbacUser) GetRoles() []types.RBACRole {
return nil
}

View File

@ -16,6 +16,9 @@ package robot
import (
"context"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/controller/robot"
"strings"
"sync"
"github.com/goharbor/harbor/src/common/models"
@ -31,7 +34,7 @@ type SecurityContext struct {
robot *model.Robot
isSystemLevel bool
ctl project.Controller
policy []*types.Policy
policies []*types.Policy
evaluator evaluator.Evaluator
once sync.Once
}
@ -39,9 +42,10 @@ type SecurityContext struct {
// NewSecurityContext ...
func NewSecurityContext(robot *model.Robot, isSystemLevel bool, policy []*types.Policy) *SecurityContext {
return &SecurityContext{
ctl: project.Ctl,
robot: robot,
policy: policy,
ctl: project.Ctl,
robot: robot,
policies: policy,
isSystemLevel: isSystemLevel,
}
}
@ -78,9 +82,25 @@ func (s *SecurityContext) IsSolutionUser() bool {
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
s.once.Do(func() {
if s.isSystemLevel {
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy))
var proPolicies []*types.Policy
var sysPolicies []*types.Policy
var evaluators evaluator.Evaluators
for _, p := range s.policies {
if strings.HasPrefix(p.Resource.String(), robot.SCOPESYSTEM) {
sysPolicies = append(sysPolicies, p)
} else if strings.HasPrefix(p.Resource.String(), robot.SCOPEPROJECT) {
proPolicies = append(proPolicies, p)
}
}
if len(sysPolicies) != 0 {
evaluators = evaluators.Add(system.NewEvaluator(s.GetUsername(), sysPolicies))
} else if len(proPolicies) != 0 {
evaluators = evaluators.Add(rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), proPolicies)))
}
s.evaluator = evaluators
} else {
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy, filterRobotPolicies))
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policies, filterRobotPolicies))
}
})

View File

@ -13,6 +13,8 @@ const (
// SCOPESYSTEM ...
SCOPESYSTEM = "/system"
// SCOPEPROJECT ...
SCOPEPROJECT = "/project"
// SCOPEALLPROJECT ...
SCOPEALLPROJECT = "/project/*"

View File

@ -17,6 +17,7 @@ package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac/system"
"net/http"
"strconv"
@ -75,7 +76,8 @@ func (l *LabelAPI) requireAccess(label *models.Label, action rbac.Action, subres
switch label.Scope {
case common.LabelScopeGlobal:
hasPermission = l.SecurityCtx.IsSysAdmin()
resource := system.NewNamespace().Resource(rbac.ResourceLabel)
hasPermission = l.SecurityCtx.Can(l.Context(), action, resource)
case common.LabelScopeProject:
if len(subresources) == 0 {
subresources = append(subresources, rbac.ResourceLabel)

View File

@ -2,6 +2,7 @@ package api
import (
"fmt"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
@ -90,7 +91,8 @@ func (w *NotificationJobAPI) List() {
}
func (w *NotificationJobAPI) validateRBAC(action rbac.Action, projectID int64) bool {
if w.SecurityCtx.IsSysAdmin() {
resource := system.NewNamespace().Resource(rbac.ResourceNotificationPolicy)
if w.SecurityCtx.Can(w.Context(), action, resource) {
return true
}

View File

@ -16,11 +16,11 @@ package api
import (
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/controller/quota"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/quota/models"
"github.com/goharbor/harbor/src/pkg/quota/types"
)
@ -32,7 +32,7 @@ type QuotaUpdateRequest struct {
// QuotaAPI handles request to /api/quotas/
type QuotaAPI struct {
BaseController
quota *models.Quota
id int64
}
// Prepare validates the URL and the user
@ -44,11 +44,6 @@ func (qa *QuotaAPI) Prepare() {
return
}
if !qa.SecurityCtx.IsSysAdmin() {
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
return
}
if len(qa.GetStringFromPath(":id")) != 0 {
id, err := qa.GetInt64FromPath(":id")
if err != nil || id <= 0 {
@ -61,25 +56,32 @@ func (qa *QuotaAPI) Prepare() {
qa.SendBadRequestError(errors.New(text))
return
}
quota, err := quota.Ctl.Get(qa.Ctx.Request.Context(), id)
if err != nil {
qa.SendError(err)
return
}
qa.quota = quota
qa.id = id
}
}
// Get returns quota by id
func (qa *QuotaAPI) Get() {
qa.Data["json"] = qa.quota
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionRead, rbac.ResourceQuota) {
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
return
}
quota, err := quota.Ctl.Get(qa.Ctx.Request.Context(), qa.id)
if err != nil {
qa.SendError(err)
return
}
qa.Data["json"] = quota
qa.ServeJSON()
}
// Put update the quota
func (qa *QuotaAPI) Put() {
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionUpdate, rbac.ResourceQuota) {
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
return
}
var req *QuotaUpdateRequest
if err := qa.DecodeJSONReq(&req); err != nil {
qa.SendBadRequestError(err)
@ -87,14 +89,19 @@ func (qa *QuotaAPI) Put() {
}
ctx := qa.Ctx.Request.Context()
if err := quota.Validate(ctx, qa.quota.Reference, req.Hard); err != nil {
q, err := quota.Ctl.Get(ctx, qa.id)
if err != nil {
qa.SendError(err)
return
}
if err := quota.Validate(ctx, q.Reference, req.Hard); err != nil {
qa.SendBadRequestError(err)
return
}
qa.quota.SetHard(req.Hard)
q.SetHard(req.Hard)
if err := quota.Ctl.Update(ctx, qa.quota); err != nil {
if err := quota.Ctl.Update(ctx, q); err != nil {
qa.SendInternalServerError(fmt.Errorf("failed to update hard limits of the quota, error: %v", err))
return
}
@ -102,6 +109,10 @@ func (qa *QuotaAPI) Put() {
// List returns quotas by query
func (qa *QuotaAPI) List() {
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionList, rbac.ResourceQuota) {
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
return
}
page, size, err := qa.GetPaginationParams()
if err != nil {
qa.SendBadRequestError(err)

View File

@ -3,6 +3,10 @@ package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/pkg/permission/types"
"net/http"
"strconv"
@ -23,6 +27,7 @@ type RegistryAPI struct {
BaseController
manager registry.Manager
policyCtl policy.Controller
resource types.Resource
}
// Prepare validates the user
@ -32,11 +37,7 @@ func (t *RegistryAPI) Prepare() {
t.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
if !t.SecurityCtx.IsSysAdmin() {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
t.resource = system.NewNamespace().Resource(rbac.ResourceRegistry)
t.manager = replication.RegistryMgr
t.policyCtl = replication.PolicyCtl
@ -44,6 +45,10 @@ func (t *RegistryAPI) Prepare() {
// Ping checks health status of a registry
func (t *RegistryAPI) Ping() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
req := struct {
ID *int64 `json:"id"`
Type *string `json:"type"`
@ -120,6 +125,10 @@ func (t *RegistryAPI) Ping() {
// Get gets a registry by id.
func (t *RegistryAPI) Get() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetIDFromURL()
if err != nil {
t.SendBadRequestError(err)
@ -157,6 +166,10 @@ func hideAccessSecret(credential *model.Credential) {
// List lists all registries
func (t *RegistryAPI) List() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionList, rbac.ResourceRegistry) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
queryStr := t.GetString("q")
// keep backward compatibility for the "name" query
if len(queryStr) == 0 {
@ -189,6 +202,10 @@ func (t *RegistryAPI) List() {
// Post creates a registry
func (t *RegistryAPI) Post() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionCreate, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
r := &model.Registry{}
isValid, err := t.DecodeJSONReqAndValidate(r)
if !isValid {
@ -243,6 +260,10 @@ func (t *RegistryAPI) getHealthStatus(r *model.Registry) string {
// Put updates a registry
func (t *RegistryAPI) Put() {
if !t.SecurityCtx.Can(t.Context(), rbac.ActionUpdate, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetIDFromURL()
if err != nil {
t.SendBadRequestError(err)
@ -323,6 +344,10 @@ func (t *RegistryAPI) Put() {
// Delete deletes a registry
func (t *RegistryAPI) Delete() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionDelete, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetIDFromURL()
if err != nil {
t.SendBadRequestError(err)
@ -397,6 +422,10 @@ func (t *RegistryAPI) Delete() {
// GetInfo returns the base info and capability declarations of the registry
func (t *RegistryAPI) GetInfo() {
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
return
}
id, err := t.GetInt64FromPath(":id")
// "0" is used for the ID of the local Harbor registry
if err != nil || id < 0 {

View File

@ -16,6 +16,9 @@ package api
import (
"errors"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/replication/adapter"
"github.com/goharbor/harbor/src/replication/model"
)
@ -23,23 +26,25 @@ import (
// ReplicationAdapterAPI handles the replication adapter requests
type ReplicationAdapterAPI struct {
BaseController
resource types.Resource
}
// Prepare ...
func (r *ReplicationAdapterAPI) Prepare() {
r.BaseController.Prepare()
if !r.SecurityCtx.IsSysAdmin() {
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
r.resource = system.NewNamespace().Resource(rbac.ResourceReplicationAdapter)
}
// List the replication adapters
func (r *ReplicationAdapterAPI) List() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
types := []model.RegistryType{}
types = append(types, adapter.ListRegisteredAdapterTypes()...)
r.WriteJSONData(types)
@ -47,5 +52,9 @@ func (r *ReplicationAdapterAPI) List() {
// ListAdapterInfos the replication adapter infos
func (r *ReplicationAdapterAPI) ListAdapterInfos() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
r.WriteJSONData(adapter.ListAdapterInfos())
}

View File

@ -17,9 +17,12 @@ package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/orm"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/permission/types"
"net/http"
"strconv"
@ -36,23 +39,25 @@ import (
// ReplicationPolicyAPI handles the replication policy requests
type ReplicationPolicyAPI struct {
BaseController
resource types.Resource
}
// Prepare ...
func (r *ReplicationPolicyAPI) Prepare() {
r.BaseController.Prepare()
if !r.SecurityCtx.IsSysAdmin() {
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
r.resource = system.NewNamespace().Resource(rbac.ResourceReplicationPolicy)
}
// List the replication policies
func (r *ReplicationPolicyAPI) List() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
page, size, err := r.GetPaginationParams()
if err != nil {
r.SendInternalServerError(err)
@ -82,6 +87,10 @@ func (r *ReplicationPolicyAPI) List() {
// Create the replication policy
func (r *ReplicationPolicyAPI) Create() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionCreate, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
policy := &model.Policy{}
isValid, err := r.DecodeJSONReqAndValidate(policy)
if !isValid {
@ -141,6 +150,10 @@ func (r *ReplicationPolicyAPI) validateRegistry(policy *model.Policy) bool {
// Get the specified replication policy
func (r *ReplicationPolicyAPI) Get() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionRead, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
id, err := r.GetInt64FromPath(":id")
if id <= 0 || err != nil {
r.SendBadRequestError(errors.New("invalid policy ID"))
@ -166,6 +179,10 @@ func (r *ReplicationPolicyAPI) Get() {
// Update the replication policy
func (r *ReplicationPolicyAPI) Update() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionUpdate, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
id, err := r.GetInt64FromPath(":id")
if id <= 0 || err != nil {
r.SendBadRequestError(errors.New("invalid policy ID"))
@ -207,6 +224,10 @@ func (r *ReplicationPolicyAPI) Update() {
// Delete the replication policy
func (r *ReplicationPolicyAPI) Delete() {
if !r.SecurityCtx.Can(r.Context(), rbac.ActionDelete, r.resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return
}
id, err := r.GetInt64FromPath(":id")
if id <= 0 || err != nil {
r.SendBadRequestError(errors.New("invalid policy ID"))

View File

@ -3,6 +3,7 @@ package api
import (
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/pkg/task"
"net/http"
@ -435,7 +436,8 @@ func (r *RetentionAPI) requireAccess(p *policy.Metadata, action rbac.Action, sub
}
hasPermission, _ = r.HasProjectPermission(p.Scope.Reference, action, subresources...)
default:
hasPermission = r.SecurityCtx.IsSysAdmin()
resource := system.NewNamespace().Resource(rbac.ResourceTagRetention)
hasPermission = r.SecurityCtx.Can(r.Context(), action, resource)
}
if !hasPermission {

View File

@ -16,6 +16,9 @@ package api
import (
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"github.com/goharbor/harbor/src/pkg/permission/types"
"net/http"
s "github.com/goharbor/harbor/src/controller/scanner"
@ -31,6 +34,8 @@ type ScannerAPI struct {
// Controller for the plug scanners
c s.Controller
resource types.Resource
}
// Prepare sth. for the subsequent actions
@ -44,10 +49,7 @@ func (sa *ScannerAPI) Prepare() {
return
}
if !sa.SecurityCtx.IsSysAdmin() {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
sa.resource = system.NewNamespace().Resource(rbac.ResourceScanner)
// Use the default controller
sa.c = s.DefaultController
@ -55,6 +57,10 @@ func (sa *ScannerAPI) Prepare() {
// Get the specified scanner
func (sa *ScannerAPI) Get() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
if r := sa.get(); r != nil {
// Response to the client
sa.Data["json"] = r
@ -64,6 +70,10 @@ func (sa *ScannerAPI) Get() {
// Metadata returns the metadata of the given scanner.
func (sa *ScannerAPI) Metadata() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
uuid := sa.GetStringFromPath(":uuid")
meta, err := sa.c.GetMetadata(uuid)
@ -79,6 +89,10 @@ func (sa *ScannerAPI) Metadata() {
// List all the scanners
func (sa *ScannerAPI) List() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionList, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
p, pz, err := sa.GetPaginationParams()
if err != nil {
sa.SendBadRequestError(errors.Wrap(err, "scanner API: list all"))
@ -117,6 +131,10 @@ func (sa *ScannerAPI) List() {
// Create a new scanner
func (sa *ScannerAPI) Create() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionCreate, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
r := &scanner.Registration{}
if err := sa.DecodeJSONReq(r); err != nil {
@ -158,6 +176,10 @@ func (sa *ScannerAPI) Create() {
// Update a scanner
func (sa *ScannerAPI) Update() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionUpdate, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
r := sa.get()
if r == nil {
// meet error
@ -213,6 +235,10 @@ func (sa *ScannerAPI) Update() {
// Delete the scanner
func (sa *ScannerAPI) Delete() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionDelete, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
r := sa.get()
if r == nil {
// meet error
@ -237,6 +263,10 @@ func (sa *ScannerAPI) Delete() {
// SetAsDefault sets the given registration as default one
func (sa *ScannerAPI) SetAsDefault() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionCreate, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
uid := sa.GetStringFromPath(":uuid")
m := make(map[string]interface{})
@ -261,6 +291,10 @@ func (sa *ScannerAPI) SetAsDefault() {
// Ping the registration.
func (sa *ScannerAPI) Ping() {
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
return
}
r := &scanner.Registration{}
if err := sa.DecodeJSONReq(r); err != nil {

View File

@ -15,8 +15,10 @@
package api
import (
"context"
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac/system"
"net/http"
"regexp"
"strconv"
@ -39,9 +41,9 @@ type UserAPI struct {
currentUserID int
userID int
SelfRegistration bool
IsAdmin bool
AuthMode string
secretKey string
resource types.Resource
}
type passwordReq struct {
@ -137,12 +139,12 @@ func (ua *UserAPI) Prepare() {
}
}
ua.IsAdmin = ua.SecurityCtx.IsSysAdmin()
ua.resource = system.NewNamespace().Resource(rbac.ResourceUser)
}
// Get ...
func (ua *UserAPI) Get() {
if ua.userID == ua.currentUserID || ua.IsAdmin {
if ua.userID == ua.currentUserID || ua.SecurityCtx.Can(ua.Context(), rbac.ActionRead, ua.resource) {
userQuery := models.User{UserID: ua.userID}
u, err := dao.GetUser(userQuery)
if err != nil {
@ -178,7 +180,7 @@ func (ua *UserAPI) Get() {
// List ...
func (ua *UserAPI) List() {
if !ua.IsAdmin {
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionList, ua.resource) {
log.Errorf("Current user, id: %d does not have admin role, can not list users", ua.currentUserID)
ua.SendForbiddenError(errors.New("user does not have admin role"))
return
@ -262,7 +264,7 @@ func (ua *UserAPI) Search() {
// Put ...
func (ua *UserAPI) Put() {
if !ua.modifiable() {
if !ua.modifiable(ua.Context()) {
ua.SendForbiddenError(fmt.Errorf("User with ID %d cannot be modified", ua.userID))
return
}
@ -318,13 +320,13 @@ func (ua *UserAPI) Post() {
return
}
if !(ua.SelfRegistration || ua.IsAdmin) {
if !(ua.SelfRegistration || ua.SecurityCtx.Can(ua.Context(), rbac.ActionCreate, ua.resource)) {
log.Warning("Registration can only be used by admin role user when self-registration is off.")
ua.SendForbiddenError(errors.New(""))
return
}
if !ua.IsAdmin && !lib.GetCarrySession(ua.Ctx.Request.Context()) {
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionCreate, ua.resource) && !lib.GetCarrySession(ua.Ctx.Request.Context()) {
ua.SendForbiddenError(errors.New("self-registration cannot be triggered via API"))
return
}
@ -341,7 +343,7 @@ func (ua *UserAPI) Post() {
return
}
if !ua.IsAdmin && user.SysAdminFlag {
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionCreate, ua.resource) && user.SysAdminFlag {
msg := "Non-admin cannot create an admin user."
log.Errorf(msg)
ua.SendForbiddenError(errors.New(msg))
@ -383,7 +385,7 @@ func (ua *UserAPI) Post() {
// Delete ...
func (ua *UserAPI) Delete() {
if !ua.IsAdmin || ua.AuthMode != common.DBAuth || ua.userID == 1 || ua.currentUserID == ua.userID {
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionDelete, ua.resource) || ua.AuthMode != common.DBAuth || ua.userID == 1 || ua.currentUserID == ua.userID {
ua.SendForbiddenError(fmt.Errorf("User with ID: %d cannot be removed, auth mode: %s, current user ID: %d", ua.userID, ua.AuthMode, ua.currentUserID))
return
}
@ -399,7 +401,7 @@ func (ua *UserAPI) Delete() {
// ChangePassword handles PUT to /api/users/{}/password
func (ua *UserAPI) ChangePassword() {
if !ua.modifiable() {
if !ua.modifiable(ua.Context()) {
ua.SendForbiddenError(fmt.Errorf("User with ID: %d is not modifiable", ua.userID))
return
}
@ -456,7 +458,7 @@ func (ua *UserAPI) ChangePassword() {
// ToggleUserAdminRole handles PUT api/users/{}/sysadmin
func (ua *UserAPI) ToggleUserAdminRole() {
if !ua.IsAdmin {
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionUpdate, ua.resource) {
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
ua.RenderError(http.StatusForbidden, "User does not have admin role")
return
@ -527,7 +529,7 @@ func (ua *UserAPI) SetCLISecret() {
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
return
}
if ua.userID != ua.currentUserID && !ua.IsAdmin {
if ua.userID != ua.currentUserID && !ua.SecurityCtx.Can(ua.Context(), rbac.ActionUpdate, ua.resource) {
ua.SendForbiddenError(errors.New(""))
return
}
@ -584,10 +586,10 @@ func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
}
// modifiable returns whether the modify is allowed based on current auth mode and context
func (ua *UserAPI) modifiable() bool {
func (ua *UserAPI) modifiable(ctx context.Context) bool {
if ua.AuthMode == common.DBAuth {
// When the auth mode is local DB, admin can modify anyone, non-admin can modify himself.
return ua.IsAdmin || ua.userID == ua.currentUserID
return ua.SecurityCtx.Can(ctx, rbac.ActionUpdate, ua.resource) || ua.userID == ua.currentUserID
}
// When the auth mode is external IDM backend, only the super user can modify himself,
// because he's the only one whose information is stored in local DB.

View File

@ -14,7 +14,10 @@
package api
import (
"context"
"fmt"
securitytesting "github.com/goharbor/harbor/src/testing/common/security"
"github.com/goharbor/harbor/src/testing/mock"
"net/http"
"testing"
@ -617,45 +620,50 @@ func TestModifiable(t *testing.T) {
BaseAPI: api.BaseAPI{
Controller: beego.Controller{},
},
SecurityCtx: nil,
}
security := &securitytesting.Context{}
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
base.SecurityCtx = security
ua1 := &UserAPI{
BaseController: base,
currentUserID: 3,
userID: 4,
SelfRegistration: false,
IsAdmin: false,
AuthMode: "db_auth",
}
assert.False(ua1.modifiable())
assert.False(ua1.modifiable(context.TODO()))
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
ua2 := &UserAPI{
BaseController: base,
currentUserID: 3,
userID: 4,
SelfRegistration: false,
IsAdmin: true,
AuthMode: "db_auth",
}
assert.True(ua2.modifiable())
assert.True(ua2.modifiable(context.TODO()))
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
ua3 := &UserAPI{
BaseController: base,
currentUserID: 3,
userID: 4,
SelfRegistration: false,
IsAdmin: true,
AuthMode: "ldap_auth",
}
assert.False(ua3.modifiable())
assert.False(ua3.modifiable(context.TODO()))
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
ua4 := &UserAPI{
BaseController: base,
currentUserID: 1,
userID: 1,
SelfRegistration: false,
IsAdmin: true,
AuthMode: "ldap_auth",
}
assert.True(ua4.modifiable())
assert.True(ua4.modifiable(context.TODO()))
}
func TestUsersCurrentPermissions(t *testing.T) {

View File

@ -17,6 +17,8 @@ package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/rbac/system"
"net/http"
"strconv"
"strings"
@ -58,11 +60,6 @@ func (uga *UserGroupAPI) Prepare() {
return
}
uga.id = int(ugid)
// Common user can create/update, only harbor admin can delete user group.
if uga.Ctx.Input.IsDelete() && !uga.SecurityCtx.IsSysAdmin() {
uga.SendForbiddenError(errors.New(uga.SecurityCtx.GetUsername()))
return
}
authMode, err := config.AuthMode()
if err != nil {
uga.SendInternalServerError(errors.New("failed to get authentication mode"))
@ -192,6 +189,11 @@ func (uga *UserGroupAPI) Put() {
// Delete ...
func (uga *UserGroupAPI) Delete() {
resource := system.NewNamespace().Resource(rbac.ResourceUserGroup)
if !uga.SecurityCtx.Can(uga.Context(), rbac.ActionDelete, resource) {
uga.SendForbiddenError(errors.New(uga.SecurityCtx.GetUsername()))
return
}
err := group.DeleteUserGroup(uga.id)
if err != nil {
uga.SendInternalServerError(fmt.Errorf("Error occurred in update user group, error: %v", err))

View File

@ -17,6 +17,7 @@ package v2auth
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/common/rbac/system"
"net/http"
"net/url"
"strings"
@ -52,8 +53,11 @@ func (rc *reqChecker) check(req *http.Request) (string, error) {
if a.target == login && !securityCtx.IsAuthenticated() {
return getChallenge(req, al), errors.New("unauthorized")
}
if a.target == catalog && !securityCtx.IsSysAdmin() {
return getChallenge(req, al), fmt.Errorf("unauthorized to list catalog")
if a.target == catalog {
resource := system.NewNamespace().Resource(rbac.ResourceCatalog)
if !securityCtx.Can(req.Context(), rbac.ActionRead, resource) {
return getChallenge(req, al), fmt.Errorf("unauthorized to list catalog")
}
}
if a.target == repository && req.Header.Get(authHeader) == "" && req.Method == http.MethodHead { // make sure 401 is returned for CLI HEAD, see #11271
return getChallenge(req, al), fmt.Errorf("authorize header needed to send HEAD to repository")

View File

@ -3,7 +3,6 @@ package handler
import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/goharbor/harbor/src/common/rbac"
@ -44,7 +43,7 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
return a.SendError(ctx, err)
}
if !secCtx.IsSysAdmin() {
if err := a.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAuditLog); err != nil {
ol := &q.OrList{}
if sc, ok := secCtx.(*local.SecurityContext); ok && sc.IsAuthenticated() {
user := sc.User()

View File

@ -18,6 +18,7 @@ package handler
import (
"context"
"github.com/goharbor/harbor/src/common/rbac/system"
"net/http"
"net/url"
"strconv"
@ -105,8 +106,8 @@ func (b *BaseAPI) RequireProjectAccess(ctx context.Context, projectIDOrName inte
return errors.ForbiddenError(nil)
}
// RequireSysAdmin checks the system admin permission according to the security context
func (b *BaseAPI) RequireSysAdmin(ctx context.Context) error {
// RequireSystemAccess checks the system admin permission according to the security context
func (b *BaseAPI) RequireSystemAccess(ctx context.Context, action rbac.Action, subresource ...rbac.Resource) error {
secCtx, ok := security.FromContext(ctx)
if !ok {
return errors.UnauthorizedError(errors.New("security context not found"))
@ -114,7 +115,8 @@ func (b *BaseAPI) RequireSysAdmin(ctx context.Context) error {
if !secCtx.IsAuthenticated() {
return errors.UnauthorizedError(nil)
}
if !secCtx.IsSysAdmin() {
resource := system.NewNamespace().Resource(subresource...)
if !secCtx.Can(ctx, action, resource) {
return errors.ForbiddenError(nil).WithMessage(secCtx.GetUsername())
}
return nil

View File

@ -8,6 +8,7 @@ import (
"strings"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/controller/gc"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/errors"
@ -30,13 +31,13 @@ func newGCAPI() *gcAPI {
}
func (g *gcAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
if err := g.RequireSysAdmin(ctx); err != nil {
return g.SendError(ctx, err)
}
return nil
}
func (g *gcAPI) CreateGCSchedule(ctx context.Context, params operation.CreateGCScheduleParams) middleware.Responder {
if err := g.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceGarbageCollection); err != nil {
return g.SendError(ctx, err)
}
id, err := g.kick(ctx, params.Schedule.Schedule.Type, params.Schedule.Schedule.Cron, params.Schedule.Parameters)
if err != nil {
return g.SendError(ctx, err)
@ -51,6 +52,9 @@ func (g *gcAPI) CreateGCSchedule(ctx context.Context, params operation.CreateGCS
}
func (g *gcAPI) UpdateGCSchedule(ctx context.Context, params operation.UpdateGCScheduleParams) middleware.Responder {
if err := g.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceGarbageCollection); err != nil {
return g.SendError(ctx, err)
}
_, err := g.kick(ctx, params.Schedule.Schedule.Type, params.Schedule.Schedule.Cron, params.Schedule.Parameters)
if err != nil {
return g.SendError(ctx, err)
@ -114,6 +118,9 @@ func (g *gcAPI) updateSchedule(ctx context.Context, cronType, cron string, polic
}
func (g *gcAPI) GetGCSchedule(ctx context.Context, params operation.GetGCScheduleParams) middleware.Responder {
if err := g.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceGarbageCollection); err != nil {
return g.SendError(ctx, err)
}
schedule, err := g.gcCtr.GetSchedule(ctx)
if errors.IsNotFoundErr(err) {
return operation.NewGetGCScheduleOK()
@ -126,6 +133,9 @@ func (g *gcAPI) GetGCSchedule(ctx context.Context, params operation.GetGCSchedul
}
func (g *gcAPI) GetGCHistory(ctx context.Context, params operation.GetGCHistoryParams) middleware.Responder {
if err := g.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceGarbageCollection); err != nil {
return g.SendError(ctx, err)
}
query, err := g.BuildQuery(ctx, params.Q, params.Page, params.PageSize)
if err != nil {
return g.SendError(ctx, err)
@ -171,6 +181,9 @@ func (g *gcAPI) GetGCHistory(ctx context.Context, params operation.GetGCHistoryP
}
func (g *gcAPI) GetGC(ctx context.Context, params operation.GetGCParams) middleware.Responder {
if err := g.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceGarbageCollection); err != nil {
return g.SendError(ctx, err)
}
exec, err := g.gcCtr.GetExecution(ctx, params.GCID)
if err != nil {
return g.SendError(ctx, err)
@ -198,6 +211,9 @@ func (g *gcAPI) GetGC(ctx context.Context, params operation.GetGCParams) middlew
}
func (g *gcAPI) GetGCLog(ctx context.Context, params operation.GetGCLogParams) middleware.Responder {
if err := g.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceGarbageCollection); err != nil {
return g.SendError(ctx, err)
}
tasks, err := g.gcCtr.ListTasks(ctx, q.New(q.KeyWords{
"ExecutionID": params.GCID,
}))

View File

@ -3,7 +3,6 @@ package handler
import (
"context"
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
@ -16,6 +15,7 @@ import (
projectCtl "github.com/goharbor/harbor/src/controller/project"
taskCtl "github.com/goharbor/harbor/src/controller/task"
"github.com/goharbor/harbor/src/jobservice/job"
"github.com/goharbor/harbor/src/lib/errors"
liberrors "github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
@ -56,7 +56,7 @@ func (api *preheatAPI) Prepare(ctx context.Context, operation string, params int
}
func (api *preheatAPI) CreateInstance(ctx context.Context, params operation.CreateInstanceParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}
@ -75,7 +75,7 @@ func (api *preheatAPI) CreateInstance(ctx context.Context, params operation.Crea
}
func (api *preheatAPI) DeleteInstance(ctx context.Context, params operation.DeleteInstanceParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionDelete, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}
@ -93,7 +93,7 @@ func (api *preheatAPI) DeleteInstance(ctx context.Context, params operation.Dele
}
func (api *preheatAPI) GetInstance(ctx context.Context, params operation.GetInstanceParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}
@ -113,7 +113,7 @@ func (api *preheatAPI) GetInstance(ctx context.Context, params operation.GetInst
// ListInstances is List p2p instances
func (api *preheatAPI) ListInstances(ctx context.Context, params operation.ListInstancesParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}
@ -147,7 +147,7 @@ func (api *preheatAPI) ListInstances(ctx context.Context, params operation.ListI
}
func (api *preheatAPI) ListProviders(ctx context.Context, params operation.ListProvidersParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}
@ -162,7 +162,7 @@ func (api *preheatAPI) ListProviders(ctx context.Context, params operation.ListP
// UpdateInstance is Update instance
func (api *preheatAPI) UpdateInstance(ctx context.Context, params operation.UpdateInstanceParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}
@ -387,7 +387,7 @@ func (api *preheatAPI) ManualPreheat(ctx context.Context, params operation.Manua
}
func (api *preheatAPI) PingInstances(ctx context.Context, params operation.PingInstancesParams) middleware.Responder {
if err := api.RequireSysAdmin(ctx); err != nil {
if err := api.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePreatInstance); err != nil {
return api.SendError(ctx, err)
}

View File

@ -76,22 +76,21 @@ func (a *projectAPI) CreateProject(ctx context.Context, params operation.CreateP
}
secCtx, _ := security.FromContext(ctx)
if onlyAdmin && !(secCtx.IsSysAdmin() || secCtx.IsSolutionUser()) {
if onlyAdmin && !(a.isSysAdmin(ctx, rbac.ActionCreate) || secCtx.IsSolutionUser()) {
log.Errorf("Only sys admin can create project")
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create project"))
}
req := params.Project
if req.RegistryID != nil && !secCtx.IsSysAdmin() {
// only system admin can create the proxy cache project
if req.RegistryID != nil && !a.isSysAdmin(ctx, rbac.ActionCreate) {
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create proxy cache project"))
}
// populate storage limit
if config.QuotaPerProjectEnable() {
// the security context is not sys admin, set the StorageLimit the global StoragePerProject
if req.StorageLimit == nil || *req.StorageLimit == 0 || !secCtx.IsSysAdmin() {
if req.StorageLimit == nil || *req.StorageLimit == 0 || !a.isSysAdmin(ctx, rbac.ActionCreate) {
setting, err := config.QuotaSetting()
if err != nil {
log.Errorf("failed to get quota setting: %v", err)
@ -378,7 +377,7 @@ func (a *projectAPI) ListProjects(ctx context.Context, params operation.ListProj
secCtx, ok := security.FromContext(ctx)
if ok && secCtx.IsAuthenticated() {
if !secCtx.IsSysAdmin() && !secCtx.IsSolutionUser() {
if !a.isSysAdmin(ctx, rbac.ActionList) && !secCtx.IsSolutionUser() {
// authenticated but not system admin or solution user,
// return public projects and projects that the user is member of
if l, ok := secCtx.(*local.SecurityContext); ok {
@ -585,6 +584,13 @@ func (a *projectAPI) populateProperties(ctx context.Context, p *project.Project)
return nil
}
func (a *projectAPI) isSysAdmin(ctx context.Context, action rbac.Action) bool {
if err := a.RequireSystemAccess(ctx, action, rbac.ResourceProject); err != nil {
return false
}
return true
}
func getProjectQuotaSummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
if !config.QuotaPerProjectEnable() {
log.Debug("Quota per project disabled")

View File

@ -16,6 +16,7 @@ package handler
import (
"context"
"github.com/goharbor/harbor/src/common/rbac"
"strconv"
"strings"
@ -48,14 +49,14 @@ type replicationAPI struct {
}
func (r *replicationAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
if err := r.RequireSysAdmin(ctx); err != nil {
return r.SendError(ctx, err)
}
return nil
}
func (r *replicationAPI) StartReplication(ctx context.Context, params operation.StartReplicationParams) middleware.Responder {
// TODO move the following logic to the replication controller after refactoring the policy management part with the new programming model
if err := r.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceReplication); err != nil {
return r.SendError(ctx, err)
}
policy, err := r.policyMgr.Get(params.Execution.PolicyID)
if err != nil {
return r.SendError(ctx, err)
@ -85,6 +86,9 @@ func (r *replicationAPI) StartReplication(ctx context.Context, params operation.
}
func (r *replicationAPI) StopReplication(ctx context.Context, params operation.StopReplicationParams) middleware.Responder {
if err := r.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceReplication); err != nil {
return r.SendError(ctx, err)
}
if err := r.ctl.Stop(ctx, params.ID); err != nil {
return r.SendError(ctx, err)
}
@ -92,6 +96,9 @@ func (r *replicationAPI) StopReplication(ctx context.Context, params operation.S
}
func (r *replicationAPI) ListReplicationExecutions(ctx context.Context, params operation.ListReplicationExecutionsParams) middleware.Responder {
if err := r.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceReplication); err != nil {
return r.SendError(ctx, err)
}
query, err := r.BuildQuery(ctx, nil, params.Page, params.PageSize)
if err != nil {
return r.SendError(ctx, err)
@ -151,6 +158,9 @@ func (r *replicationAPI) ListReplicationExecutions(ctx context.Context, params o
}
func (r *replicationAPI) GetReplicationExecution(ctx context.Context, params operation.GetReplicationExecutionParams) middleware.Responder {
if err := r.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceReplication); err != nil {
return r.SendError(ctx, err)
}
execution, err := r.ctl.GetExecution(ctx, params.ID)
if err != nil {
return r.SendError(ctx, err)
@ -159,6 +169,9 @@ func (r *replicationAPI) GetReplicationExecution(ctx context.Context, params ope
}
func (r *replicationAPI) ListReplicationTasks(ctx context.Context, params operation.ListReplicationTasksParams) middleware.Responder {
if err := r.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceReplication); err != nil {
return r.SendError(ctx, err)
}
query, err := r.BuildQuery(ctx, nil, params.Page, params.PageSize)
if err != nil {
return r.SendError(ctx, err)
@ -210,6 +223,9 @@ func (r *replicationAPI) ListReplicationTasks(ctx context.Context, params operat
}
func (r *replicationAPI) GetReplicationLog(ctx context.Context, params operation.GetReplicationLogParams) middleware.Responder {
if err := r.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceReplication); err != nil {
return r.SendError(ctx, err)
}
execution, err := r.ctl.GetExecution(ctx, params.ID)
if err != nil {
return r.SendError(ctx, err)
@ -229,7 +245,6 @@ func (r *replicationAPI) GetReplicationLog(ctx context.Context, params operation
}
return operation.NewGetReplicationLogOK().WithContentType("text/plain").WithPayload(string(log))
}
func convertExecution(execution *replication.Execution) *models.ReplicationExecution {
exec := &models.ReplicationExecution{
ID: execution.ID,

View File

@ -244,7 +244,7 @@ func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSe
func (rAPI *robotAPI) requireAccess(ctx context.Context, level string, projectIDOrName interface{}, action rbac.Action) error {
if level == robot.LEVELSYSTEM {
return rAPI.RequireSysAdmin(ctx)
return rAPI.RequireSystemAccess(ctx, action, rbac.ResourceRobot)
} else if level == robot.LEVELPROJECT {
return rAPI.RequireProjectAccess(ctx, projectIDOrName, action, rbac.ResourceRobot)
}

View File

@ -20,6 +20,7 @@ import (
"strings"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/controller/scan"
"github.com/goharbor/harbor/src/controller/scanner"
"github.com/goharbor/harbor/src/jobservice/job"
@ -50,18 +51,14 @@ type scanAllAPI struct {
}
func (s *scanAllAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
if err := s.RequireSysAdmin(ctx); err != nil {
return s.SendError(ctx, err)
}
if err := s.requireScanEnabled(ctx); err != nil {
return s.SendError(ctx, err)
}
return nil
}
func (s *scanAllAPI) CreateScanAllSchedule(ctx context.Context, params operation.CreateScanAllScheduleParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionCreate); err != nil {
return s.SendError(ctx, err)
}
req := params.Schedule
if req.Schedule.Type == ScheduleNone {
@ -102,6 +99,9 @@ func (s *scanAllAPI) CreateScanAllSchedule(ctx context.Context, params operation
}
func (s *scanAllAPI) UpdateScanAllSchedule(ctx context.Context, params operation.UpdateScanAllScheduleParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionUpdate); err != nil {
return s.SendError(ctx, err)
}
req := params.Schedule
if req.Schedule.Type == ScheduleManual {
@ -130,6 +130,9 @@ func (s *scanAllAPI) UpdateScanAllSchedule(ctx context.Context, params operation
}
func (s *scanAllAPI) GetScanAllSchedule(ctx context.Context, params operation.GetScanAllScheduleParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionRead); err != nil {
return s.SendError(ctx, err)
}
schedule, err := s.getScanAllSchedule(ctx)
if err != nil {
return s.SendError(ctx, err)
@ -139,6 +142,9 @@ func (s *scanAllAPI) GetScanAllSchedule(ctx context.Context, params operation.Ge
}
func (s *scanAllAPI) GetLatestScanAllMetrics(ctx context.Context, params operation.GetLatestScanAllMetricsParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionRead); err != nil {
return s.SendError(ctx, err)
}
stats, err := s.getMetrics(ctx)
if err != nil {
return s.SendError(ctx, err)
@ -148,6 +154,9 @@ func (s *scanAllAPI) GetLatestScanAllMetrics(ctx context.Context, params operati
}
func (s *scanAllAPI) GetLatestScheduledScanAllMetrics(ctx context.Context, params operation.GetLatestScheduledScanAllMetricsParams) middleware.Responder {
if err := s.requireAccess(ctx, rbac.ActionRead); err != nil {
return s.SendError(ctx, err)
}
stats, err := s.getMetrics(ctx, task.ExecutionTriggerSchedule)
if err != nil {
return s.SendError(ctx, err)
@ -259,3 +268,13 @@ func (s *scanAllAPI) requireScanEnabled(ctx context.Context) error {
return nil
}
func (s *scanAllAPI) requireAccess(ctx context.Context, action rbac.Action) error {
if err := s.RequireSystemAccess(ctx, action, rbac.ResourceScanAll); err != nil {
return err
}
if err := s.requireScanEnabled(ctx); err != nil {
return err
}
return nil
}

View File

@ -130,7 +130,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
{
// system admin required
suite.Security.On("IsAuthenticated").Return(true).Once()
suite.Security.On("IsSysAdmin").Return(false).Once()
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
suite.Security.On("GetUsername").Return("username").Once()
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
@ -141,8 +141,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
{
// default scanner required
suite.Security.On("IsAuthenticated").Return(true).Once()
suite.Security.On("IsSysAdmin").Return(true).Once()
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, nil).Once()
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
@ -153,8 +152,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
{
// default scanner required failed
suite.Security.On("IsAuthenticated").Return(true).Once()
suite.Security.On("IsSysAdmin").Return(true).Once()
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, fmt.Errorf("failed")).Once()
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
@ -167,7 +165,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
func (suite *ScanAllTestSuite) TestGetLatestScanAllMetrics() {
times := 3
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("IsSysAdmin").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
{
@ -205,7 +203,7 @@ func (suite *ScanAllTestSuite) TestGetLatestScanAllMetrics() {
func (suite *ScanAllTestSuite) TestGetLatestScheduledScanAllMetrics() {
times := 3
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("IsSysAdmin").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
{
@ -243,7 +241,7 @@ func (suite *ScanAllTestSuite) TestGetLatestScheduledScanAllMetrics() {
func (suite *ScanAllTestSuite) TestCreateScanAllSchedule() {
times := 11
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("IsSysAdmin").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
{
@ -356,7 +354,7 @@ func (suite *ScanAllTestSuite) TestCreateScanAllSchedule() {
func (suite *ScanAllTestSuite) TestUpdateScanAllSchedule() {
times := 11
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("IsSysAdmin").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
{
@ -472,7 +470,7 @@ func (suite *ScanAllTestSuite) TestUpdateScanAllSchedule() {
func (suite *ScanAllTestSuite) TestGetScanAllSchedule() {
times := 4
suite.Security.On("IsAuthenticated").Return(true).Times(times)
suite.Security.On("IsSysAdmin").Return(true).Times(times)
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
{

View File

@ -2,6 +2,7 @@ package handler
import (
"context"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/security"
@ -43,7 +44,7 @@ func (s *sysInfoAPI) GetSysteminfoGetcert(ctx context.Context, params systeminfo
}
func (s *sysInfoAPI) GetSysteminfoVolumes(ctx context.Context, params systeminfo.GetSysteminfoVolumesParams) middleware.Responder {
if err := s.RequireSysAdmin(ctx); err != nil {
if err := s.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceSystemVolumes); err != nil {
return s.SendError(ctx, err)
}
c, err := s.ctl.GetCapacity(ctx)