feat(stacks): prevent external stack removal by a non-administrator user (#3800)

* fix(stacks): prevent external stacks removal by non admin

* feat(stacks): add RBAC checks for external stack removals

Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com>
pull/3826/head
Anthony Lapenna 2020-05-13 15:37:35 +12:00 committed by GitHub
parent 29c0584454
commit 9dcd223134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 15 deletions

View File

@ -9,7 +9,7 @@ import (
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
) )
// DELETE request on /api/stacks/:id?external=<external>&endpointId=<endpointId> // DELETE request on /api/stacks/:id?external=<external>&endpointId=<endpointId>
@ -21,9 +21,14 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusBadRequest, "Invalid stack identifier route variable", err} return &httperror.HandlerError{http.StatusBadRequest, "Invalid stack identifier route variable", err}
} }
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
externalStack, _ := request.RetrieveBooleanQueryParameter(r, "external", true) externalStack, _ := request.RetrieveBooleanQueryParameter(r, "external", true)
if externalStack { if externalStack {
return handler.deleteExternalStack(r, w, stackID) return handler.deleteExternalStack(r, w, stackID, securityContext)
} }
id, err := strconv.Atoi(stackID) id, err := strconv.Atoi(stackID)
@ -68,11 +73,6 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
} }
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl) access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl)
if err != nil { if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate stack access", err} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate stack access", err}
@ -106,7 +106,38 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
return response.Empty(w) return response.Empty(w)
} }
func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWriter, stackName string) *httperror.HandlerError { func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWriter, stackName string, securityContext *security.RestrictedRequestContext) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
user, err := handler.UserService.User(securityContext.UserID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
}
rbacExtension, err := handler.ExtensionService.Extension(portainer.RBACExtension)
if err != nil && err != portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify if RBAC extension is loaded", err}
}
endpointResourceAccess := false
_, ok := user.EndpointAuthorizations[portainer.EndpointID(endpointID)][portainer.EndpointResourcesAccess]
if ok {
endpointResourceAccess = true
}
if rbacExtension != nil {
if !securityContext.IsAdmin && !endpointResourceAccess {
return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", portainer.ErrUnauthorized}
}
} else {
if !securityContext.IsAdmin {
return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", portainer.ErrUnauthorized}
}
}
stack, err := handler.StackService.StackByName(stackName) stack, err := handler.StackService.StackByName(stackName)
if err != nil && err != portainer.ErrObjectNotFound { if err != nil && err != portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for stack existence inside the database", err} return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for stack existence inside the database", err}
@ -115,11 +146,6 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit
return &httperror.HandlerError{http.StatusBadRequest, "A stack with this name exists inside the database. Cannot use external delete method", portainer.ErrStackNotExternal} return &httperror.HandlerError{http.StatusBadRequest, "A stack with this name exists inside the database. Cannot use external delete method", portainer.ErrStackNotExternal}
} }
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID)) endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
if err == portainer.ErrObjectNotFound { if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the stack inside the database", err} return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the stack inside the database", err}

View File

@ -2,17 +2,24 @@ angular.module('portainer.app').controller('StacksDatatableController', [
'$scope', '$scope',
'$controller', '$controller',
'DatatableService', 'DatatableService',
function ($scope, $controller, DatatableService) { 'Authentication',
function ($scope, $controller, DatatableService, Authentication) {
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
/** /**
* Do not allow external items * Do not allow external items
*/ */
this.allowSelection = function (item) { this.allowSelection = function (item) {
return !(item.External && item.Type === 2); if (item.External && item.Type === 2) {
return false;
}
return !(item.External && !this.isAdmin && !this.isEndpointAdmin);
}; };
this.$onInit = function () { this.$onInit = function () {
this.isAdmin = Authentication.isAdmin();
this.isEndpointAdmin = Authentication.hasAuthorizations(['EndpointResourcesAccess']);
this.setDefaults(); this.setDefaults();
this.prepareTableFromDataset(); this.prepareTableFromDataset();