mirror of https://github.com/portainer/portainer
feat(api): filter tasks based on service UAC (#1207)
parent
dd0fc6fab8
commit
912ebf4672
|
@ -160,3 +160,26 @@ func filterSecretList(secretData []interface{}, resourceControls []portainer.Res
|
||||||
|
|
||||||
return filteredSecretData, nil
|
return filteredSecretData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterTaskList loops through all tasks, filters tasks without any resource control (public resources) or with
|
||||||
|
// any resource control giving access to the user based on the associated service identifier.
|
||||||
|
// Task object schema reference: https://docs.docker.com/engine/api/v1.28/#operation/TaskList
|
||||||
|
func filterTaskList(taskData []interface{}, resourceControls []portainer.ResourceControl, userID portainer.UserID, userTeamIDs []portainer.TeamID) ([]interface{}, error) {
|
||||||
|
filteredTaskData := make([]interface{}, 0)
|
||||||
|
|
||||||
|
for _, task := range taskData {
|
||||||
|
taskObject := task.(map[string]interface{})
|
||||||
|
if taskObject[taskServiceIdentifier] == nil {
|
||||||
|
return nil, ErrDockerTaskServiceIdentifierNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceID := taskObject[taskServiceIdentifier].(string)
|
||||||
|
|
||||||
|
resourceControl := getResourceControlByResourceID(serviceID, resourceControls)
|
||||||
|
if resourceControl == nil || (resourceControl != nil && canUserAccessResource(userID, userTeamIDs, resourceControl)) {
|
||||||
|
filteredTaskData = append(filteredTaskData, taskObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredTaskData, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrDockerTaskServiceIdentifierNotFound defines an error raised when Portainer is unable to find the service identifier associated to a task
|
||||||
|
ErrDockerTaskServiceIdentifierNotFound = portainer.Error("Docker task service identifier not found")
|
||||||
|
taskServiceIdentifier = "ServiceID"
|
||||||
|
)
|
||||||
|
|
||||||
|
// taskListOperation extracts the response as a JSON object, loop through the tasks array
|
||||||
|
// and filter the tasks based on resource controls before rewriting the response
|
||||||
|
func taskListOperation(request *http.Request, response *http.Response, executor *operationExecutor) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// TaskList response is a JSON array
|
||||||
|
// https://docs.docker.com/engine/api/v1.28/#operation/TaskList
|
||||||
|
responseArray, err := getResponseAsJSONArray(response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !executor.operationContext.isAdmin {
|
||||||
|
responseArray, err = filterTaskList(responseArray, executor.operationContext.resourceControls,
|
||||||
|
executor.operationContext.userID, executor.operationContext.userTeamIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewriteResponse(response, responseArray, http.StatusOK)
|
||||||
|
}
|
|
@ -68,6 +68,8 @@ func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Respon
|
||||||
return p.proxySwarmRequest(request)
|
return p.proxySwarmRequest(request)
|
||||||
case strings.HasPrefix(path, "/nodes"):
|
case strings.HasPrefix(path, "/nodes"):
|
||||||
return p.proxyNodeRequest(request)
|
return p.proxyNodeRequest(request)
|
||||||
|
case strings.HasPrefix(path, "/tasks"):
|
||||||
|
return p.proxyTaskRequest(request)
|
||||||
default:
|
default:
|
||||||
return p.executeDockerRequest(request)
|
return p.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
|
@ -203,6 +205,16 @@ func (p *proxyTransport) proxySwarmRequest(request *http.Request) (*http.Respons
|
||||||
return p.administratorOperation(request)
|
return p.administratorOperation(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *proxyTransport) proxyTaskRequest(request *http.Request) (*http.Response, error) {
|
||||||
|
switch requestPath := request.URL.Path; requestPath {
|
||||||
|
case "/tasks":
|
||||||
|
return p.rewriteOperation(request, taskListOperation)
|
||||||
|
default:
|
||||||
|
// assume /tasks/{id}
|
||||||
|
return p.executeDockerRequest(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// restrictedOperation ensures that the current user has the required authorizations
|
// restrictedOperation ensures that the current user has the required authorizations
|
||||||
// before executing the original request.
|
// before executing the original request.
|
||||||
func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID string) (*http.Response, error) {
|
func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID string) (*http.Response, error) {
|
||||||
|
|
Loading…
Reference in New Issue