mirror of https://github.com/portainer/portainer
chore(docker): clean up the code EE-7325 (#11997)
parent
faca64442f
commit
340830d121
|
@ -7,6 +7,7 @@ import (
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
||||||
"github.com/portainer/portainer/api/internal/authorization"
|
"github.com/portainer/portainer/api/internal/authorization"
|
||||||
|
"github.com/portainer/portainer/api/slicesx"
|
||||||
"github.com/portainer/portainer/api/stacks/stackutils"
|
"github.com/portainer/portainer/api/stacks/stackutils"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -23,7 +24,8 @@ const (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
resourceLabelsObjectSelector func(map[string]any) map[string]any
|
resourceLabelsObjectSelector func(map[string]any) map[string]any
|
||||||
resourceOperationParameters struct {
|
|
||||||
|
resourceOperationParameters struct {
|
||||||
resourceIdentifierAttribute string
|
resourceIdentifierAttribute string
|
||||||
resourceType portainer.ResourceControlType
|
resourceType portainer.ResourceControlType
|
||||||
labelsObjectSelector resourceLabelsObjectSelector
|
labelsObjectSelector resourceLabelsObjectSelector
|
||||||
|
@ -31,28 +33,18 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func getUniqueElements(items string) []string {
|
func getUniqueElements(items string) []string {
|
||||||
result := []string{}
|
xs := strings.Split(items, ",")
|
||||||
seen := make(map[string]struct{})
|
xs = slicesx.Map(xs, strings.TrimSpace)
|
||||||
for _, item := range strings.Split(items, ",") {
|
xs = slicesx.Filter(xs, func(x string) bool { return len(x) > 0 })
|
||||||
v := strings.TrimSpace(item)
|
|
||||||
if v == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[v]; !ok {
|
|
||||||
result = append(result, v)
|
|
||||||
seen[v] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return slicesx.Unique(xs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject map[string]any, resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject map[string]any, resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error) {
|
||||||
if labelsObject[resourceLabelForPortainerPublicResourceControl] != nil {
|
if labelsObject[resourceLabelForPortainerPublicResourceControl] != nil {
|
||||||
resourceControl := authorization.NewPublicResourceControl(resourceID, resourceType)
|
resourceControl := authorization.NewPublicResourceControl(resourceID, resourceType)
|
||||||
|
|
||||||
err := transport.dataStore.ResourceControl().Create(resourceControl)
|
if err := transport.dataStore.ResourceControl().Create(resourceControl); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +53,7 @@ func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject m
|
||||||
|
|
||||||
teamNames := make([]string, 0)
|
teamNames := make([]string, 0)
|
||||||
userNames := make([]string, 0)
|
userNames := make([]string, 0)
|
||||||
|
|
||||||
if labelsObject[resourceLabelForPortainerTeamResourceControl] != nil {
|
if labelsObject[resourceLabelForPortainerTeamResourceControl] != nil {
|
||||||
concatenatedTeamNames := labelsObject[resourceLabelForPortainerTeamResourceControl].(string)
|
concatenatedTeamNames := labelsObject[resourceLabelForPortainerTeamResourceControl].(string)
|
||||||
teamNames = getUniqueElements(concatenatedTeamNames)
|
teamNames = getUniqueElements(concatenatedTeamNames)
|
||||||
|
@ -71,48 +64,48 @@ func (transport *Transport) newResourceControlFromPortainerLabels(labelsObject m
|
||||||
userNames = getUniqueElements(concatenatedUserNames)
|
userNames = getUniqueElements(concatenatedUserNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(teamNames) > 0 || len(userNames) > 0 {
|
if len(teamNames) == 0 && len(userNames) == 0 {
|
||||||
teamIDs := make([]portainer.TeamID, 0)
|
return nil, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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
|
return resourceControl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceLabelsObject != nil {
|
if resourceLabelsObject == nil {
|
||||||
if resourceLabelsObject[resourceLabelForDockerServiceID] != nil {
|
return nil, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func getStackResourceIDFromLabels(resourceLabelsObject map[string]string, endpointID portainer.EndpointID) string {
|
||||||
|
|
|
@ -19,6 +19,8 @@ type postDockerfileRequest struct {
|
||||||
Content string
|
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.
|
// 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
|
// 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":
|
case "multipart/form-data":
|
||||||
err := request.ParseMultipartForm(32 * OneMegabyte) // limit parser memory to 32MB
|
if err := request.ParseMultipartForm(32 * OneMegabyte); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.MultipartForm == nil || request.MultipartForm.File == nil {
|
if request.MultipartForm == nil || request.MultipartForm.File == nil {
|
||||||
return errors.New("uploaded files not found to build image")
|
return ErrUploadedFilesNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
tfb := archive.NewTarFileInBuffer()
|
tfb := archive.NewTarFileInBuffer()
|
||||||
|
|
|
@ -19,6 +19,15 @@ import (
|
||||||
|
|
||||||
const containerObjectIdentifier = "Id"
|
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) {
|
func getInheritedResourceControlFromContainerLabels(dockerClient *client.Client, endpointID portainer.EndpointID, containerID string, resourceControls []portainer.ResourceControl) (*portainer.ResourceControl, error) {
|
||||||
container, err := dockerClient.ContainerInspect(context.Background(), containerID)
|
container, err := dockerClient.ContainerInspect(context.Background(), containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -108,6 +117,7 @@ func selectorContainerLabelsFromContainerInspectOperation(responseObject map[str
|
||||||
containerLabelsObject := utils.GetJSONObject(containerConfigObject, "Labels")
|
containerLabelsObject := utils.GetJSONObject(containerConfigObject, "Labels")
|
||||||
return containerLabelsObject
|
return containerLabelsObject
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +127,7 @@ func selectorContainerLabelsFromContainerInspectOperation(responseObject map[str
|
||||||
// API schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ContainerList
|
// API schema reference: https://docs.docker.com/engine/api/v1.28/#operation/ContainerList
|
||||||
func selectorContainerLabelsFromContainerListOperation(responseObject map[string]any) map[string]any {
|
func selectorContainerLabelsFromContainerListOperation(responseObject map[string]any) map[string]any {
|
||||||
containerLabelsObject := utils.GetJSONObject(responseObject, "Labels")
|
containerLabelsObject := utils.GetJSONObject(responseObject, "Labels")
|
||||||
|
|
||||||
return containerLabelsObject
|
return containerLabelsObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,13 +140,12 @@ func filterContainersWithBlackListedLabels(containerData []any, labelBlackList [
|
||||||
containerObject := container.(map[string]any)
|
containerObject := container.(map[string]any)
|
||||||
|
|
||||||
containerLabels := selectorContainerLabelsFromContainerListOperation(containerObject)
|
containerLabels := selectorContainerLabelsFromContainerListOperation(containerObject)
|
||||||
if containerLabels != nil {
|
|
||||||
if !containerHasBlackListedLabel(containerLabels, labelBlackList) {
|
if containerHasBlackListedLabel(containerLabels, labelBlackList) {
|
||||||
filteredContainerData = append(filteredContainerData, containerObject)
|
continue
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filteredContainerData = append(filteredContainerData, containerObject)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filteredContainerData = append(filteredContainerData, containerObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filteredContainerData, nil
|
return filteredContainerData, nil
|
||||||
|
@ -195,35 +205,34 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||||
}
|
}
|
||||||
|
|
||||||
partialContainer := &PartialContainer{}
|
partialContainer := &PartialContainer{}
|
||||||
err = json.Unmarshal(body, partialContainer)
|
if err := json.Unmarshal(body, partialContainer); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !securitySettings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged {
|
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" {
|
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 {
|
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 {
|
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) {
|
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) {
|
if !securitySettings.AllowBindMountsForRegularUsers && (len(partialContainer.HostConfig.Binds) > 0) {
|
||||||
for _, bind := range partialContainer.HostConfig.Binds {
|
for _, bind := range partialContainer.HostConfig.Binds {
|
||||||
if strings.HasPrefix(bind, "/") {
|
if strings.HasPrefix(bind, "/") {
|
||||||
return forbiddenResponse, errors.New("forbidden to use bind mounts")
|
return forbiddenResponse, ErrBindMountsForbidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,15 @@ func init() {
|
||||||
|
|
||||||
func (transport *Transport) applyPortainerContainers(resources []any) ([]any, error) {
|
func (transport *Transport) applyPortainerContainers(resources []any) ([]any, error) {
|
||||||
decoratedResourceData := make([]any, 0)
|
decoratedResourceData := make([]any, 0)
|
||||||
|
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
responseObject, ok := resource.(map[string]any)
|
responseObject, ok := resource.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
decoratedResourceData = append(decoratedResourceData, resource)
|
decoratedResourceData = append(decoratedResourceData, resource)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
responseObject, _ = transport.applyPortainerContainer(responseObject)
|
responseObject, _ = transport.applyPortainerContainer(responseObject)
|
||||||
decoratedResourceData = append(decoratedResourceData, responseObject)
|
decoratedResourceData = append(decoratedResourceData, responseObject)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,31 +29,36 @@ type (
|
||||||
|
|
||||||
func createRegistryAuthenticationHeader(
|
func createRegistryAuthenticationHeader(
|
||||||
dataStore dataservices.DataStore,
|
dataStore dataservices.DataStore,
|
||||||
registryId portainer.RegistryID,
|
registryID portainer.RegistryID,
|
||||||
accessContext *registryAccessContext,
|
accessContext *registryAccessContext,
|
||||||
) (authenticationHeader registryAuthenticationHeader, err error) {
|
) (authenticationHeader registryAuthenticationHeader, err error) {
|
||||||
if registryId == 0 { // dockerhub (anonymous)
|
if registryID == 0 { // dockerhub (anonymous)
|
||||||
authenticationHeader.Serveraddress = "docker.io"
|
authenticationHeader.Serveraddress = "docker.io"
|
||||||
} else { // any "custom" registry
|
|
||||||
var matchingRegistry *portainer.Registry
|
return
|
||||||
for _, registry := range accessContext.registries {
|
}
|
||||||
registry := registry
|
|
||||||
if registry.ID == registryId &&
|
// Any "custom" registry
|
||||||
(accessContext.isAdmin ||
|
var matchingRegistry *portainer.Registry
|
||||||
security.AuthorizedRegistryAccess(®istry, accessContext.user, accessContext.teamMemberships, accessContext.endpointID)) {
|
|
||||||
matchingRegistry = ®istry
|
for _, registry := range accessContext.registries {
|
||||||
break
|
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 {
|
authenticationHeader.Serveraddress = matchingRegistry.URL
|
||||||
err = registryutils.EnsureRegTokenValid(dataStore, matchingRegistry)
|
authenticationHeader.Username, authenticationHeader.Password, err = registryutils.GetRegEffectiveCredential(matchingRegistry)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authenticationHeader.Serveraddress = matchingRegistry.URL
|
|
||||||
authenticationHeader.Username, authenticationHeader.Password, err = registryutils.GetRegEffectiveCredential(matchingRegistry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -59,8 +59,8 @@ func (transport *Transport) serviceListOperation(response *http.Response, execut
|
||||||
// serviceInspectOperation extracts the response as a JSON object, verify that the user
|
// 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.
|
// 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 {
|
func (transport *Transport) serviceInspectOperation(response *http.Response, executor *operationExecutor) error {
|
||||||
//ServiceInspect response is a JSON object
|
// ServiceInspect response is a JSON object
|
||||||
//https://docs.docker.com/engine/api/v1.28/#operation/ServiceInspect
|
// https://docs.docker.com/engine/api/v1.28/#operation/ServiceInspect
|
||||||
responseObject, err := utils.GetResponseAsJSONObject(response)
|
responseObject, err := utils.GetResponseAsJSONObject(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -85,6 +85,7 @@ func selectorServiceLabels(responseObject map[string]any) map[string]any {
|
||||||
if serviceSpecObject != nil {
|
if serviceSpecObject != nil {
|
||||||
return utils.GetJSONObject(serviceSpecObject, "Labels")
|
return utils.GetJSONObject(serviceSpecObject, "Labels")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,33 +109,34 @@ func (transport *Transport) decorateServiceCreationOperation(request *http.Reque
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isAdminOrEndpointAdmin {
|
if isAdminOrEndpointAdmin {
|
||||||
securitySettings, err := transport.fetchEndpointSecuritySettings()
|
return transport.replaceRegistryAuthenticationHeader(request)
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(request.Body)
|
securitySettings, err := transport.fetchEndpointSecuritySettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
partialService := &PartialService{}
|
body, err := io.ReadAll(request.Body)
|
||||||
err = json.Unmarshal(body, partialService)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !securitySettings.AllowBindMountsForRegularUsers && (len(partialService.TaskTemplate.ContainerSpec.Mounts) > 0) {
|
partialService := &PartialService{}
|
||||||
for _, mount := range partialService.TaskTemplate.ContainerSpec.Mounts {
|
if err := json.Unmarshal(body, partialService); err != nil {
|
||||||
if mount.Type == "bind" {
|
return nil, err
|
||||||
return forbiddenResponse, errors.New("forbidden to use bind mounts")
|
}
|
||||||
}
|
|
||||||
|
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)
|
return transport.replaceRegistryAuthenticationHeader(request)
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ type (
|
||||||
operationContext *restrictedDockerOperationContext
|
operationContext *restrictedDockerOperationContext
|
||||||
labelBlackList []portainer.Pair
|
labelBlackList []portainer.Pair
|
||||||
}
|
}
|
||||||
|
|
||||||
restrictedOperationRequest func(*http.Response, *operationExecutor) error
|
restrictedOperationRequest func(*http.Response, *operationExecutor) error
|
||||||
operationRequest func(*http.Request) 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){
|
var prefixProxyFuncMap = map[string]func(*Transport, *http.Request, string) (*http.Response, error){
|
||||||
|
"build": (*Transport).proxyBuildRequest,
|
||||||
"configs": (*Transport).proxyConfigRequest,
|
"configs": (*Transport).proxyConfigRequest,
|
||||||
"containers": (*Transport).proxyContainerRequest,
|
"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,
|
"images": (*Transport).proxyImageRequest,
|
||||||
|
"networks": (*Transport).proxyNetworkRequest,
|
||||||
|
"nodes": (*Transport).proxyNodeRequest,
|
||||||
|
"secrets": (*Transport).proxySecretRequest,
|
||||||
|
"services": (*Transport).proxyServiceRequest,
|
||||||
|
"swarm": (*Transport).proxySwarmRequest,
|
||||||
|
"tasks": (*Transport).proxyTaskRequest,
|
||||||
"v2": (*Transport).proxyAgentRequest,
|
"v2": (*Transport).proxyAgentRequest,
|
||||||
|
"volumes": (*Transport).proxyVolumeRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyDockerRequest intercepts a Docker API request and apply logic based
|
// 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 {
|
if proxyFunc := prefixProxyFuncMap[prefix]; proxyFunc != nil {
|
||||||
return proxyFunc(transport, request, unversionedPath)
|
return proxyFunc(transport, request, unversionedPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +143,7 @@ func (transport *Transport) proxyAgentRequest(r *http.Request, unversionedPath s
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(requestPath, "/browse"):
|
case strings.HasPrefix(requestPath, "/browse"):
|
||||||
// host file browser request
|
// Host file browser request
|
||||||
volumeIDParameter, found := r.URL.Query()["volumeID"]
|
volumeIDParameter, found := r.URL.Query()["volumeID"]
|
||||||
if !found || len(volumeIDParameter) < 1 {
|
if !found || len(volumeIDParameter) < 1 {
|
||||||
return transport.administratorOperation(r)
|
return transport.administratorOperation(r)
|
||||||
|
@ -154,7 +156,7 @@ func (transport *Transport) proxyAgentRequest(r *http.Request, unversionedPath s
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// volume browser request
|
// Volume browser request
|
||||||
return transport.restrictedResourceOperation(r, resourceID, volumeName, portainer.VolumeResourceControl, true)
|
return transport.restrictedResourceOperation(r, resourceID, volumeName, portainer.VolumeResourceControl, true)
|
||||||
case strings.HasPrefix(requestPath, "/dockerhub"):
|
case strings.HasPrefix(requestPath, "/dockerhub"):
|
||||||
requestPath, registryIdString := path.Split(r.URL.Path)
|
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)
|
return transport.rewriteOperation(request, transport.configListOperation)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// assume /configs/{id}
|
// Assume /configs/{id}
|
||||||
configID := path.Base(requestPath)
|
configID := path.Base(requestPath)
|
||||||
|
|
||||||
if request.Method == http.MethodGet {
|
if request.Method == http.MethodGet {
|
||||||
|
@ -242,6 +244,7 @@ func (transport *Transport) proxyContainerRequest(request *http.Request, unversi
|
||||||
if action == "json" {
|
if action == "json" {
|
||||||
return transport.rewriteOperation(request, transport.containerInspectOperation)
|
return transport.rewriteOperation(request, transport.containerInspectOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false)
|
return transport.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false)
|
||||||
} else if match, _ := path.Match("/containers/*", requestPath); match {
|
} else if match, _ := path.Match("/containers/*", requestPath); match {
|
||||||
// Handle /containers/{id} requests
|
// 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.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,6 +277,7 @@ func (transport *Transport) proxyServiceRequest(request *http.Request, unversion
|
||||||
// Handle /services/{id}/{action} requests
|
// Handle /services/{id}/{action} requests
|
||||||
serviceID := path.Base(path.Dir(requestPath))
|
serviceID := path.Base(path.Dir(requestPath))
|
||||||
transport.decorateRegistryAuthenticationHeader(request)
|
transport.decorateRegistryAuthenticationHeader(request)
|
||||||
|
|
||||||
return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false)
|
return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false)
|
||||||
} else if match, _ := path.Match("/services/*", requestPath); match {
|
} else if match, _ := path.Match("/services/*", requestPath); match {
|
||||||
// Handle /services/{id} requests
|
// Handle /services/{id} requests
|
||||||
|
@ -284,8 +289,10 @@ func (transport *Transport) proxyServiceRequest(request *http.Request, unversion
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
return transport.executeGenericResourceDeletionOperation(request, serviceID, serviceID, portainer.ServiceResourceControl)
|
return transport.executeGenericResourceDeletionOperation(request, serviceID, serviceID, portainer.ServiceResourceControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false)
|
return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,7 +311,7 @@ func (transport *Transport) proxyVolumeRequest(request *http.Request, unversione
|
||||||
return transport.rewriteOperation(request, transport.volumeListOperation)
|
return transport.rewriteOperation(request, transport.volumeListOperation)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// assume /volumes/{name}
|
// Assume /volumes/{name}
|
||||||
return transport.restrictedVolumeOperation(requestPath, request)
|
return transport.restrictedVolumeOperation(requestPath, request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +327,7 @@ func (transport *Transport) proxyNetworkRequest(request *http.Request, unversion
|
||||||
return transport.rewriteOperation(request, transport.networkListOperation)
|
return transport.rewriteOperation(request, transport.networkListOperation)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// assume /networks/{id}
|
// Assume /networks/{id}
|
||||||
networkID := path.Base(requestPath)
|
networkID := path.Base(requestPath)
|
||||||
|
|
||||||
if request.Method == http.MethodGet {
|
if request.Method == http.MethodGet {
|
||||||
|
@ -328,6 +335,7 @@ func (transport *Transport) proxyNetworkRequest(request *http.Request, unversion
|
||||||
} else if request.Method == http.MethodDelete {
|
} else if request.Method == http.MethodDelete {
|
||||||
return transport.executeGenericResourceDeletionOperation(request, networkID, networkID, portainer.NetworkResourceControl)
|
return transport.executeGenericResourceDeletionOperation(request, networkID, networkID, portainer.NetworkResourceControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.restrictedResourceOperation(request, networkID, networkID, portainer.NetworkResourceControl, false)
|
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)
|
return transport.rewriteOperation(request, transport.secretListOperation)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// assume /secrets/{id}
|
// Assume /secrets/{id}
|
||||||
secretID := path.Base(requestPath)
|
secretID := path.Base(requestPath)
|
||||||
|
|
||||||
if request.Method == http.MethodGet {
|
if request.Method == http.MethodGet {
|
||||||
|
@ -351,6 +359,7 @@ func (transport *Transport) proxySecretRequest(request *http.Request, unversione
|
||||||
} else if request.Method == http.MethodDelete {
|
} else if request.Method == http.MethodDelete {
|
||||||
return transport.executeGenericResourceDeletionOperation(request, secretID, secretID, portainer.SecretResourceControl)
|
return transport.executeGenericResourceDeletionOperation(request, secretID, secretID, portainer.SecretResourceControl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.restrictedResourceOperation(request, secretID, secretID, portainer.SecretResourceControl, false)
|
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) {
|
func (transport *Transport) proxyNodeRequest(request *http.Request, unversionedPath string) (*http.Response, error) {
|
||||||
requestPath := unversionedPath
|
requestPath := unversionedPath
|
||||||
|
|
||||||
// assume /nodes/{id}
|
// Assume /nodes/{id}
|
||||||
if path.Base(requestPath) != "nodes" {
|
if path.Base(requestPath) != "nodes" {
|
||||||
return transport.administratorOperation(request)
|
return transport.administratorOperation(request)
|
||||||
}
|
}
|
||||||
|
@ -373,7 +382,7 @@ func (transport *Transport) proxySwarmRequest(request *http.Request, unversioned
|
||||||
case "/swarm":
|
case "/swarm":
|
||||||
return transport.rewriteOperation(request, swarmInspectOperation)
|
return transport.rewriteOperation(request, swarmInspectOperation)
|
||||||
default:
|
default:
|
||||||
// assume /swarm/{action}
|
// Assume /swarm/{action}
|
||||||
return transport.administratorOperation(request)
|
return transport.administratorOperation(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -385,34 +394,38 @@ func (transport *Transport) proxyTaskRequest(request *http.Request, unversionedP
|
||||||
case "/tasks":
|
case "/tasks":
|
||||||
return transport.rewriteOperation(request, transport.taskListOperation)
|
return transport.rewriteOperation(request, transport.taskListOperation)
|
||||||
default:
|
default:
|
||||||
// assume /tasks/{id}
|
// Assume /tasks/{id}
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) proxyBuildRequest(request *http.Request, _ string) (*http.Response, error) {
|
func (transport *Transport) proxyBuildRequest(request *http.Request, _ string) (*http.Response, error) {
|
||||||
err := transport.updateDefaultGitBranch(request)
|
if err := transport.updateDefaultGitBranch(request); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.interceptAndRewriteRequest(request, buildOperation)
|
return transport.interceptAndRewriteRequest(request, buildOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (transport *Transport) updateDefaultGitBranch(request *http.Request) error {
|
func (transport *Transport) updateDefaultGitBranch(request *http.Request) error {
|
||||||
remote := request.URL.Query().Get("remote")
|
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()
|
if !strings.HasSuffix(remote, ".git") {
|
||||||
q.Set("remote", newRemote)
|
return nil
|
||||||
request.URL.RawQuery = q.Encode()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,6 +439,7 @@ func (transport *Transport) proxyImageRequest(request *http.Request, unversioned
|
||||||
if path.Base(requestPath) == "push" && request.Method == http.MethodPost {
|
if path.Base(requestPath) == "push" && request.Method == http.MethodPost {
|
||||||
return transport.replaceRegistryAuthenticationHeader(request)
|
return transport.replaceRegistryAuthenticationHeader(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
return transport.executeDockerRequest(request)
|
return transport.executeDockerRequest(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,47 +458,46 @@ func (transport *Transport) decorateRegistryAuthenticationHeader(request *http.R
|
||||||
|
|
||||||
originalHeader := request.Header.Get("X-Registry-Auth")
|
originalHeader := request.Header.Get("X-Registry-Auth")
|
||||||
|
|
||||||
if originalHeader != "" {
|
if originalHeader == "" {
|
||||||
|
return nil
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -659,11 +672,9 @@ func (transport *Transport) executeGenericResourceDeletionOperation(request *htt
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType)
|
resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType)
|
||||||
if err != nil {
|
if dataservices.IsErrObjectNotFound(err) {
|
||||||
if dataservices.IsErrObjectNotFound(err) {
|
return response, nil
|
||||||
return response, nil
|
} else if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// The "Volumes" field contains the list of volumes as an array of JSON objects
|
||||||
if responseObject["Volumes"] != nil {
|
if responseObject["Volumes"] == nil {
|
||||||
volumeData := responseObject["Volumes"].([]any)
|
return utils.RewriteResponse(response, responseObject, http.StatusOK)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
return utils.RewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue