From 340830d121d8b4763ff3d73fa05fef54a89e3a76 Mon Sep 17 00:00:00 2001 From: andres-portainer <91705312+andres-portainer@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:19:46 -0300 Subject: [PATCH] chore(docker): clean up the code EE-7325 (#11997) --- .../proxy/factory/docker/access_control.go | 169 +++++++++--------- api/http/proxy/factory/docker/build.go | 7 +- api/http/proxy/factory/docker/containers.go | 37 ++-- api/http/proxy/factory/docker/portainer.go | 3 + api/http/proxy/factory/docker/registry.go | 45 ++--- api/http/proxy/factory/docker/services.go | 48 ++--- api/http/proxy/factory/docker/transport.go | 157 ++++++++-------- api/http/proxy/factory/docker/volumes.go | 68 +++---- 8 files changed, 280 insertions(+), 254 deletions(-) diff --git a/api/http/proxy/factory/docker/access_control.go b/api/http/proxy/factory/docker/access_control.go index 26c5c435d..e945d38da 100644 --- a/api/http/proxy/factory/docker/access_control.go +++ b/api/http/proxy/factory/docker/access_control.go @@ -7,6 +7,7 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/proxy/factory/utils" "github.com/portainer/portainer/api/internal/authorization" + "github.com/portainer/portainer/api/slicesx" "github.com/portainer/portainer/api/stacks/stackutils" "github.com/rs/zerolog/log" @@ -23,7 +24,8 @@ const ( type ( resourceLabelsObjectSelector func(map[string]any) map[string]any - resourceOperationParameters struct { + + resourceOperationParameters struct { resourceIdentifierAttribute string resourceType portainer.ResourceControlType labelsObjectSelector resourceLabelsObjectSelector @@ -31,28 +33,18 @@ type ( ) func getUniqueElements(items string) []string { - result := []string{} - seen := make(map[string]struct{}) - for _, item := range strings.Split(items, ",") { - v := strings.TrimSpace(item) - if v == "" { - continue - } - if _, ok := seen[v]; !ok { - result = append(result, v) - seen[v] = struct{}{} - } - } + xs := strings.Split(items, ",") + xs = slicesx.Map(xs, strings.TrimSpace) + xs = slicesx.Filter(xs, func(x string) bool { return len(x) > 0 }) - return result + return slicesx.Unique(xs) } func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject map[string]any, resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) { if labelsObject[resourceLabelForPortainerPublicResourceControl] != nil { resourceControl := authorization.NewPublicResourceControl(resourceID, resourceType) - err := transport.dataStore.ResourceControl().Create(resourceControl) - if err != nil { + if err := transport.dataStore.ResourceControl().Create(resourceControl); err != nil { return nil, err } @@ -61,6 +53,7 @@ func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject m teamNames := make([]string, 0) userNames := make([]string, 0) + if labelsObject[resourceLabelForPortainerTeamResourceControl] != nil { concatenatedTeamNames := labelsObject[resourceLabelForPortainerTeamResourceControl].(string) teamNames = getUniqueElements(concatenatedTeamNames) @@ -71,48 +64,48 @@ func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject m userNames = getUniqueElements(concatenatedUserNames) } - if len(teamNames) > 0 || len(userNames) > 0 { - teamIDs := make([]portainer.TeamID, 0) - userIDs := make([]portainer.UserID, 0) - - for _, name := range teamNames { - team, err := transport.dataStore.Team().TeamByName(name) - if err != nil { - log.Warn(). - Str("name", name). - Str("resource_id", resourceID). - Msg("unknown team name in access control label, ignoring access control rule for this team") - - continue - } - - teamIDs = append(teamIDs, team.ID) - } - - for _, name := range userNames { - user, err := transport.dataStore.User().UserByUsername(name) - if err != nil { - log.Warn(). - Str("name", name). - Str("resource_id", resourceID). - Msg("unknown user name in access control label, ignoring access control rule for this user") - - continue - } - - userIDs = append(userIDs, user.ID) - } - - resourceControl := authorization.NewRestrictedResourceControl(resourceID, resourceType, userIDs, teamIDs) - - if err := transport.dataStore.ResourceControl().Create(resourceControl); err != nil { - return nil, err - } - - return resourceControl, nil + if len(teamNames) == 0 && len(userNames) == 0 { + return nil, nil } - return nil, nil + teamIDs := make([]portainer.TeamID, 0) + userIDs := make([]portainer.UserID, 0) + + for _, name := range teamNames { + team, err := transport.dataStore.Team().TeamByName(name) + if err != nil { + log.Warn(). + Str("name", name). + Str("resource_id", resourceID). + Msg("unknown team name in access control label, ignoring access control rule for this team") + + continue + } + + teamIDs = append(teamIDs, team.ID) + } + + for _, name := range userNames { + user, err := transport.dataStore.User().UserByUsername(name) + if err != nil { + log.Warn(). + Str("name", name). + Str("resource_id", resourceID). + Msg("unknown user name in access control label, ignoring access control rule for this user") + + continue + } + + userIDs = append(userIDs, user.ID) + } + + resourceControl := authorization.NewRestrictedResourceControl(resourceID, resourceType, userIDs, teamIDs) + + if err := transport.dataStore.ResourceControl().Create(resourceControl); err != nil { + return nil, err + } + + return resourceControl, nil } func (transport *Transport) createPrivateResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType, userID portainer.UserID) (*portainer.ResourceControl, error) { @@ -298,40 +291,40 @@ func (transport *Transport) findResourceControl(resourceIdentifier string, resou return resourceControl, nil } - if resourceLabelsObject != nil { - if resourceLabelsObject[resourceLabelForDockerServiceID] != nil { - inheritedServiceIdentifier := resourceLabelsObject[resourceLabelForDockerServiceID].(string) - resourceControl = authorization.GetResourceControlByResourceIDAndType(inheritedServiceIdentifier, portainer.ServiceResourceControl, resourceControls) - - if resourceControl != nil { - return resourceControl, nil - } - } - - if resourceLabelsObject[resourceLabelForDockerSwarmStackName] != nil { - stackName := resourceLabelsObject[resourceLabelForDockerSwarmStackName].(string) - stackResourceID := stackutils.ResourceControlID(transport.endpoint.ID, stackName) - resourceControl = authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls) - - if resourceControl != nil { - return resourceControl, nil - } - } - - if resourceLabelsObject[resourceLabelForDockerComposeStackName] != nil { - stackName := resourceLabelsObject[resourceLabelForDockerComposeStackName].(string) - stackResourceID := stackutils.ResourceControlID(transport.endpoint.ID, stackName) - resourceControl = authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls) - - if resourceControl != nil { - return resourceControl, nil - } - } - - return transport.newResourceControlFromPortainerLabels(resourceLabelsObject, resourceIdentifier, resourceType) + if resourceLabelsObject == nil { + return nil, nil } - return nil, nil + if resourceLabelsObject[resourceLabelForDockerServiceID] != nil { + inheritedServiceIdentifier := resourceLabelsObject[resourceLabelForDockerServiceID].(string) + resourceControl = authorization.GetResourceControlByResourceIDAndType(inheritedServiceIdentifier, portainer.ServiceResourceControl, resourceControls) + + if resourceControl != nil { + return resourceControl, nil + } + } + + if resourceLabelsObject[resourceLabelForDockerSwarmStackName] != nil { + stackName := resourceLabelsObject[resourceLabelForDockerSwarmStackName].(string) + stackResourceID := stackutils.ResourceControlID(transport.endpoint.ID, stackName) + resourceControl = authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls) + + if resourceControl != nil { + return resourceControl, nil + } + } + + if resourceLabelsObject[resourceLabelForDockerComposeStackName] != nil { + stackName := resourceLabelsObject[resourceLabelForDockerComposeStackName].(string) + stackResourceID := stackutils.ResourceControlID(transport.endpoint.ID, stackName) + resourceControl = authorization.GetResourceControlByResourceIDAndType(stackResourceID, portainer.StackResourceControl, resourceControls) + + if resourceControl != nil { + return resourceControl, nil + } + } + + return transport.newResourceControlFromPortainerLabels(resourceLabelsObject, resourceIdentifier, resourceType) } func getStackResourceIDFromLabels(resourceLabelsObject map[string]string, endpointID portainer.EndpointID) string { diff --git a/api/http/proxy/factory/docker/build.go b/api/http/proxy/factory/docker/build.go index 77c5e2863..e4923d74e 100644 --- a/api/http/proxy/factory/docker/build.go +++ b/api/http/proxy/factory/docker/build.go @@ -19,6 +19,8 @@ type postDockerfileRequest struct { Content string } +var ErrUploadedFilesNotFound = errors.New("uploaded files not found to build image") + // buildOperation inspects the "Content-Type" header to determine if it needs to alter the request. // // If the value of the header is empty, it means that a Dockerfile is posted via upload, the function @@ -69,13 +71,12 @@ func buildOperation(request *http.Request) error { } case "multipart/form-data": - err := request.ParseMultipartForm(32 * OneMegabyte) // limit parser memory to 32MB - if err != nil { + if err := request.ParseMultipartForm(32 * OneMegabyte); err != nil { return err } if request.MultipartForm == nil || request.MultipartForm.File == nil { - return errors.New("uploaded files not found to build image") + return ErrUploadedFilesNotFound } tfb := archive.NewTarFileInBuffer() diff --git a/api/http/proxy/factory/docker/containers.go b/api/http/proxy/factory/docker/containers.go index 9b98580dd..ffd745699 100644 --- a/api/http/proxy/factory/docker/containers.go +++ b/api/http/proxy/factory/docker/containers.go @@ -19,6 +19,15 @@ import ( const containerObjectIdentifier = "Id" +var ( + ErrPrivilegedModeForbidden = errors.New("forbidden to use privileged mode") + ErrPIDHostNamespaceForbidden = errors.New("forbidden to use pid host namespace") + ErrDeviceMappingForbidden = errors.New("forbidden to use device mapping") + ErrSysCtlSettingsForbidden = errors.New("forbidden to use sysctl settings") + ErrContainerCapabilitiesForbidden = errors.New("forbidden to use container capabilities") + ErrBindMountsForbidden = errors.New("forbidden to use bind mounts") +) + func getInheritedResourceControlFromContainerLabels(dockerClient *client.Client, endpointID portainer.EndpointID, containerID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) { container, err := dockerClient.ContainerInspect(context.Background(), containerID) if err != nil { @@ -108,6 +117,7 @@ func selectorContainerLabelsFromContainerInspectOperation(responseObject map[str containerLabelsObject := utils.GetJSONObject(containerConfigObject, "Labels") return containerLabelsObject } + return nil } @@ -117,6 +127,7 @@ func selectorContainerLabelsFromContainerInspectOperation(responseObject map[str // API schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ContainerList func selectorContainerLabelsFromContainerListOperation(responseObject map[string]any) map[string]any { containerLabelsObject := utils.GetJSONObject(responseObject, "Labels") + return containerLabelsObject } @@ -129,13 +140,12 @@ func filterContainersWithBlackListedLabels(containerData []any, labelBlackList [ containerObject := container.(map[string]any) containerLabels := selectorContainerLabelsFromContainerListOperation(containerObject) - if containerLabels != nil { - if !containerHasBlackListedLabel(containerLabels, labelBlackList) { - filteredContainerData = append(filteredContainerData, containerObject) - } - } else { - filteredContainerData = append(filteredContainerData, containerObject) + + if containerHasBlackListedLabel(containerLabels, labelBlackList) { + continue } + + filteredContainerData = append(filteredContainerData, containerObject) } return filteredContainerData, nil @@ -195,35 +205,34 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req } partialContainer := &PartialContainer{} - err = json.Unmarshal(body, partialContainer) - if err != nil { + if err := json.Unmarshal(body, partialContainer); err != nil { return nil, err } if !securitySettings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged { - return forbiddenResponse, errors.New("forbidden to use privileged mode") + return forbiddenResponse, ErrPrivilegedModeForbidden } if !securitySettings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" { - return forbiddenResponse, errors.New("forbidden to use pid host namespace") + return forbiddenResponse, ErrPIDHostNamespaceForbidden } if !securitySettings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 { - return forbiddenResponse, errors.New("forbidden to use device mapping") + return forbiddenResponse, ErrDeviceMappingForbidden } if !securitySettings.AllowSysctlSettingForRegularUsers && len(partialContainer.HostConfig.Sysctls) > 0 { - return forbiddenResponse, errors.New("forbidden to use sysctl settings") + return forbiddenResponse, ErrSysCtlSettingsForbidden } if !securitySettings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) { - return nil, errors.New("forbidden to use container capabilities") + return nil, ErrContainerCapabilitiesForbidden } if !securitySettings.AllowBindMountsForRegularUsers && (len(partialContainer.HostConfig.Binds) > 0) { for _, bind := range partialContainer.HostConfig.Binds { if strings.HasPrefix(bind, "/") { - return forbiddenResponse, errors.New("forbidden to use bind mounts") + return forbiddenResponse, ErrBindMountsForbidden } } } diff --git a/api/http/proxy/factory/docker/portainer.go b/api/http/proxy/factory/docker/portainer.go index 6d1ad0d90..2868d2194 100644 --- a/api/http/proxy/factory/docker/portainer.go +++ b/api/http/proxy/factory/docker/portainer.go @@ -17,12 +17,15 @@ func init() { func (transport *Transport) applyPortainerContainers(resources []any) ([]any, error) { decoratedResourceData := make([]any, 0) + for _, resource := range resources { responseObject, ok := resource.(map[string]any) if !ok { decoratedResourceData = append(decoratedResourceData, resource) + continue } + responseObject, _ = transport.applyPortainerContainer(responseObject) decoratedResourceData = append(decoratedResourceData, responseObject) } diff --git a/api/http/proxy/factory/docker/registry.go b/api/http/proxy/factory/docker/registry.go index 0c3d3cfac..00e73a2af 100644 --- a/api/http/proxy/factory/docker/registry.go +++ b/api/http/proxy/factory/docker/registry.go @@ -29,31 +29,36 @@ type ( func createRegistryAuthenticationHeader( dataStore dataservices.DataStore, - registryId portainer.RegistryID, + registryID portainer.RegistryID, accessContext *registryAccessContext, ) (authenticationHeader registryAuthenticationHeader, err error) { - if registryId == 0 { // dockerhub (anonymous) + if registryID == 0 { // dockerhub (anonymous) authenticationHeader.Serveraddress = "docker.io" - } else { // any "custom" registry - var matchingRegistry *portainer.Registry - for _, registry := range accessContext.registries { - registry := registry - if registry.ID == registryId && - (accessContext.isAdmin || - security.AuthorizedRegistryAccess(®istry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) { - matchingRegistry = ®istry - break - } + + return + } + + // Any "custom" registry + var matchingRegistry *portainer.Registry + + for _, registry := range accessContext.registries { + registry := registry + if registry.ID == registryID && + (accessContext.isAdmin || + security.AuthorizedRegistryAccess(®istry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) { + matchingRegistry = ®istry + + break + } + } + + if matchingRegistry != nil { + if err = registryutils.EnsureRegTokenValid(dataStore, matchingRegistry); err != nil { + return } - if matchingRegistry != nil { - err = registryutils.EnsureRegTokenValid(dataStore, matchingRegistry) - if err != nil { - return - } - authenticationHeader.Serveraddress = matchingRegistry.URL - authenticationHeader.Username, authenticationHeader.Password, err = registryutils.GetRegEffectiveCredential(matchingRegistry) - } + authenticationHeader.Serveraddress = matchingRegistry.URL + authenticationHeader.Username, authenticationHeader.Password, err = registryutils.GetRegEffectiveCredential(matchingRegistry) } return diff --git a/api/http/proxy/factory/docker/services.go b/api/http/proxy/factory/docker/services.go index f59aecabd..11eb7fdee 100644 --- a/api/http/proxy/factory/docker/services.go +++ b/api/http/proxy/factory/docker/services.go @@ -59,8 +59,8 @@ func (transport *Transport) serviceListOperation(response *http.Response, execut // 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 (transport *Transport) serviceInspectOperation(response *http.Response, executor *operationExecutor) error { - //ServiceInspect response is a JSON object - //https://docs.docker.com/engine/api/v1.28/#operation/ServiceInspect + // ServiceInspect response is a JSON object + // https://docs.docker.com/engine/api/v1.28/#operation/ServiceInspect responseObject, err := utils.GetResponseAsJSONObject(response) if err != nil { return err @@ -85,6 +85,7 @@ func selectorServiceLabels(responseObject map[string]any) map[string]any { if serviceSpecObject != nil { return utils.GetJSONObject(serviceSpecObject, "Labels") } + return nil } @@ -108,33 +109,34 @@ func (transport *Transport) decorateServiceCreationOperation(request *http.Reque return nil, err } - if !isAdminOrEndpointAdmin { - securitySettings, err := transport.fetchEndpointSecuritySettings() - if err != nil { - return nil, err - } + if isAdminOrEndpointAdmin { + return transport.replaceRegistryAuthenticationHeader(request) + } - body, err := io.ReadAll(request.Body) - if err != nil { - return nil, err - } + securitySettings, err := transport.fetchEndpointSecuritySettings() + if err != nil { + return nil, err + } - partialService := &PartialService{} - err = json.Unmarshal(body, partialService) - if err != nil { - return nil, err - } + body, err := io.ReadAll(request.Body) + if err != nil { + return nil, err + } - if !securitySettings.AllowBindMountsForRegularUsers && (len(partialService.TaskTemplate.ContainerSpec.Mounts) > 0) { - for _, mount := range partialService.TaskTemplate.ContainerSpec.Mounts { - if mount.Type == "bind" { - return forbiddenResponse, errors.New("forbidden to use bind mounts") - } + partialService := &PartialService{} + if err := json.Unmarshal(body, partialService); err != nil { + return nil, err + } + + if !securitySettings.AllowBindMountsForRegularUsers && (len(partialService.TaskTemplate.ContainerSpec.Mounts) > 0) { + for _, mount := range partialService.TaskTemplate.ContainerSpec.Mounts { + if mount.Type == "bind" { + return forbiddenResponse, errors.New("forbidden to use bind mounts") } } - - request.Body = io.NopCloser(bytes.NewBuffer(body)) } + request.Body = io.NopCloser(bytes.NewBuffer(body)) + return transport.replaceRegistryAuthenticationHeader(request) } diff --git a/api/http/proxy/factory/docker/transport.go b/api/http/proxy/factory/docker/transport.go index 2ecd79674..1cb7420d9 100644 --- a/api/http/proxy/factory/docker/transport.go +++ b/api/http/proxy/factory/docker/transport.go @@ -59,6 +59,7 @@ type ( operationContext *restrictedDockerOperationContext labelBlackList []portainer.Pair } + restrictedOperationRequest func(*http.Response, *operationExecutor) error operationRequest func(*http.Request) error ) @@ -85,18 +86,18 @@ func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, er } var prefixProxyFuncMap = map[string]func(*Transport, *http.Request, string) (*http.Response, error){ + "build": (*Transport).proxyBuildRequest, "configs": (*Transport).proxyConfigRequest, "containers": (*Transport).proxyContainerRequest, - "services": (*Transport).proxyServiceRequest, - "volumes": (*Transport).proxyVolumeRequest, - "networks": (*Transport).proxyNetworkRequest, - "secrets": (*Transport).proxySecretRequest, - "swarm": (*Transport).proxySwarmRequest, - "nodes": (*Transport).proxyNodeRequest, - "tasks": (*Transport).proxyTaskRequest, - "build": (*Transport).proxyBuildRequest, "images": (*Transport).proxyImageRequest, + "networks": (*Transport).proxyNetworkRequest, + "nodes": (*Transport).proxyNodeRequest, + "secrets": (*Transport).proxySecretRequest, + "services": (*Transport).proxyServiceRequest, + "swarm": (*Transport).proxySwarmRequest, + "tasks": (*Transport).proxyTaskRequest, "v2": (*Transport).proxyAgentRequest, + "volumes": (*Transport).proxyVolumeRequest, } // ProxyDockerRequest intercepts a Docker API request and apply logic based @@ -119,6 +120,7 @@ func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Res if proxyFunc := prefixProxyFuncMap[prefix]; proxyFunc != nil { return proxyFunc(transport, request, unversionedPath) } + return transport.executeDockerRequest(request) } @@ -141,7 +143,7 @@ func (transport *Transport) proxyAgentRequest(r *http.Request, unversionedPath s switch { case strings.HasPrefix(requestPath, "/browse"): - // host file browser request + // Host file browser request volumeIDParameter, found := r.URL.Query()["volumeID"] if !found || len(volumeIDParameter) < 1 { return transport.administratorOperation(r) @@ -154,7 +156,7 @@ func (transport *Transport) proxyAgentRequest(r *http.Request, unversionedPath s return nil, err } - // volume browser request + // Volume browser request return transport.restrictedResourceOperation(r, resourceID, volumeName, portainer.VolumeResourceControl, true) case strings.HasPrefix(requestPath, "/dockerhub"): requestPath, registryIdString := path.Split(r.URL.Path) @@ -206,7 +208,7 @@ func (transport *Transport) proxyConfigRequest(request *http.Request, unversione return transport.rewriteOperation(request, transport.configListOperation) default: - // assume /configs/{id} + // Assume /configs/{id} configID := path.Base(requestPath) if request.Method == http.MethodGet { @@ -242,6 +244,7 @@ func (transport *Transport) proxyContainerRequest(request *http.Request, unversi if action == "json" { return transport.rewriteOperation(request, transport.containerInspectOperation) } + return transport.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false) } else if match, _ := path.Match("/containers/*", requestPath); match { // Handle /containers/{id} requests @@ -253,6 +256,7 @@ func (transport *Transport) proxyContainerRequest(request *http.Request, unversi return transport.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false) } + return transport.executeDockerRequest(request) } } @@ -273,6 +277,7 @@ func (transport *Transport) proxyServiceRequest(request *http.Request, unversion // Handle /services/{id}/{action} requests serviceID := path.Base(path.Dir(requestPath)) transport.decorateRegistryAuthenticationHeader(request) + return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false) } else if match, _ := path.Match("/services/*", requestPath); match { // Handle /services/{id} requests @@ -284,8 +289,10 @@ func (transport *Transport) proxyServiceRequest(request *http.Request, unversion case http.MethodDelete: return transport.executeGenericResourceDeletionOperation(request, serviceID, serviceID, portainer.ServiceResourceControl) } + return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false) } + return transport.executeDockerRequest(request) } } @@ -304,7 +311,7 @@ func (transport *Transport) proxyVolumeRequest(request *http.Request, unversione return transport.rewriteOperation(request, transport.volumeListOperation) default: - // assume /volumes/{name} + // Assume /volumes/{name} return transport.restrictedVolumeOperation(requestPath, request) } } @@ -320,7 +327,7 @@ func (transport *Transport) proxyNetworkRequest(request *http.Request, unversion return transport.rewriteOperation(request, transport.networkListOperation) default: - // assume /networks/{id} + // Assume /networks/{id} networkID := path.Base(requestPath) if request.Method == http.MethodGet { @@ -328,6 +335,7 @@ func (transport *Transport) proxyNetworkRequest(request *http.Request, unversion } else if request.Method == http.MethodDelete { return transport.executeGenericResourceDeletionOperation(request, networkID, networkID, portainer.NetworkResourceControl) } + return transport.restrictedResourceOperation(request, networkID, networkID, portainer.NetworkResourceControl, false) } } @@ -343,7 +351,7 @@ func (transport *Transport) proxySecretRequest(request *http.Request, unversione return transport.rewriteOperation(request, transport.secretListOperation) default: - // assume /secrets/{id} + // Assume /secrets/{id} secretID := path.Base(requestPath) if request.Method == http.MethodGet { @@ -351,6 +359,7 @@ func (transport *Transport) proxySecretRequest(request *http.Request, unversione } else if request.Method == http.MethodDelete { return transport.executeGenericResourceDeletionOperation(request, secretID, secretID, portainer.SecretResourceControl) } + return transport.restrictedResourceOperation(request, secretID, secretID, portainer.SecretResourceControl, false) } } @@ -358,7 +367,7 @@ func (transport *Transport) proxySecretRequest(request *http.Request, unversione func (transport *Transport) proxyNodeRequest(request *http.Request, unversionedPath string) (*http.Response, error) { requestPath := unversionedPath - // assume /nodes/{id} + // Assume /nodes/{id} if path.Base(requestPath) != "nodes" { return transport.administratorOperation(request) } @@ -373,7 +382,7 @@ func (transport *Transport) proxySwarmRequest(request *http.Request, unversioned case "/swarm": return transport.rewriteOperation(request, swarmInspectOperation) default: - // assume /swarm/{action} + // Assume /swarm/{action} return transport.administratorOperation(request) } } @@ -385,34 +394,38 @@ func (transport *Transport) proxyTaskRequest(request *http.Request, unversionedP case "/tasks": return transport.rewriteOperation(request, transport.taskListOperation) default: - // assume /tasks/{id} + // Assume /tasks/{id} return transport.executeDockerRequest(request) } } func (transport *Transport) proxyBuildRequest(request *http.Request, _ string) (*http.Response, error) { - err := transport.updateDefaultGitBranch(request) - if err != nil { + if err := transport.updateDefaultGitBranch(request); err != nil { return nil, err } + return transport.interceptAndRewriteRequest(request, buildOperation) } func (transport *Transport) updateDefaultGitBranch(request *http.Request) error { remote := request.URL.Query().Get("remote") - if strings.HasSuffix(remote, ".git") { - repositoryURL := remote[:len(remote)-4] - latestCommitID, err := transport.gitService.LatestCommitID(repositoryURL, "", "", "", false) - if err != nil { - return err - } - newRemote := fmt.Sprintf("%s#%s", remote, latestCommitID) - q := request.URL.Query() - q.Set("remote", newRemote) - request.URL.RawQuery = q.Encode() + if !strings.HasSuffix(remote, ".git") { + return nil } + repositoryURL := remote[:len(remote)-4] + latestCommitID, err := transport.gitService.LatestCommitID(repositoryURL, "", "", "", false) + if err != nil { + return err + } + + newRemote := fmt.Sprintf("%s#%s", remote, latestCommitID) + + q := request.URL.Query() + q.Set("remote", newRemote) + request.URL.RawQuery = q.Encode() + return nil } @@ -426,6 +439,7 @@ func (transport *Transport) proxyImageRequest(request *http.Request, unversioned if path.Base(requestPath) == "push" && request.Method == http.MethodPost { return transport.replaceRegistryAuthenticationHeader(request) } + return transport.executeDockerRequest(request) } } @@ -444,47 +458,46 @@ func (transport *Transport) decorateRegistryAuthenticationHeader(request *http.R originalHeader := request.Header.Get("X-Registry-Auth") - if originalHeader != "" { - - decodedHeaderData, err := base64.StdEncoding.DecodeString(originalHeader) - if err != nil { - return err - } - - var originalHeaderData portainerRegistryAuthenticationHeader - err = json.Unmarshal(decodedHeaderData, &originalHeaderData) - if err != nil { - return err - } - - // delete header and exist function without error if Front End - // passes empty json. This is to restore original behavior which - // never originally passed this header - if string(decodedHeaderData) == "{}" { - request.Header.Del("X-Registry-Auth") - return nil - } - - // only set X-Registry-Auth if registryId is defined - if originalHeaderData.RegistryId == nil { - return nil - } - - authenticationHeader, err := createRegistryAuthenticationHeader(transport.dataStore, *originalHeaderData.RegistryId, accessContext) - if err != nil { - return err - } - - headerData, err := json.Marshal(authenticationHeader) - if err != nil { - return err - } - - header := base64.URLEncoding.EncodeToString(headerData) - - request.Header.Set("X-Registry-Auth", header) + if originalHeader == "" { + return nil } + decodedHeaderData, err := base64.StdEncoding.DecodeString(originalHeader) + if err != nil { + return err + } + + var originalHeaderData portainerRegistryAuthenticationHeader + if err := json.Unmarshal(decodedHeaderData, &originalHeaderData); err != nil { + return err + } + + // Delete header and exist function without error if Front End + // passes empty json. This is to restore original behavior which + // never originally passed this header + if string(decodedHeaderData) == "{}" { + request.Header.Del("X-Registry-Auth") + + return nil + } + + // Only set X-Registry-Auth if registryId is defined + if originalHeaderData.RegistryId == nil { + return nil + } + + authenticationHeader, err := createRegistryAuthenticationHeader(transport.dataStore, *originalHeaderData.RegistryId, accessContext) + if err != nil { + return err + } + + headerData, err := json.Marshal(authenticationHeader) + if err != nil { + return err + } + + request.Header.Set("X-Registry-Auth", base64.URLEncoding.EncodeToString(headerData)) + return nil } @@ -659,11 +672,9 @@ func (transport *Transport) executeGenericResourceDeletionOperation(request *htt } resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType) - if err != nil { - if dataservices.IsErrObjectNotFound(err) { - return response, nil - } - + if dataservices.IsErrObjectNotFound(err) { + return response, nil + } else if err != nil { return response, err } diff --git a/api/http/proxy/factory/docker/volumes.go b/api/http/proxy/factory/docker/volumes.go index 7d5a4714e..12bcdd892 100644 --- a/api/http/proxy/factory/docker/volumes.go +++ b/api/http/proxy/factory/docker/volumes.go @@ -44,41 +44,43 @@ func (transport *Transport) volumeListOperation(response *http.Response, executo } // The "Volumes" field contains the list of volumes as an array of JSON objects - if responseObject["Volumes"] != nil { - volumeData := responseObject["Volumes"].([]any) - - if transport.snapshotService != nil { - // Filling snapshot data can improve the performance of getVolumeResourceID - if err = transport.snapshotService.FillSnapshotData(transport.endpoint); err != nil { - log.Info().Err(err). - Int("endpoint id", int(transport.endpoint.ID)). - Msg("snapshot is not filled into the endpoint.") - } - } - - for _, volumeObject := range volumeData { - volume := volumeObject.(map[string]any) - - if err := transport.decorateVolumeResponseWithResourceID(volume); err != nil { - return fmt.Errorf("failed decorating volume response: %w", err) - } - } - - resourceOperationParameters := &resourceOperationParameters{ - resourceIdentifierAttribute: volumeObjectIdentifier, - resourceType: portainer.VolumeResourceControl, - labelsObjectSelector: selectorVolumeLabels, - } - - volumeData, err = transport.applyAccessControlOnResourceList(resourceOperationParameters, volumeData, executor) - if err != nil { - return err - } - - // Overwrite the original volume list - responseObject["Volumes"] = volumeData + if responseObject["Volumes"] == nil { + return utils.RewriteResponse(response, responseObject, http.StatusOK) } + volumeData := responseObject["Volumes"].([]any) + + if transport.snapshotService != nil { + // Filling snapshot data can improve the performance of getVolumeResourceID + if err = transport.snapshotService.FillSnapshotData(transport.endpoint); err != nil { + log.Info().Err(err). + Int("endpoint id", int(transport.endpoint.ID)). + Msg("snapshot is not filled into the endpoint.") + } + } + + for _, volumeObject := range volumeData { + volume := volumeObject.(map[string]any) + + if err := transport.decorateVolumeResponseWithResourceID(volume); err != nil { + return fmt.Errorf("failed decorating volume response: %w", err) + } + } + + resourceOperationParameters := &resourceOperationParameters{ + resourceIdentifierAttribute: volumeObjectIdentifier, + resourceType: portainer.VolumeResourceControl, + labelsObjectSelector: selectorVolumeLabels, + } + + volumeData, err = transport.applyAccessControlOnResourceList(resourceOperationParameters, volumeData, executor) + if err != nil { + return err + } + + // Overwrite the original volume list + responseObject["Volumes"] = volumeData + return utils.RewriteResponse(response, responseObject, http.StatusOK) }