diff --git a/api/bolt/init.go b/api/bolt/init.go index 244f4e961..982702071 100644 --- a/api/bolt/init.go +++ b/api/bolt/init.go @@ -28,6 +28,7 @@ func (store *Store) Init() error { AllowBindMountsForRegularUsers: true, AllowPrivilegedModeForRegularUsers: true, AllowVolumeBrowserForRegularUsers: false, + AllowHostNamespaceForRegularUsers: true, EnableHostManagementFeatures: false, EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds, TemplatesURL: portainer.DefaultTemplatesURL, diff --git a/api/bolt/migrator/migrate_dbversion23.go b/api/bolt/migrator/migrate_dbversion23.go index f106038b5..74f6436cf 100644 --- a/api/bolt/migrator/migrate_dbversion23.go +++ b/api/bolt/migrator/migrate_dbversion23.go @@ -1,6 +1,12 @@ package migrator func (m *Migrator) updateSettingsToDB24() error { - // Placeholder for 1.24.1 backports - return nil + legacySettings, err := m.settingsService.Settings() + if err != nil { + return err + } + + legacySettings.AllowHostNamespaceForRegularUsers = true + + return m.settingsService.UpdateSettings(legacySettings) } diff --git a/api/http/handler/settings/settings_public.go b/api/http/handler/settings/settings_public.go index 5af534d45..3dfcd5325 100644 --- a/api/http/handler/settings/settings_public.go +++ b/api/http/handler/settings/settings_public.go @@ -18,6 +18,7 @@ type publicSettingsResponse struct { EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"` EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"` OAuthLoginURI string `json:"OAuthLoginURI"` + AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"` } // GET request on /api/settings/public @@ -33,6 +34,7 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) * AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers, AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers, AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers, + AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers, EnableHostManagementFeatures: settings.EnableHostManagementFeatures, EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures, OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login", diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index d0a7f896a..a0a05c9e8 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -22,6 +22,7 @@ type settingsUpdatePayload struct { OAuthSettings *portainer.OAuthSettings AllowBindMountsForRegularUsers *bool AllowPrivilegedModeForRegularUsers *bool + AllowHostNamespaceForRegularUsers *bool AllowVolumeBrowserForRegularUsers *bool EnableHostManagementFeatures *bool SnapshotInterval *string @@ -125,6 +126,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) * settings.EnableEdgeComputeFeatures = *payload.EnableEdgeComputeFeatures } + if payload.AllowHostNamespaceForRegularUsers != nil { + settings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers + } + if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval { err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval) if err != nil { diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index 37d8c1d7c..0e7f1c2e3 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -336,7 +336,11 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig) return err } - if (!settings.AllowBindMountsForRegularUsers || !settings.AllowPrivilegedModeForRegularUsers) && !isAdminOrEndpointAdmin { + if (!settings.AllowBindMountsForRegularUsers || + !settings.AllowPrivilegedModeForRegularUsers || + !settings.AllowHostNamespaceForRegularUsers) && + !isAdminOrEndpointAdmin { + composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint) stackContent, err := handler.FileService.GetFileContent(composeFilePath) diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go index a785ff8a2..2da04589e 100644 --- a/api/http/handler/stacks/stack_create.go +++ b/api/http/handler/stacks/stack_create.go @@ -142,6 +142,10 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port if !settings.AllowPrivilegedModeForRegularUsers && service.Privileged == true { return errors.New("privileged mode disabled for non administrator users") } + + if !settings.AllowHostNamespaceForRegularUsers && service.Pid == "host" { + return errors.New("pid host disabled for non administrator users") + } } return nil diff --git a/api/http/proxy/factory/docker/containers.go b/api/http/proxy/factory/docker/containers.go index 519282be4..cdf1fdb8a 100644 --- a/api/http/proxy/factory/docker/containers.go +++ b/api/http/proxy/factory/docker/containers.go @@ -158,10 +158,15 @@ func containerHasBlackListedLabel(containerLabels map[string]interface{}, labelB func (transport *Transport) decorateContainerCreationOperation(request *http.Request, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType) (*http.Response, error) { type PartialContainer struct { HostConfig struct { - Privileged bool `json:"Privileged"` + Privileged bool `json:"Privileged"` + PidMode string `json:"PidMode"` } `json:"HostConfig"` } + forbiddenResponse := &http.Response{ + StatusCode: http.StatusForbidden, + } + tokenData, err := security.RetrieveTokenData(request) if err != nil { return nil, err @@ -189,24 +194,26 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req return nil, err } - if !settings.AllowPrivilegedModeForRegularUsers { - body, err := ioutil.ReadAll(request.Body) - if err != nil { - return nil, err - } - - partialContainer := &PartialContainer{} - err = json.Unmarshal(body, partialContainer) - if err != nil { - return nil, err - } - - if partialContainer.HostConfig.Privileged { - return nil, errors.New("forbidden to use privileged mode") - } - - request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return nil, err } + + partialContainer := &PartialContainer{} + err = json.Unmarshal(body, partialContainer) + if err != nil { + return nil, err + } + + if !settings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged { + return forbiddenResponse, errors.New("forbidden to use privileged mode") + } + + if !settings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" { + return forbiddenResponse, errors.New("forbidden to use pid host namespace") + } + + request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) } response, err := transport.executeDockerRequest(request) diff --git a/api/portainer.go b/api/portainer.go index fa2a8afcc..8ff870800 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -524,6 +524,7 @@ type ( EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"` EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"` UserSessionTimeout string `json:"UserSessionTimeout"` + AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"` // Deprecated fields DisplayDonationHeader bool diff --git a/app/portainer/models/settings.js b/app/portainer/models/settings.js index f7498f81b..49cdd0faa 100644 --- a/app/portainer/models/settings.js +++ b/app/portainer/models/settings.js @@ -13,6 +13,7 @@ export function SettingsViewModel(data) { this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval; this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures; this.UserSessionTimeout = data.UserSessionTimeout; + this.AllowHostNamespaceForRegularUsers = data.AllowHostNamespaceForRegularUsers; } export function PublicSettingsViewModel(settings) { diff --git a/app/portainer/services/stateManager.js b/app/portainer/services/stateManager.js index 43a021cce..9f57c2959 100644 --- a/app/portainer/services/stateManager.js +++ b/app/portainer/services/stateManager.js @@ -76,6 +76,11 @@ angular.module('portainer.app').factory('StateManager', [ LocalStorage.storeApplicationState(state.application); }; + manager.updateAllowHostNamespaceForRegularUsers = function (allowHostNamespaceForRegularUsers) { + state.application.allowHostNamespaceForRegularUsers = allowHostNamespaceForRegularUsers; + LocalStorage.storeApplicationState(state.application); + }; + function assignStateFromStatusAndSettings(status, settings) { state.application.analytics = status.Analytics; state.application.version = status.Version; diff --git a/app/portainer/views/settings/settings.html b/app/portainer/views/settings/settings.html index 07ab23533..0b3f4a848 100644 --- a/app/portainer/views/settings/settings.html +++ b/app/portainer/views/settings/settings.html @@ -108,6 +108,17 @@ +