mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
* feat(container-creation): container add/drop capabilities on creation * feat(container-creation): capabilities are now loaded on edit/duplicate/update
This commit is contained in:
parent
5222413532
commit
9c0b568773
5 changed files with 157 additions and 2 deletions
|
@ -0,0 +1,6 @@
|
||||||
|
angular.module('portainer.docker').component('containerCapabilities', {
|
||||||
|
templateUrl: 'app/docker/components/container-capabilities/containerCapabilities.html',
|
||||||
|
bindings: {
|
||||||
|
capabilities: '='
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
<form class="form-horizontal" style="margin-top: 15px;">
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Container capabilities
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div ng-repeat="cap in $ctrl.capabilities">
|
||||||
|
<div class="col-xs-8 col-sm-3 col-md-2">
|
||||||
|
<label for="capability" class="control-label text-left">
|
||||||
|
{{ cap.capability }}
|
||||||
|
<portainer-tooltip position="bottom" message="{{ cap.description }}"></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4 col-sm-2 col-md-1">
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" name="capability" ng-model="cap.allowed"><i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-0 col-sm-1 col-md-1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
90
app/docker/models/containerCapabilities.js
Normal file
90
app/docker/models/containerCapabilities.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
var capDesc = {
|
||||||
|
'SETPCAP': 'Modify process capabilities.',
|
||||||
|
'MKNOD': 'Create special files using mknod(2).',
|
||||||
|
'AUDIT_WRITE': 'Write records to kernel auditing log.',
|
||||||
|
'CHOWN': 'Make arbitrary changes to file UIDs and GIDs (see chown(2)).',
|
||||||
|
'NET_RAW': 'Use RAW and PACKET sockets.',
|
||||||
|
'DAC_OVERRIDE': 'Bypass file read, write, and execute permission checks.',
|
||||||
|
'FOWNER': 'Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file.',
|
||||||
|
'FSETID': 'Don’t clear set-user-ID and set-group-ID permission bits when a file is modified.',
|
||||||
|
'KILL': 'Bypass permission checks for sending signals.',
|
||||||
|
'SETGID': 'Make arbitrary manipulations of process GIDs and supplementary GID list.',
|
||||||
|
'SETUID': 'Make arbitrary manipulations of process UIDs.',
|
||||||
|
'NET_BIND_SERVICE': 'Bind a socket to internet domain privileged ports (port numbers less than 1024).',
|
||||||
|
'SYS_CHROOT': 'Use chroot(2), change root directory.',
|
||||||
|
'SETFCAP': 'Set file capabilities.',
|
||||||
|
'SYS_MODULE': 'Load and unload kernel modules.',
|
||||||
|
'SYS_RAWIO': 'Perform I/O port operations (iopl(2) and ioperm(2)).',
|
||||||
|
'SYS_PACCT': 'Use acct(2), switch process accounting on or off.',
|
||||||
|
'SYS_ADMIN': 'Perform a range of system administration operations.',
|
||||||
|
'SYS_NICE': 'Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes.',
|
||||||
|
'SYS_RESOURCE': 'Override resource Limits.',
|
||||||
|
'SYS_TIME': 'Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock.',
|
||||||
|
'SYS_TTY_CONFIG': 'Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals.',
|
||||||
|
'AUDIT_CONTROL': 'Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules.',
|
||||||
|
'MAC_ADMIN': 'Allow MAC configuration or state changes. Implemented for the Smack LSM.',
|
||||||
|
'MAC_OVERRIDE': 'Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM).',
|
||||||
|
'NET_ADMIN': 'Perform various network-related operations.',
|
||||||
|
'SYSLOG': 'Perform privileged syslog(2) operations.',
|
||||||
|
'DAC_READ_SEARCH': 'Bypass file read permission checks and directory read and execute permission checks.',
|
||||||
|
'LINUX_IMMUTABLE': 'Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags.',
|
||||||
|
'NET_BROADCAST': 'Make socket broadcasts, and listen to multicasts.',
|
||||||
|
'IPC_LOCK': 'Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)).',
|
||||||
|
'IPC_OWNER': 'Bypass permission checks for operations on System V IPC objects.',
|
||||||
|
'SYS_PTRACE': 'Trace arbitrary processes using ptrace(2).',
|
||||||
|
'SYS_BOOT': 'Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution.',
|
||||||
|
'LEASE': 'Establish leases on arbitrary files (see fcntl(2)).',
|
||||||
|
'WAKE_ALARM': 'Trigger something that will wake up the system.',
|
||||||
|
'BLOCK_SUSPEND': 'Employ features that can block system suspend.'
|
||||||
|
};
|
||||||
|
|
||||||
|
function ContainerCapabilities() {
|
||||||
|
// all capabilities can be found at https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
||||||
|
return [
|
||||||
|
new ContainerCapability('SETPCAP', true),
|
||||||
|
new ContainerCapability('MKNOD', true),
|
||||||
|
new ContainerCapability('AUDIT_WRITE', true),
|
||||||
|
new ContainerCapability('CHOWN', true),
|
||||||
|
new ContainerCapability('NET_RAW', true),
|
||||||
|
new ContainerCapability('DAC_OVERRIDE', true),
|
||||||
|
new ContainerCapability('FOWNER', true),
|
||||||
|
new ContainerCapability('FSETID', true),
|
||||||
|
new ContainerCapability('KILL', true),
|
||||||
|
new ContainerCapability('SETGID', true),
|
||||||
|
new ContainerCapability('SETUID', true),
|
||||||
|
new ContainerCapability('NET_BIND_SERVICE', true),
|
||||||
|
new ContainerCapability('SYS_CHROOT', true),
|
||||||
|
new ContainerCapability('SETFCAP', true),
|
||||||
|
new ContainerCapability('SYS_MODULE', false),
|
||||||
|
new ContainerCapability('SYS_RAWIO', false),
|
||||||
|
new ContainerCapability('SYS_PACCT', false),
|
||||||
|
new ContainerCapability('SYS_ADMIN', false),
|
||||||
|
new ContainerCapability('SYS_NICE', false),
|
||||||
|
new ContainerCapability('SYS_RESOURCE', false),
|
||||||
|
new ContainerCapability('SYS_TIME', false),
|
||||||
|
new ContainerCapability('SYS_TTY_CONFIG', false),
|
||||||
|
new ContainerCapability('AUDIT_CONTROL', false),
|
||||||
|
new ContainerCapability('MAC_ADMIN', false),
|
||||||
|
new ContainerCapability('MAC_OVERRIDE', false),
|
||||||
|
new ContainerCapability('NET_ADMIN', false),
|
||||||
|
new ContainerCapability('SYSLOG', false),
|
||||||
|
new ContainerCapability('DAC_READ_SEARCH', false),
|
||||||
|
new ContainerCapability('LINUX_IMMUTABLE', false),
|
||||||
|
new ContainerCapability('NET_BROADCAST', false),
|
||||||
|
new ContainerCapability('IPC_LOCK', false),
|
||||||
|
new ContainerCapability('IPC_OWNER', false),
|
||||||
|
new ContainerCapability('SYS_PTRACE', false),
|
||||||
|
new ContainerCapability('SYS_BOOT', false),
|
||||||
|
new ContainerCapability('LEASE', false),
|
||||||
|
new ContainerCapability('WAKE_ALARM', false),
|
||||||
|
new ContainerCapability('BLOCK_SUSPEND', false)
|
||||||
|
].sort(function (a, b) {
|
||||||
|
return a.capability < b.capability ? -1 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContainerCapability(cap, allowed) {
|
||||||
|
this.capability = cap;
|
||||||
|
this.allowed = allowed;
|
||||||
|
this.description = capDesc[cap];
|
||||||
|
}
|
|
@ -16,7 +16,8 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
CpuLimit: 0,
|
CpuLimit: 0,
|
||||||
MemoryLimit: 0,
|
MemoryLimit: 0,
|
||||||
MemoryReservation: 0,
|
MemoryReservation: 0,
|
||||||
NodeName: null
|
NodeName: null,
|
||||||
|
capabilities: []
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.extraNetworks = {};
|
$scope.extraNetworks = {};
|
||||||
|
@ -48,7 +49,9 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
NetworkMode: 'bridge',
|
NetworkMode: 'bridge',
|
||||||
Privileged: false,
|
Privileged: false,
|
||||||
ExtraHosts: [],
|
ExtraHosts: [],
|
||||||
Devices:[]
|
Devices: [],
|
||||||
|
CapAdd: [],
|
||||||
|
CapDrop: []
|
||||||
},
|
},
|
||||||
NetworkingConfig: {
|
NetworkingConfig: {
|
||||||
EndpointsConfig: {}
|
EndpointsConfig: {}
|
||||||
|
@ -251,6 +254,15 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareCapabilities(config) {
|
||||||
|
var allowed = $scope.formValues.capabilities.filter(function(item) {return item.allowed === true;});
|
||||||
|
var notAllowed = $scope.formValues.capabilities.filter(function(item) {return item.allowed === false;});
|
||||||
|
|
||||||
|
var getCapName = function(item) {return item.capability;};
|
||||||
|
config.HostConfig.CapAdd = allowed.map(getCapName);
|
||||||
|
config.HostConfig.CapDrop = notAllowed.map(getCapName);
|
||||||
|
}
|
||||||
|
|
||||||
function prepareConfiguration() {
|
function prepareConfiguration() {
|
||||||
var config = angular.copy($scope.config);
|
var config = angular.copy($scope.config);
|
||||||
config.Cmd = ContainerHelper.commandStringToArray(config.Cmd);
|
config.Cmd = ContainerHelper.commandStringToArray(config.Cmd);
|
||||||
|
@ -263,6 +275,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
prepareLabels(config);
|
prepareLabels(config);
|
||||||
prepareDevices(config);
|
prepareDevices(config);
|
||||||
prepareResources(config);
|
prepareResources(config);
|
||||||
|
prepareCapabilities(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,6 +491,22 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadFromContainerCapabilities(d) {
|
||||||
|
if (d.HostConfig.CapAdd) {
|
||||||
|
d.HostConfig.CapAdd.forEach(function(cap) {
|
||||||
|
$scope.formValues.capabilities.push(new ContainerCapability(cap, true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (d.HostConfig.CapDrop) {
|
||||||
|
d.HostConfig.CapDrop.forEach(function(cap) {
|
||||||
|
$scope.formValues.capabilities.push(new ContainerCapability(cap, false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$scope.formValues.capabilities.sort(function(a, b) {
|
||||||
|
return a.capability < b.capability ? -1 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function loadFromContainerSpec() {
|
function loadFromContainerSpec() {
|
||||||
// Get container
|
// Get container
|
||||||
Container.get({ id: $transition$.params().from }).$promise
|
Container.get({ id: $transition$.params().from }).$promise
|
||||||
|
@ -498,6 +527,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
loadFromContainerDevices(d);
|
loadFromContainerDevices(d);
|
||||||
loadFromContainerImageConfig(d);
|
loadFromContainerImageConfig(d);
|
||||||
loadFromContainerResources(d);
|
loadFromContainerResources(d);
|
||||||
|
loadFromContainerCapabilities(d);
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve container');
|
Notifications.error('Failure', err, 'Unable to retrieve container');
|
||||||
|
@ -543,6 +573,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai
|
||||||
} else {
|
} else {
|
||||||
$scope.fromContainer = {};
|
$scope.fromContainer = {};
|
||||||
$scope.formValues.Registry = {};
|
$scope.formValues.Registry = {};
|
||||||
|
$scope.formValues.capabilities = new ContainerCapabilities();
|
||||||
}
|
}
|
||||||
}, function(e) {
|
}, function(e) {
|
||||||
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
Notifications.error('Failure', e, 'Unable to retrieve running containers');
|
||||||
|
|
|
@ -152,6 +152,7 @@
|
||||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||||
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
||||||
<li class="interactive"><a data-target="#runtime-resources" ng-click="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
<li class="interactive"><a data-target="#runtime-resources" ng-click="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||||
|
<li class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- tab-content -->
|
<!-- tab-content -->
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
@ -585,6 +586,11 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tab-runtime-resources -->
|
<!-- !tab-runtime-resources -->
|
||||||
|
<!-- tab-container-capabilities -->
|
||||||
|
<div class="tab-pane" id="container-capabilities">
|
||||||
|
<container-capabilities capabilities="formValues.capabilities" ></container-capabilities>
|
||||||
|
</div>
|
||||||
|
<!-- !tab-container-capabilities -->
|
||||||
</div>
|
</div>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
</rd-widget>
|
</rd-widget>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue