diff --git a/api/http/handler/endpoints/endpoint_settings_update.go b/api/http/handler/endpoints/endpoint_settings_update.go index 8684e4e7f..1404c0470 100644 --- a/api/http/handler/endpoints/endpoint_settings_update.go +++ b/api/http/handler/endpoints/endpoint_settings_update.go @@ -12,14 +12,24 @@ import ( ) type endpointSettingsUpdatePayload struct { - AllowBindMountsForRegularUsers *bool `json:"allowBindMountsForRegularUsers"` - AllowPrivilegedModeForRegularUsers *bool `json:"allowPrivilegedModeForRegularUsers"` - AllowVolumeBrowserForRegularUsers *bool `json:"allowVolumeBrowserForRegularUsers"` - AllowHostNamespaceForRegularUsers *bool `json:"allowHostNamespaceForRegularUsers"` - AllowDeviceMappingForRegularUsers *bool `json:"allowDeviceMappingForRegularUsers"` - AllowStackManagementForRegularUsers *bool `json:"allowStackManagementForRegularUsers"` - AllowContainerCapabilitiesForRegularUsers *bool `json:"allowContainerCapabilitiesForRegularUsers"` - EnableHostManagementFeatures *bool `json:"enableHostManagementFeatures"` + // Whether non-administrator should be able to use bind mounts when creating containers + AllowBindMountsForRegularUsers *bool `json:"allowBindMountsForRegularUsers" example:"false"` + // Whether non-administrator should be able to use privileged mode when creating containers + AllowPrivilegedModeForRegularUsers *bool `json:"allowPrivilegedModeForRegularUsers" example:"false"` + // Whether non-administrator should be able to browse volumes + AllowVolumeBrowserForRegularUsers *bool `json:"allowVolumeBrowserForRegularUsers" example:"true"` + // Whether non-administrator should be able to use the host pid + AllowHostNamespaceForRegularUsers *bool `json:"allowHostNamespaceForRegularUsers" example:"true"` + // Whether non-administrator should be able to use device mapping + AllowDeviceMappingForRegularUsers *bool `json:"allowDeviceMappingForRegularUsers" example:"true"` + // Whether non-administrator should be able to manage stacks + AllowStackManagementForRegularUsers *bool `json:"allowStackManagementForRegularUsers" example:"true"` + // Whether non-administrator should be able to use container capabilities + AllowContainerCapabilitiesForRegularUsers *bool `json:"allowContainerCapabilitiesForRegularUsers" example:"true"` + // Whether non-administrator should be able to use sysctl settings + AllowSysctlSettingForRegularUsers *bool `json:"allowSysctlSettingForRegularUsers" example:"true"` + // Whether host management features are enabled + EnableHostManagementFeatures *bool `json:"enableHostManagementFeatures" example:"true"` } func (payload *endpointSettingsUpdatePayload) Validate(r *http.Request) error { @@ -83,6 +93,10 @@ func (handler *Handler) endpointSettingsUpdate(w http.ResponseWriter, r *http.Re securitySettings.AllowVolumeBrowserForRegularUsers = *payload.AllowVolumeBrowserForRegularUsers } + if payload.AllowSysctlSettingForRegularUsers != nil { + securitySettings.AllowSysctlSettingForRegularUsers = *payload.AllowSysctlSettingForRegularUsers + } + if payload.EnableHostManagementFeatures != nil { securitySettings.EnableHostManagementFeatures = *payload.EnableHostManagementFeatures } diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index b0c08979f..1ace49de2 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -349,6 +349,7 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig) !securitySettings.AllowPrivilegedModeForRegularUsers || !securitySettings.AllowHostNamespaceForRegularUsers || !securitySettings.AllowDeviceMappingForRegularUsers || + !securitySettings.AllowSysctlSettingForRegularUsers || !securitySettings.AllowContainerCapabilitiesForRegularUsers) && !isAdminOrEndpointAdmin { diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go index 3115914af..b979dd3cd 100644 --- a/api/http/handler/stacks/stack_create.go +++ b/api/http/handler/stacks/stack_create.go @@ -165,6 +165,10 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, securitySettin return errors.New("device mapping disabled for non administrator users") } + if !securitySettings.AllowSysctlSettingForRegularUsers && service.Sysctls != nil && len(service.Sysctls) > 0 { + return errors.New("sysctl setting disabled for non administrator users") + } + if !securitySettings.AllowContainerCapabilitiesForRegularUsers && (len(service.CapAdd) > 0 || len(service.CapDrop) > 0) { return errors.New("container capabilities disabled for non administrator users") } diff --git a/api/http/proxy/factory/docker/containers.go b/api/http/proxy/factory/docker/containers.go index 9396cc20a..fe85f246c 100644 --- a/api/http/proxy/factory/docker/containers.go +++ b/api/http/proxy/factory/docker/containers.go @@ -152,12 +152,13 @@ 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"` - PidMode string `json:"PidMode"` - Devices []interface{} `json:"Devices"` - CapAdd []string `json:"CapAdd"` - CapDrop []string `json:"CapDrop"` - Binds []string `json:"Binds"` + Privileged bool `json:"Privileged"` + PidMode string `json:"PidMode"` + Devices []interface{} `json:"Devices"` + Sysctls map[string]interface{} `json:"Sysctls"` + CapAdd []string `json:"CapAdd"` + CapDrop []string `json:"CapDrop"` + Binds []string `json:"Binds"` } `json:"HostConfig"` } @@ -204,6 +205,10 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req return forbiddenResponse, errors.New("forbidden to use device mapping") } + if !securitySettings.AllowSysctlSettingForRegularUsers && len(partialContainer.HostConfig.Sysctls) > 0 { + return forbiddenResponse, errors.New("forbidden to use sysctl settings") + } + if !securitySettings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) { return nil, errors.New("forbidden to use container capabilities") } diff --git a/api/portainer.go b/api/portainer.go index 5b7fcc5ad..637f23ca0 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -307,14 +307,24 @@ type ( // EndpointSecuritySettings represents settings for an endpoint EndpointSecuritySettings struct { - AllowBindMountsForRegularUsers bool `json:"allowBindMountsForRegularUsers"` - AllowPrivilegedModeForRegularUsers bool `json:"allowPrivilegedModeForRegularUsers"` - AllowVolumeBrowserForRegularUsers bool `json:"allowVolumeBrowserForRegularUsers"` - AllowHostNamespaceForRegularUsers bool `json:"allowHostNamespaceForRegularUsers"` - AllowDeviceMappingForRegularUsers bool `json:"allowDeviceMappingForRegularUsers"` - AllowStackManagementForRegularUsers bool `json:"allowStackManagementForRegularUsers"` - AllowContainerCapabilitiesForRegularUsers bool `json:"allowContainerCapabilitiesForRegularUsers"` - EnableHostManagementFeatures bool `json:"enableHostManagementFeatures"` + // Whether non-administrator should be able to use bind mounts when creating containers + AllowBindMountsForRegularUsers bool `json:"allowBindMountsForRegularUsers" example:"false"` + // Whether non-administrator should be able to use privileged mode when creating containers + AllowPrivilegedModeForRegularUsers bool `json:"allowPrivilegedModeForRegularUsers" example:"false"` + // Whether non-administrator should be able to browse volumes + AllowVolumeBrowserForRegularUsers bool `json:"allowVolumeBrowserForRegularUsers" example:"true"` + // Whether non-administrator should be able to use the host pid + AllowHostNamespaceForRegularUsers bool `json:"allowHostNamespaceForRegularUsers" example:"true"` + // Whether non-administrator should be able to use device mapping + AllowDeviceMappingForRegularUsers bool `json:"allowDeviceMappingForRegularUsers" example:"true"` + // Whether non-administrator should be able to manage stacks + AllowStackManagementForRegularUsers bool `json:"allowStackManagementForRegularUsers" example:"true"` + // Whether non-administrator should be able to use container capabilities + AllowContainerCapabilitiesForRegularUsers bool `json:"allowContainerCapabilitiesForRegularUsers" example:"true"` + // Whether non-administrator should be able to use sysctl settings + AllowSysctlSettingForRegularUsers bool `json:"AllowSysctlSettingForRegularUsers" example:"true"` + // Whether host management features are enabled + EnableHostManagementFeatures bool `json:"enableHostManagementFeatures" example:"true"` } // EndpointType represents the type of an endpoint diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 070f6e11c..4e9b47aa0 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -79,6 +79,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ EntrypointMode: 'default', NodeName: null, capabilities: [], + Sysctls: [], LogDriverName: '', LogDriverOpts: [], RegistryModel: new PorImageRegistryModel(), @@ -126,6 +127,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ Devices: [], CapAdd: [], CapDrop: [], + Sysctls: {}, }, NetworkingConfig: { EndpointsConfig: {}, @@ -181,6 +183,14 @@ angular.module('portainer.docker').controller('CreateContainerController', [ $scope.config.HostConfig.Devices.splice(index, 1); }; + $scope.addSysctl = function () { + $scope.formValues.Sysctls.push({ name: '', value: '' }); + }; + + $scope.removeSysctl = function (index) { + $scope.formValues.Sysctls.splice(index, 1); + }; + $scope.addLogDriverOpt = function () { $scope.formValues.LogDriverOpts.push({ name: '', value: '' }); }; @@ -334,6 +344,16 @@ angular.module('portainer.docker').controller('CreateContainerController', [ config.HostConfig.Devices = path; } + function prepareSysctls(config) { + var sysctls = {}; + $scope.formValues.Sysctls.forEach(function (sysctl) { + if (sysctl.name && sysctl.value) { + sysctls[sysctl.name] = sysctl.value; + } + }); + config.HostConfig.Sysctls = sysctls; + } + function prepareResources(config) { // Memory Limit - Round to 0.125 if ($scope.formValues.MemoryLimit >= 0) { @@ -402,6 +422,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ prepareResources(config); prepareLogDriver(config); prepareCapabilities(config); + prepareSysctls(config); return config; } @@ -547,6 +568,14 @@ angular.module('portainer.docker').controller('CreateContainerController', [ $scope.config.HostConfig.Devices = path; } + function loadFromContainerSysctls() { + for (var s in $scope.config.HostConfig.Sysctls) { + if ({}.hasOwnProperty.call($scope.config.HostConfig.Sysctls, s)) { + $scope.formValues.Sysctls.push({ name: s, value: $scope.config.HostConfig.Sysctls[s] }); + } + } + } + function loadFromContainerImageConfig() { RegistryService.retrievePorRegistryModelFromRepository($scope.config.Image) .then((model) => { @@ -622,6 +651,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ loadFromContainerImageConfig(d); loadFromContainerResources(d); loadFromContainerCapabilities(d); + loadFromContainerSysctls(d); }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to retrieve container'); @@ -710,6 +740,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ $scope.allowBindMounts = checkIfAdminOrEndpointAdmin() || endpoint.SecuritySettings.allowBindMountsForRegularUsers; $scope.allowPrivilegedMode = checkIfAdminOrEndpointAdmin() || endpoint.SecuritySettings.allowPrivilegedModeForRegularUsers; + $scope.allowSysctl = checkIfAdminOrEndpointAdmin() || endpoint.SecuritySettings.AllowSysctlSettingForRegularUsers; PluginService.loggingPlugins(apiVersion < 1.25).then(function success(loggingDrivers) { $scope.availableLoggingDrivers = loggingDrivers; diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 895f8112d..f82aed772 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -699,6 +699,33 @@ + +
| {{ k }} | +{{ v }} | +