mirror of
https://github.com/harness/drone.git
synced 2025-05-05 15:32:56 +00:00
234 lines
6.6 KiB
Go
234 lines
6.6 KiB
Go
// Copyright 2023 Harness, Inc.
|
|
//
|
|
// 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 authz
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/harness/gitness/app/auth"
|
|
"github.com/harness/gitness/app/paths"
|
|
"github.com/harness/gitness/app/services/publicaccess"
|
|
"github.com/harness/gitness/app/store"
|
|
"github.com/harness/gitness/types"
|
|
"github.com/harness/gitness/types/enum"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
var _ Authorizer = (*MembershipAuthorizer)(nil)
|
|
|
|
type MembershipAuthorizer struct {
|
|
permissionCache PermissionCache
|
|
spaceStore store.SpaceStore
|
|
publicAccess publicaccess.Service
|
|
}
|
|
|
|
func NewMembershipAuthorizer(
|
|
permissionCache PermissionCache,
|
|
spaceStore store.SpaceStore,
|
|
publicAccess publicaccess.Service,
|
|
) *MembershipAuthorizer {
|
|
return &MembershipAuthorizer{
|
|
permissionCache: permissionCache,
|
|
spaceStore: spaceStore,
|
|
publicAccess: publicAccess,
|
|
}
|
|
}
|
|
|
|
func (a *MembershipAuthorizer) Check(
|
|
ctx context.Context,
|
|
session *auth.Session,
|
|
scope *types.Scope,
|
|
resource *types.Resource,
|
|
permission enum.Permission,
|
|
) (bool, error) {
|
|
publicAccessAllowed, err := CheckPublicAccess(ctx, a.publicAccess, scope, resource, permission)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to check public access: %w", err)
|
|
}
|
|
|
|
if publicAccessAllowed {
|
|
return true, nil
|
|
}
|
|
|
|
log.Ctx(ctx).Debug().Msgf(
|
|
"[MembershipAuthorizer] %s with id '%d' requests %s for %s '%s' in scope %#v with metadata %#v",
|
|
session.Principal.Type,
|
|
session.Principal.ID,
|
|
permission,
|
|
resource.Type,
|
|
resource.Identifier,
|
|
scope,
|
|
session.Metadata,
|
|
)
|
|
|
|
if session.Principal.Admin {
|
|
return true, nil // system admin can call any API
|
|
}
|
|
|
|
var spacePath string
|
|
|
|
//nolint:exhaustive // we want to fail on anything else
|
|
switch resource.Type {
|
|
case enum.ResourceTypeSpace:
|
|
spacePath = paths.Concatenate(scope.SpacePath, resource.Identifier)
|
|
|
|
case enum.ResourceTypeRepo:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeServiceAccount:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypePipeline:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeSecret:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeConnector:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeTemplate:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeGitspace:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeInfraProvider:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeRegistry:
|
|
spacePath = scope.SpacePath
|
|
|
|
case enum.ResourceTypeUser:
|
|
// a user is allowed to edit themselves
|
|
if resource.Identifier == session.Principal.UID &&
|
|
permission == enum.PermissionUserEdit {
|
|
return true, nil
|
|
}
|
|
|
|
// user can see all other users in the system.
|
|
if permission == enum.PermissionUserView {
|
|
return true, nil
|
|
}
|
|
|
|
// everything else is reserved for admins only (like operations on users other than yourself, or setting admin)
|
|
return false, nil
|
|
|
|
// Service operations aren't exposed to users
|
|
case enum.ResourceTypeService:
|
|
return false, nil
|
|
|
|
default:
|
|
return false, nil
|
|
}
|
|
|
|
// ephemeral membership overrides any other space memberships of the principal
|
|
if membershipMetadata, ok := session.Metadata.(*auth.MembershipMetadata); ok {
|
|
return a.checkWithMembershipMetadata(ctx, membershipMetadata, spacePath, permission)
|
|
}
|
|
|
|
// accessPermissionMetadata contains the access permissions of per space
|
|
if accessPermissionMetadata, ok := session.Metadata.(*auth.AccessPermissionMetadata); ok {
|
|
return a.checkWithAccessPermissionMetadata(ctx, accessPermissionMetadata, spacePath, permission)
|
|
}
|
|
|
|
// ensure we aren't bypassing unknown metadata with impact on authorization
|
|
if session.Metadata != nil && session.Metadata.ImpactsAuthorization() {
|
|
return false, fmt.Errorf("session contains unknown metadata that impacts authorization: %T", session.Metadata)
|
|
}
|
|
|
|
return a.permissionCache.Get(
|
|
ctx, PermissionCacheKey{
|
|
PrincipalID: session.Principal.ID,
|
|
SpaceRef: spacePath,
|
|
Permission: permission,
|
|
},
|
|
)
|
|
}
|
|
|
|
func (a *MembershipAuthorizer) CheckAll(
|
|
ctx context.Context, session *auth.Session,
|
|
permissionChecks ...types.PermissionCheck,
|
|
) (bool, error) {
|
|
for i := range permissionChecks {
|
|
p := permissionChecks[i]
|
|
if _, err := a.Check(ctx, session, &p.Scope, &p.Resource, p.Permission); err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// checkWithMembershipMetadata checks access using the ephemeral membership provided in the metadata.
|
|
func (a *MembershipAuthorizer) checkWithMembershipMetadata(
|
|
ctx context.Context,
|
|
membershipMetadata *auth.MembershipMetadata,
|
|
requestedSpacePath string,
|
|
requestedPermission enum.Permission,
|
|
) (bool, error) {
|
|
space, err := a.spaceStore.Find(ctx, membershipMetadata.SpaceID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to find space: %w", err)
|
|
}
|
|
|
|
if !paths.IsAncesterOf(space.Path, requestedSpacePath) {
|
|
return false, fmt.Errorf(
|
|
"requested permission scope '%s' is outside of ephemeral membership scope '%s'",
|
|
requestedSpacePath,
|
|
space.Path,
|
|
)
|
|
}
|
|
|
|
if !roleHasPermission(membershipMetadata.Role, requestedPermission) {
|
|
return false, fmt.Errorf(
|
|
"requested permission '%s' is outside of ephemeral membership role '%s'",
|
|
requestedPermission,
|
|
membershipMetadata.Role,
|
|
)
|
|
}
|
|
|
|
// access is granted by ephemeral membership
|
|
return true, nil
|
|
}
|
|
|
|
// checkWithAccessPermissionMetadata checks access using the ephemeral membership provided in the metadata.
|
|
func (a *MembershipAuthorizer) checkWithAccessPermissionMetadata(
|
|
ctx context.Context,
|
|
accessPermissionMetadata *auth.AccessPermissionMetadata,
|
|
requestedSpacePath string,
|
|
requestedPermission enum.Permission,
|
|
) (bool, error) {
|
|
space, err := a.spaceStore.FindByRef(ctx, requestedSpacePath)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to find space by ref: %w", err)
|
|
}
|
|
|
|
if accessPermissionMetadata.AccessPermissions.Permissions == nil {
|
|
return false, fmt.Errorf("no %s permission provided", requestedPermission)
|
|
}
|
|
|
|
for _, accessPermission := range accessPermissionMetadata.AccessPermissions.Permissions {
|
|
if space.ID == accessPermission.SpaceID && slices.Contains(accessPermission.Permissions, requestedPermission) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, fmt.Errorf("no %s permission provided", requestedPermission)
|
|
}
|