mirror of https://github.com/portainer/portainer
feat(container): add sysctls setting in the container view (#4910)
* feat(container): add sysctls in the container view (#2756) * feat(container): add setting to restrict sysctl access * feat(endpoint): move sysctl disable setting to security settings * feat(container): add sysctls to container edit view * fix(container) remove unnecessary migration setting Co-authored-by: Owen Kirby <oskirby@gmail.com>pull/4982/head
parent
ac7d819620
commit
d09ae22ba8
|
@ -25,6 +25,8 @@ type endpointSettingsUpdatePayload struct {
|
|||
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"`
|
||||
}
|
||||
|
@ -97,6 +99,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
|
||||
}
|
||||
|
|
|
@ -356,6 +356,7 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig)
|
|||
!securitySettings.AllowPrivilegedModeForRegularUsers ||
|
||||
!securitySettings.AllowHostNamespaceForRegularUsers ||
|
||||
!securitySettings.AllowDeviceMappingForRegularUsers ||
|
||||
!securitySettings.AllowSysctlSettingForRegularUsers ||
|
||||
!securitySettings.AllowContainerCapabilitiesForRegularUsers) &&
|
||||
!isAdminOrEndpointAdmin {
|
||||
|
||||
|
|
|
@ -192,6 +192,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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -335,6 +335,8 @@ type (
|
|||
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"`
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
EntrypointMode: 'default',
|
||||
NodeName: null,
|
||||
capabilities: [],
|
||||
Sysctls: [],
|
||||
LogDriverName: '',
|
||||
LogDriverOpts: [],
|
||||
RegistryModel: new PorImageRegistryModel(),
|
||||
|
@ -136,6 +137,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
Devices: [],
|
||||
CapAdd: [],
|
||||
CapDrop: [],
|
||||
Sysctls: {},
|
||||
},
|
||||
NetworkingConfig: {
|
||||
EndpointsConfig: {},
|
||||
|
@ -191,6 +193,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: '' });
|
||||
};
|
||||
|
@ -344,6 +354,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) {
|
||||
|
@ -412,6 +432,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
prepareResources(config);
|
||||
prepareLogDriver(config);
|
||||
prepareCapabilities(config);
|
||||
prepareSysctls(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
@ -557,6 +578,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) => {
|
||||
|
@ -632,6 +661,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');
|
||||
|
@ -656,6 +686,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.showDeviceMapping = await shouldShowDevices();
|
||||
$scope.showSysctls = await shouldShowSysctls();
|
||||
$scope.areContainerCapabilitiesEnabled = await checkIfContainerCapabilitiesEnabled();
|
||||
$scope.isAdminOrEndpointAdmin = Authentication.isAdmin();
|
||||
|
||||
|
@ -940,6 +971,12 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
|||
return endpoint.SecuritySettings.allowDeviceMappingForRegularUsers || Authentication.isAdmin();
|
||||
}
|
||||
|
||||
async function shouldShowSysctls() {
|
||||
const { allowSysctlSettingForRegularUsers } = $scope.applicationState.application;
|
||||
|
||||
return allowSysctlSettingForRegularUsers || Authentication.isAdmin();
|
||||
}
|
||||
|
||||
async function checkIfContainerCapabilitiesEnabled() {
|
||||
return endpoint.SecuritySettings.allowContainerCapabilitiesForRegularUsers || Authentication.isAdmin();
|
||||
}
|
||||
|
|
|
@ -706,6 +706,33 @@
|
|||
<!-- !devices-input-list -->
|
||||
</div>
|
||||
<!-- !devices-->
|
||||
<!-- sysctls -->
|
||||
<div ng-if="showSysctls" class="form-group">
|
||||
<div class="col-sm-12" style="margin-top: 5px;">
|
||||
<label class="control-label text-left">Sysctls</label>
|
||||
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addSysctl()">
|
||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> add sysctl
|
||||
</span>
|
||||
</div>
|
||||
<!-- sysctls-input-list -->
|
||||
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
|
||||
<div ng-repeat="sysctl in formValues.Sysctls" style="margin-top: 2px;">
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">name</span>
|
||||
<input type="text" class="form-control" ng-model="sysctl.name" placeholder="e.g. FOO" />
|
||||
</div>
|
||||
<div class="input-group col-sm-5 input-group-sm">
|
||||
<span class="input-group-addon">value</span>
|
||||
<input type="text" class="form-control" ng-model="sysctl.value" placeholder="e.g. bar" />
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="removeSysctl($index)">
|
||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !sysctls-input-list -->
|
||||
</div>
|
||||
<!-- !sysctls -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Resources
|
||||
</div>
|
||||
|
|
|
@ -274,6 +274,17 @@
|
|||
</container-restart-policy>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!(container.HostConfig.Sysctls | emptyobject)">
|
||||
<td>Sysctls</td>
|
||||
<td>
|
||||
<table class="table table-bordered table-condensed">
|
||||
<tr ng-repeat="(k, v) in container.HostConfig.Sysctls">
|
||||
<td>{{ k }}</td>
|
||||
<td>{{ v }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
|
|
|
@ -104,6 +104,7 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
allowContainerCapabilitiesForRegularUsers,
|
||||
allowHostNamespaceForRegularUsers,
|
||||
allowDeviceMappingForRegularUsers,
|
||||
allowSysctlSettingForRegularUsers,
|
||||
allowBindMountsForRegularUsers,
|
||||
allowPrivilegedModeForRegularUsers,
|
||||
} = endpoint.SecuritySettings;
|
||||
|
@ -112,6 +113,7 @@ angular.module('portainer.docker').controller('ContainerController', [
|
|||
!allowContainerCapabilitiesForRegularUsers ||
|
||||
!allowBindMountsForRegularUsers ||
|
||||
!allowDeviceMappingForRegularUsers ||
|
||||
!allowSysctlSettingForRegularUsers ||
|
||||
!allowHostNamespaceForRegularUsers ||
|
||||
!allowPrivilegedModeForRegularUsers;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ export default class DockerFeaturesConfigurationController {
|
|||
disableStackManagementForRegularUsers: false,
|
||||
disableDeviceMappingForRegularUsers: false,
|
||||
disableContainerCapabilitiesForRegularUsers: false,
|
||||
disableSysctlSettingForRegularUsers: false,
|
||||
};
|
||||
|
||||
this.isAgent = false;
|
||||
|
@ -33,13 +34,15 @@ export default class DockerFeaturesConfigurationController {
|
|||
disablePrivilegedModeForRegularUsers,
|
||||
disableDeviceMappingForRegularUsers,
|
||||
disableContainerCapabilitiesForRegularUsers,
|
||||
disableSysctlSettingForRegularUsers,
|
||||
} = this.formValues;
|
||||
return (
|
||||
disableBindMountsForRegularUsers ||
|
||||
disableHostNamespaceForRegularUsers ||
|
||||
disablePrivilegedModeForRegularUsers ||
|
||||
disableDeviceMappingForRegularUsers ||
|
||||
disableContainerCapabilitiesForRegularUsers
|
||||
disableContainerCapabilitiesForRegularUsers ||
|
||||
disableSysctlSettingForRegularUsers
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -56,6 +59,7 @@ export default class DockerFeaturesConfigurationController {
|
|||
allowDeviceMappingForRegularUsers: !this.formValues.disableDeviceMappingForRegularUsers,
|
||||
allowStackManagementForRegularUsers: !this.formValues.disableStackManagementForRegularUsers,
|
||||
allowContainerCapabilitiesForRegularUsers: !this.formValues.disableContainerCapabilitiesForRegularUsers,
|
||||
allowSysctlSettingForRegularUsers: !this.formValues.disableSysctlSettingForRegularUsers,
|
||||
};
|
||||
|
||||
await this.EndpointService.updateSecuritySettings(this.endpoint.Id, securitySettings);
|
||||
|
@ -89,6 +93,7 @@ export default class DockerFeaturesConfigurationController {
|
|||
disableDeviceMappingForRegularUsers: !securitySettings.allowDeviceMappingForRegularUsers,
|
||||
disableStackManagementForRegularUsers: !securitySettings.allowStackManagementForRegularUsers,
|
||||
disableContainerCapabilitiesForRegularUsers: !securitySettings.allowContainerCapabilitiesForRegularUsers,
|
||||
disableSysctlSettingForRegularUsers: !securitySettings.allowSysctlSettingForRegularUsers,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,16 @@
|
|||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<por-switch-field
|
||||
ng-model="$ctrl.formValues.disableSysctlSettingForRegularUsers"
|
||||
name="disableSysctlSettingForRegularUsers"
|
||||
label="Disable sysctl settings for non-administrators"
|
||||
label-class="col-sm-7 col-lg-4"
|
||||
></por-switch-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="$ctrl.isContainerEditDisabled()">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
|
|
|
@ -53,7 +53,11 @@
|
|||
<div
|
||||
class="col-sm-11 small text-warning"
|
||||
style="margin-top: 5px;"
|
||||
ng-show="kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$invalid || (!entry.Used && $ctrl.state.duplicateKeys[index] !== undefined) || $ctrl.state.invalidKeys[index]"
|
||||
ng-show="
|
||||
kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$invalid ||
|
||||
(!entry.Used && $ctrl.state.duplicateKeys[index] !== undefined) ||
|
||||
$ctrl.state.invalidKeys[index]
|
||||
"
|
||||
>
|
||||
<ng-messages for="kubernetesConfigurationDataCreationForm['configuration_data_key_' + index].$error">
|
||||
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
KubernetesPortainerApplicationNote,
|
||||
} from 'Kubernetes/models/application/models';
|
||||
|
||||
import { createPayloadFactory } from './payloads/create';
|
||||
import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity, KubernetesPodContainer, KubernetesPodContainerTypes, KubernetesPodEviction } from 'Kubernetes/pod/models';
|
||||
import { createPayloadFactory } from './payloads/create';
|
||||
|
||||
function computeStatus(statuses) {
|
||||
const containerStatuses = _.map(statuses, 'state');
|
||||
|
|
|
@ -213,6 +213,7 @@ describe('startContainerController', function () {
|
|||
CgroupPermissions: 'mrw',
|
||||
},
|
||||
],
|
||||
Sysctls: { 'net.ipv6.conf.all.disable_ipv6': '0' },
|
||||
LxcConf: { 'lxc.utsname': 'docker' },
|
||||
ExtraHosts: ['hostname:127.0.0.1'],
|
||||
RestartPolicy: { name: 'always', MaximumRetryCount: 5 },
|
||||
|
@ -255,6 +256,7 @@ describe('startContainerController', function () {
|
|||
CgroupPermissions: 'mrw',
|
||||
},
|
||||
];
|
||||
scope.config.HostConfig.Sysctls = [{ name: 'net.ipv6.conf.all.disable_ipv6', value: '0' }];
|
||||
scope.config.HostConfig.LxcConf = [{ name: 'lxc.utsname', value: 'docker' }];
|
||||
scope.config.HostConfig.ExtraHosts = [{ host: 'hostname', ip: '127.0.0.1' }];
|
||||
|
||||
|
|
Loading…
Reference in New Issue