chore(docker): clean up the code EE-7325 (#11997)

pull/12017/head
andres-portainer 2024-07-05 16:19:46 -03:00 committed by GitHub
parent faca64442f
commit 340830d121
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 280 additions and 254 deletions

View File

@ -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 {

View File

@ -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()

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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(&registry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) {
matchingRegistry = &registry
break
}
return
}
// Any "custom" registry
var matchingRegistry *portainer.Registry
for _, registry := range accessContext.registries {
registry := registry
if registry.ID == registryID &&
(accessContext.isAdmin ||
security.AuthorizedRegistryAccess(&registry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) {
matchingRegistry = &registry
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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}