mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
feat(UAC): change default ownership to admininstrators (#2137)
* #960 feat(UAC): change ownership to admins for externally created ressources * feat(UAC): change ownership to admins for externally created resources Deprecated AdministratorsOnly js and go backend * #960 feat(UAC): remove AdministratorsOnly property and minor GUI fixes Update swagger definition changing AdministratorsOnly to Public * #960 feat(UAC): fix create resource with access control data * #960 feat(UAC): authorization of non-admin users for restricted operations On stacks, containers networks, services , tasks and volumes. * #960 feat(UAC): database migration to version 14 The administrator resources are deleted and Public resources are now managed by admins * #960 feat(UAC): small fixes from PR #2137 * #960 feat(UAC): improve the readability of the source code * feat(UAC) fix displayed ownership for Swarm related resources (#960)
This commit is contained in:
parent
31c2a6d9e7
commit
e1e263d8c8
30 changed files with 206 additions and 179 deletions
19
api/bolt/migrator/migrate_dbversion13.go
Normal file
19
api/bolt/migrator/migrate_dbversion13.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package migrator
|
||||
|
||||
func (m *Migrator) updateResourceControlsToDBVersion14() error {
|
||||
resourceControls, err := m.resourceControlService.ResourceControls()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, resourceControl := range resourceControls {
|
||||
if resourceControl.AdministratorsOnly == true {
|
||||
err = m.resourceControlService.DeleteResourceControl(resourceControl.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -178,5 +178,13 @@ func (m *Migrator) Migrate() error {
|
|||
}
|
||||
}
|
||||
|
||||
// 1.19.2-dev
|
||||
if m.currentDBVersion < 14 {
|
||||
err := m.updateResourceControlsToDBVersion14()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ import (
|
|||
)
|
||||
|
||||
type resourceControlCreatePayload struct {
|
||||
ResourceID string
|
||||
Type string
|
||||
AdministratorsOnly bool
|
||||
Users []int
|
||||
Teams []int
|
||||
SubResourceIDs []string
|
||||
ResourceID string
|
||||
Type string
|
||||
Public bool
|
||||
Users []int
|
||||
Teams []int
|
||||
SubResourceIDs []string
|
||||
}
|
||||
|
||||
func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
|
||||
|
@ -29,8 +29,8 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
|
|||
return portainer.Error("Invalid type")
|
||||
}
|
||||
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -90,12 +90,12 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
|
||||
resourceControl := portainer.ResourceControl{
|
||||
ResourceID: payload.ResourceID,
|
||||
SubResourceIDs: payload.SubResourceIDs,
|
||||
Type: resourceControlType,
|
||||
AdministratorsOnly: payload.AdministratorsOnly,
|
||||
UserAccesses: userAccesses,
|
||||
TeamAccesses: teamAccesses,
|
||||
ResourceID: payload.ResourceID,
|
||||
SubResourceIDs: payload.SubResourceIDs,
|
||||
Type: resourceControlType,
|
||||
Public: payload.Public,
|
||||
UserAccesses: userAccesses,
|
||||
TeamAccesses: teamAccesses,
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
|
|
|
@ -11,14 +11,14 @@ import (
|
|||
)
|
||||
|
||||
type resourceControlUpdatePayload struct {
|
||||
AdministratorsOnly bool
|
||||
Users []int
|
||||
Teams []int
|
||||
Public bool
|
||||
Users []int
|
||||
Teams []int
|
||||
}
|
||||
|
||||
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
|
||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
|
|||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
resourceControl.AdministratorsOnly = payload.AdministratorsOnly
|
||||
resourceControl.Public = payload.Public
|
||||
|
||||
var userAccesses = make([]portainer.UserResourceAccess, 0)
|
||||
for _, v := range payload.Users {
|
||||
|
|
|
@ -48,8 +48,8 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
if !securityContext.IsAdmin {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,10 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
|
|||
}
|
||||
|
||||
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
||||
if !securityContext.IsAdmin && resourceControl == nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
extendedStack.ResourceControl = *resourceControl
|
||||
|
|
|
@ -36,6 +36,10 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
|
|||
}
|
||||
|
||||
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
||||
if !securityContext.IsAdmin && resourceControl == nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
extendedStack.ResourceControl = *resourceControl
|
||||
|
|
|
@ -53,8 +53,8 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
if !securityContext.IsAdmin {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,8 +62,8 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
if resourceControl != nil {
|
||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
if !securityContext.IsAdmin {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package proxy
|
||||
|
||||
import "github.com/portainer/portainer"
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
type (
|
||||
// ExtendedStack represents a stack combined with its associated access control
|
||||
|
@ -15,7 +17,7 @@ type (
|
|||
// It will retrieve an identifier from the labels object. If an identifier exists, it will check for
|
||||
// an existing resource control associated to it.
|
||||
// Returns a decorated object and authorized access (true) when a resource control is found and the user can access the resource.
|
||||
// Returns the original object and authorized access (true) when no resource control is found.
|
||||
// Returns the original object and denied access (false) when no resource control is found.
|
||||
// Returns the original object and denied access (false) when a resource control is found and the user cannot access the resource.
|
||||
func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string]interface{}, labelIdentifier string,
|
||||
context *restrictedOperationContext) (map[string]interface{}, bool) {
|
||||
|
@ -24,32 +26,31 @@ func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string
|
|||
resourceIdentifier := labelsObject[labelIdentifier].(string)
|
||||
return applyResourceAccessControl(resourceObject, resourceIdentifier, context)
|
||||
}
|
||||
return resourceObject, true
|
||||
return resourceObject, false
|
||||
}
|
||||
|
||||
// applyResourceAccessControl returns an optionally decorated object as the first return value and the
|
||||
// access level for the user (granted or denied) as the second return value.
|
||||
// Returns a decorated object and authorized access (true) when a resource control is found to the specified resource
|
||||
// identifier and the user can access the resource.
|
||||
// Returns the original object and authorized access (true) when no resource control is found for the specified
|
||||
// Returns the original object and authorized access (false) when no resource control is found for the specified
|
||||
// resource identifier.
|
||||
// Returns the original object and denied access (false) when a resource control is associated to the resource
|
||||
// and the user cannot access the resource.
|
||||
func applyResourceAccessControl(resourceObject map[string]interface{}, resourceIdentifier string,
|
||||
context *restrictedOperationContext) (map[string]interface{}, bool) {
|
||||
|
||||
authorizedAccess := true
|
||||
|
||||
resourceControl := getResourceControlByResourceID(resourceIdentifier, context.resourceControls)
|
||||
if resourceControl != nil {
|
||||
if context.isAdmin || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) {
|
||||
resourceObject = decorateObject(resourceObject, resourceControl)
|
||||
} else {
|
||||
authorizedAccess = false
|
||||
}
|
||||
if resourceControl == nil {
|
||||
return resourceObject, context.isAdmin
|
||||
}
|
||||
|
||||
return resourceObject, authorizedAccess
|
||||
if context.isAdmin || resourceControl.Public || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) {
|
||||
resourceObject = decorateObject(resourceObject, resourceControl)
|
||||
return resourceObject, true
|
||||
}
|
||||
|
||||
return resourceObject, false
|
||||
}
|
||||
|
||||
// decorateResourceWithAccessControlFromLabel will retrieve an identifier from the labels object. If an identifier exists,
|
||||
|
@ -94,7 +95,7 @@ func canUserAccessResource(userID portainer.UserID, userTeamIDs []portainer.Team
|
|||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return resourceControl.Public
|
||||
}
|
||||
|
||||
func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} {
|
||||
|
@ -123,6 +124,10 @@ func getResourceControlByResourceID(resourceID string, resourceControls []portai
|
|||
|
||||
// CanAccessStack checks if a user can access a stack
|
||||
func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceControl, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
|
||||
if resourceControl == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
userTeamIDs := make([]portainer.TeamID, 0)
|
||||
for _, membership := range memberships {
|
||||
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
||||
|
@ -132,7 +137,7 @@ func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceC
|
|||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return resourceControl.Public
|
||||
}
|
||||
|
||||
// FilterStacks filters stacks based on user role and resource controls.
|
||||
|
@ -149,9 +154,9 @@ func FilterStacks(stacks []portainer.Stack, resourceControls []portainer.Resourc
|
|||
for _, stack := range stacks {
|
||||
extendedStack := ExtendedStack{stack, portainer.ResourceControl{}}
|
||||
resourceControl := getResourceControlByResourceID(stack.Name, resourceControls)
|
||||
if resourceControl == nil {
|
||||
if resourceControl == nil && isAdmin {
|
||||
filteredStacks = append(filteredStacks, extendedStack)
|
||||
} else if resourceControl != nil && (isAdmin || canUserAccessResource(userID, userTeamIDs, resourceControl)) {
|
||||
} else if resourceControl != nil && (isAdmin || resourceControl.Public || canUserAccessResource(userID, userTeamIDs, resourceControl)) {
|
||||
extendedStack.ResourceControl = *resourceControl
|
||||
filteredStacks = append(filteredStacks, extendedStack)
|
||||
}
|
||||
|
|
|
@ -62,27 +62,27 @@ func containerInspectOperation(response *http.Response, executor *operationExecu
|
|||
|
||||
containerID := responseObject[containerIdentifier].(string)
|
||||
responseObject, access := applyResourceAccessControl(responseObject, containerID, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
containerLabels := extractContainerLabelsFromContainerInspectObject(responseObject)
|
||||
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForServiceIdentifier, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForSwarmStackIdentifier, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForComposeStackIdentifier, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
}
|
||||
|
||||
// extractContainerLabelsFromContainerInspectObject retrieve the Labels of the container if present.
|
||||
|
@ -148,19 +148,20 @@ func filterContainerList(containerData []interface{}, context *restrictedOperati
|
|||
|
||||
containerID := containerObject[containerIdentifier].(string)
|
||||
containerObject, access := applyResourceAccessControl(containerObject, containerID, context)
|
||||
if access {
|
||||
if !access {
|
||||
containerLabels := extractContainerLabelsFromContainerListObject(containerObject)
|
||||
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForComposeStackIdentifier, context)
|
||||
if access {
|
||||
if !access {
|
||||
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForServiceIdentifier, context)
|
||||
if access {
|
||||
if !access {
|
||||
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForSwarmStackIdentifier, context)
|
||||
if access {
|
||||
filteredContainerData = append(filteredContainerData, containerObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if access {
|
||||
filteredContainerData = append(filteredContainerData, containerObject)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredContainerData, nil
|
||||
|
|
|
@ -53,17 +53,17 @@ func networkInspectOperation(response *http.Response, executor *operationExecuto
|
|||
|
||||
networkID := responseObject[networkIdentifier].(string)
|
||||
responseObject, access := applyResourceAccessControl(responseObject, networkID, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
networkLabels := extractNetworkLabelsFromNetworkInspectObject(responseObject)
|
||||
responseObject, access = applyResourceAccessControlFromLabel(networkLabels, responseObject, networkLabelForStackIdentifier, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
}
|
||||
|
||||
// extractNetworkLabelsFromNetworkInspectObject retrieve the Labels of the network if present.
|
||||
|
@ -121,12 +121,13 @@ func filterNetworkList(networkData []interface{}, context *restrictedOperationCo
|
|||
|
||||
networkID := networkObject[networkIdentifier].(string)
|
||||
networkObject, access := applyResourceAccessControl(networkObject, networkID, context)
|
||||
if access {
|
||||
if !access {
|
||||
networkLabels := extractNetworkLabelsFromNetworkListObject(networkObject)
|
||||
networkObject, access = applyResourceAccessControlFromLabel(networkLabels, networkObject, networkLabelForStackIdentifier, context)
|
||||
if access {
|
||||
filteredNetworkData = append(filteredNetworkData, networkObject)
|
||||
}
|
||||
}
|
||||
|
||||
if access {
|
||||
filteredNetworkData = append(filteredNetworkData, networkObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,17 +53,17 @@ func serviceInspectOperation(response *http.Response, executor *operationExecuto
|
|||
|
||||
serviceID := responseObject[serviceIdentifier].(string)
|
||||
responseObject, access := applyResourceAccessControl(responseObject, serviceID, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
serviceLabels := extractServiceLabelsFromServiceInspectObject(responseObject)
|
||||
responseObject, access = applyResourceAccessControlFromLabel(serviceLabels, responseObject, serviceLabelForStackIdentifier, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
}
|
||||
|
||||
// extractServiceLabelsFromServiceInspectObject retrieve the Labels of the service if present.
|
||||
|
@ -129,12 +129,13 @@ func filterServiceList(serviceData []interface{}, context *restrictedOperationCo
|
|||
|
||||
serviceID := serviceObject[serviceIdentifier].(string)
|
||||
serviceObject, access := applyResourceAccessControl(serviceObject, serviceID, context)
|
||||
if access {
|
||||
if !access {
|
||||
serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject)
|
||||
serviceObject, access = applyResourceAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, context)
|
||||
if access {
|
||||
filteredServiceData = append(filteredServiceData, serviceObject)
|
||||
}
|
||||
}
|
||||
|
||||
if access {
|
||||
filteredServiceData = append(filteredServiceData, serviceObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,12 +65,13 @@ func filterTaskList(taskData []interface{}, context *restrictedOperationContext)
|
|||
|
||||
serviceID := taskObject[taskServiceIdentifier].(string)
|
||||
taskObject, access := applyResourceAccessControl(taskObject, serviceID, context)
|
||||
if access {
|
||||
if !access {
|
||||
taskLabels := extractTaskLabelsFromTaskListObject(taskObject)
|
||||
taskObject, access = applyResourceAccessControlFromLabel(taskLabels, taskObject, taskLabelForStackIdentifier, context)
|
||||
if access {
|
||||
filteredTaskData = append(filteredTaskData, taskObject)
|
||||
}
|
||||
}
|
||||
|
||||
if access {
|
||||
filteredTaskData = append(filteredTaskData, taskObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,17 +62,17 @@ func volumeInspectOperation(response *http.Response, executor *operationExecutor
|
|||
|
||||
volumeID := responseObject[volumeIdentifier].(string)
|
||||
responseObject, access := applyResourceAccessControl(responseObject, volumeID, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
volumeLabels := extractVolumeLabelsFromVolumeInspectObject(responseObject)
|
||||
responseObject, access = applyResourceAccessControlFromLabel(volumeLabels, responseObject, volumeLabelForStackIdentifier, executor.operationContext)
|
||||
if !access {
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
if access {
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
}
|
||||
|
||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||
return rewriteAccessDeniedResponse(response)
|
||||
}
|
||||
|
||||
// extractVolumeLabelsFromVolumeInspectObject retrieve the Labels of the volume if present.
|
||||
|
@ -130,12 +130,13 @@ func filterVolumeList(volumeData []interface{}, context *restrictedOperationCont
|
|||
|
||||
volumeID := volumeObject[volumeIdentifier].(string)
|
||||
volumeObject, access := applyResourceAccessControl(volumeObject, volumeID, context)
|
||||
if access {
|
||||
if !access {
|
||||
volumeLabels := extractVolumeLabelsFromVolumeListObject(volumeObject)
|
||||
volumeObject, access = applyResourceAccessControlFromLabel(volumeLabels, volumeObject, volumeLabelForStackIdentifier, context)
|
||||
if access {
|
||||
filteredVolumeData = append(filteredVolumeData, volumeObject)
|
||||
}
|
||||
}
|
||||
|
||||
if access {
|
||||
filteredVolumeData = append(filteredVolumeData, volumeObject)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
package security
|
||||
|
||||
import "github.com/portainer/portainer"
|
||||
import (
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
// AuthorizedResourceControlDeletion ensure that the user can delete a resource control object.
|
||||
// A non-administrator user cannot delete a resource control where:
|
||||
// * the AdministratorsOnly flag is set
|
||||
// * the Public flag is false
|
||||
// * he is not one of the users in the user accesses
|
||||
// * he is not a member of any team within the team accesses
|
||||
func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
||||
if context.IsAdmin {
|
||||
if context.IsAdmin || resourceControl.Public {
|
||||
return true
|
||||
}
|
||||
|
||||
if resourceControl.AdministratorsOnly {
|
||||
return false
|
||||
}
|
||||
|
||||
userAccessesCount := len(resourceControl.UserAccesses)
|
||||
teamAccessesCount := len(resourceControl.TeamAccesses)
|
||||
|
||||
|
@ -42,39 +40,25 @@ func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceContro
|
|||
|
||||
// AuthorizedResourceControlAccess checks whether the user can alter an existing resource control.
|
||||
func AuthorizedResourceControlAccess(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
||||
if context.IsAdmin {
|
||||
if context.IsAdmin || resourceControl.Public {
|
||||
return true
|
||||
}
|
||||
|
||||
if resourceControl.AdministratorsOnly {
|
||||
return false
|
||||
}
|
||||
|
||||
authorizedTeamAccess := false
|
||||
for _, access := range resourceControl.TeamAccesses {
|
||||
for _, membership := range context.UserMemberships {
|
||||
if membership.TeamID == access.TeamID {
|
||||
authorizedTeamAccess = true
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !authorizedTeamAccess {
|
||||
return false
|
||||
}
|
||||
|
||||
authorizedUserAccess := false
|
||||
for _, access := range resourceControl.UserAccesses {
|
||||
if context.UserID == access.UserID {
|
||||
authorizedUserAccess = true
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !authorizedUserAccess {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthorizedResourceControlUpdate ensure that the user can update a resource control object.
|
||||
|
@ -92,20 +76,16 @@ func AuthorizedResourceControlUpdate(resourceControl *portainer.ResourceControl,
|
|||
|
||||
// AuthorizedResourceControlCreation ensure that the user can create a resource control object.
|
||||
// A non-administrator user cannot create a resource control where:
|
||||
// * the AdministratorsOnly flag is set
|
||||
// * the Public flag is set false
|
||||
// * he wants to create a resource control without any user/team accesses
|
||||
// * he wants to add more than one user in the user accesses
|
||||
// * he wants tp add a user in the user accesses that is not corresponding to its id
|
||||
// * he wants to add a team he is not a member of
|
||||
func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
||||
if context.IsAdmin {
|
||||
if context.IsAdmin || resourceControl.Public {
|
||||
return true
|
||||
}
|
||||
|
||||
if resourceControl.AdministratorsOnly {
|
||||
return false
|
||||
}
|
||||
|
||||
userAccessesCount := len(resourceControl.UserAccesses)
|
||||
teamAccessesCount := len(resourceControl.TeamAccesses)
|
||||
|
||||
|
@ -126,19 +106,15 @@ func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceContro
|
|||
|
||||
if teamAccessesCount > 0 {
|
||||
for _, access := range resourceControl.TeamAccesses {
|
||||
isMember := false
|
||||
for _, membership := range context.UserMemberships {
|
||||
if membership.TeamID == access.TeamID {
|
||||
isMember = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !isMember {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthorizedTeamManagement ensure that access to the management of the specified team is granted.
|
||||
|
|
|
@ -274,18 +274,21 @@ type (
|
|||
|
||||
// ResourceControl represent a reference to a Docker resource with specific access controls
|
||||
ResourceControl struct {
|
||||
ID ResourceControlID `json:"Id"`
|
||||
ResourceID string `json:"ResourceId"`
|
||||
SubResourceIDs []string `json:"SubResourceIds"`
|
||||
Type ResourceControlType `json:"Type"`
|
||||
AdministratorsOnly bool `json:"AdministratorsOnly"`
|
||||
UserAccesses []UserResourceAccess `json:"UserAccesses"`
|
||||
TeamAccesses []TeamResourceAccess `json:"TeamAccesses"`
|
||||
ID ResourceControlID `json:"Id"`
|
||||
ResourceID string `json:"ResourceId"`
|
||||
SubResourceIDs []string `json:"SubResourceIds"`
|
||||
Type ResourceControlType `json:"Type"`
|
||||
UserAccesses []UserResourceAccess `json:"UserAccesses"`
|
||||
TeamAccesses []TeamResourceAccess `json:"TeamAccesses"`
|
||||
Public bool `json:"Public"`
|
||||
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 2
|
||||
OwnerID UserID `json:"OwnerId,omitempty"`
|
||||
AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"`
|
||||
|
||||
// Deprecated in DBVersion == 14
|
||||
AdministratorsOnly bool `json:"AdministratorsOnly,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceControlType represents the type of resource associated to the resource control (volume, container, service...).
|
||||
|
@ -613,7 +616,7 @@ const (
|
|||
// APIVersion is the version number of the Portainer API.
|
||||
APIVersion = "1.19.2-dev"
|
||||
// DBVersion is the version number of the Portainer database.
|
||||
DBVersion = 13
|
||||
DBVersion = 14
|
||||
// PortainerAgentHeader represents the name of the header available in any agent response
|
||||
PortainerAgentHeader = "Portainer-Agent"
|
||||
// PortainerAgentTargetHeader represent the name of the header containing the target node name.
|
||||
|
|
|
@ -3268,11 +3268,10 @@ definitions:
|
|||
example: "container"
|
||||
description: "Type of Docker resource. Valid values are: container, volume\
|
||||
\ service, secret, config or stack"
|
||||
AdministratorsOnly:
|
||||
Public:
|
||||
type: "boolean"
|
||||
example: true
|
||||
description: "Restrict access to the associated resource to administrators\
|
||||
\ only"
|
||||
description: "Permit access to the associated resource to any user"
|
||||
Users:
|
||||
type: "array"
|
||||
description: "List of user identifiers with access to the associated resource"
|
||||
|
@ -3491,11 +3490,10 @@ definitions:
|
|||
example: "container"
|
||||
description: "Type of Docker resource. Valid values are: container, volume\
|
||||
\ service, secret, config or stack"
|
||||
AdministratorsOnly:
|
||||
Public:
|
||||
type: "boolean"
|
||||
example: true
|
||||
description: "Restrict access to the associated resource to administrators\
|
||||
\ only"
|
||||
description: "Permit access to the associated resource to any user"
|
||||
Users:
|
||||
type: "array"
|
||||
description: "List of user identifiers with access to the associated resource"
|
||||
|
@ -3520,11 +3518,10 @@ definitions:
|
|||
ResourceControlUpdateRequest:
|
||||
type: "object"
|
||||
properties:
|
||||
AdministratorsOnly:
|
||||
Public:
|
||||
type: "boolean"
|
||||
example: false
|
||||
description: "Restrict access to the associated resource to administrators\
|
||||
\ only"
|
||||
description: "Permit access to the associated resource to any user"
|
||||
Users:
|
||||
type: "array"
|
||||
description: "List of user identifiers with access to the associated resource"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue