mirror of https://github.com/portainer/portainer
143 lines
5.8 KiB
Go
143 lines
5.8 KiB
Go
|
package proxy
|
||
|
|
||
|
import (
|
||
|
"net/http"
|
||
|
|
||
|
"github.com/portainer/portainer"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// ErrDockerServiceIdentifierNotFound defines an error raised when Portainer is unable to find a service identifier
|
||
|
ErrDockerServiceIdentifierNotFound = portainer.Error("Docker service identifier not found")
|
||
|
serviceIdentifier = "ID"
|
||
|
serviceLabelForStackIdentifier = "com.docker.stack.namespace"
|
||
|
)
|
||
|
|
||
|
// serviceListOperation extracts the response as a JSON array, loop through the service array
|
||
|
// decorate and/or filter the services based on resource controls before rewriting the response
|
||
|
func serviceListOperation(request *http.Request, response *http.Response, executor *operationExecutor) error {
|
||
|
var err error
|
||
|
// ServiceList response is a JSON array
|
||
|
// https://docs.docker.com/engine/api/v1.28/#operation/ServiceList
|
||
|
responseArray, err := getResponseAsJSONArray(response)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if executor.operationContext.isAdmin {
|
||
|
responseArray, err = decorateServiceList(responseArray, executor.operationContext.resourceControls)
|
||
|
} else {
|
||
|
responseArray, err = filterServiceList(responseArray, executor.operationContext)
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return rewriteResponse(response, responseArray, http.StatusOK)
|
||
|
}
|
||
|
|
||
|
// serviceInspectOperation extracts the response as a JSON object, verify that the user
|
||
|
// has access to the service based on resource control and either rewrite an access denied response
|
||
|
// or a decorated service.
|
||
|
func serviceInspectOperation(request *http.Request, response *http.Response, executor *operationExecutor) error {
|
||
|
// ServiceInspect response is a JSON object
|
||
|
// https://docs.docker.com/engine/api/v1.28/#operation/ServiceInspect
|
||
|
responseObject, err := getResponseAsJSONOBject(response)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if responseObject[serviceIdentifier] == nil {
|
||
|
return ErrDockerServiceIdentifierNotFound
|
||
|
}
|
||
|
|
||
|
serviceID := responseObject[serviceIdentifier].(string)
|
||
|
responseObject, access := applyResourceAccessControl(responseObject, serviceID, executor.operationContext)
|
||
|
if !access {
|
||
|
return rewriteAccessDeniedResponse(response)
|
||
|
}
|
||
|
|
||
|
serviceLabels := extractServiceLabelsFromServiceInspectObject(responseObject)
|
||
|
responseObject, access = applyResourceAccessControlFromLabel(serviceLabels, responseObject, serviceLabelForStackIdentifier, executor.operationContext)
|
||
|
if !access {
|
||
|
return rewriteAccessDeniedResponse(response)
|
||
|
}
|
||
|
|
||
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||
|
}
|
||
|
|
||
|
// extractServiceLabelsFromServiceInspectObject retrieve the Labels of the service if present.
|
||
|
// Service schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ServiceInspect
|
||
|
func extractServiceLabelsFromServiceInspectObject(responseObject map[string]interface{}) map[string]interface{} {
|
||
|
// Labels are stored under Spec.Labels
|
||
|
serviceSpecObject := extractJSONField(responseObject, "Spec")
|
||
|
if serviceSpecObject != nil {
|
||
|
return extractJSONField(serviceSpecObject, "Labels")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// extractServiceLabelsFromServiceListObject retrieve the Labels of the service if present.
|
||
|
// Service schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ServiceList
|
||
|
func extractServiceLabelsFromServiceListObject(responseObject map[string]interface{}) map[string]interface{} {
|
||
|
// Labels are stored under Spec.Labels
|
||
|
serviceSpecObject := extractJSONField(responseObject, "Spec")
|
||
|
if serviceSpecObject != nil {
|
||
|
return extractJSONField(serviceSpecObject, "Labels")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// decorateServiceList loops through all services and decorates any service with an existing resource control.
|
||
|
// Resource controls checks are based on: resource identifier, stack identifier (from label).
|
||
|
// Service object schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ServiceList
|
||
|
func decorateServiceList(serviceData []interface{}, resourceControls []portainer.ResourceControl) ([]interface{}, error) {
|
||
|
decoratedServiceData := make([]interface{}, 0)
|
||
|
|
||
|
for _, service := range serviceData {
|
||
|
|
||
|
serviceObject := service.(map[string]interface{})
|
||
|
if serviceObject[serviceIdentifier] == nil {
|
||
|
return nil, ErrDockerServiceIdentifierNotFound
|
||
|
}
|
||
|
|
||
|
serviceID := serviceObject[serviceIdentifier].(string)
|
||
|
serviceObject = decorateResourceWithAccessControl(serviceObject, serviceID, resourceControls)
|
||
|
|
||
|
serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject)
|
||
|
serviceObject = decorateResourceWithAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, resourceControls)
|
||
|
|
||
|
decoratedServiceData = append(decoratedServiceData, serviceObject)
|
||
|
}
|
||
|
|
||
|
return decoratedServiceData, nil
|
||
|
}
|
||
|
|
||
|
// filterServiceList loops through all services and filters public services (no associated resource control)
|
||
|
// as well as authorized services (access granted to the user based on existing resource control).
|
||
|
// Authorized services are decorated during the process.
|
||
|
// Resource controls checks are based on: resource identifier, stack identifier (from label).
|
||
|
// Service object schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ServiceList
|
||
|
func filterServiceList(serviceData []interface{}, context *restrictedOperationContext) ([]interface{}, error) {
|
||
|
filteredServiceData := make([]interface{}, 0)
|
||
|
|
||
|
for _, service := range serviceData {
|
||
|
serviceObject := service.(map[string]interface{})
|
||
|
if serviceObject[serviceIdentifier] == nil {
|
||
|
return nil, ErrDockerServiceIdentifierNotFound
|
||
|
}
|
||
|
|
||
|
serviceID := serviceObject[serviceIdentifier].(string)
|
||
|
serviceObject, access := applyResourceAccessControl(serviceObject, serviceID, context)
|
||
|
if access {
|
||
|
serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject)
|
||
|
serviceObject, access = applyResourceAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, context)
|
||
|
if access {
|
||
|
filteredServiceData = append(filteredServiceData, serviceObject)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return filteredServiceData, nil
|
||
|
}
|