diff --git a/app/docker/components/container-capabilities/container-capabilities.js b/app/docker/components/container-capabilities/container-capabilities.js new file mode 100644 index 000000000..6d572a464 --- /dev/null +++ b/app/docker/components/container-capabilities/container-capabilities.js @@ -0,0 +1,6 @@ +angular.module('portainer.docker').component('containerCapabilities', { + templateUrl: 'app/docker/components/container-capabilities/containerCapabilities.html', + bindings: { + capabilities: '=' + } +}); diff --git a/app/docker/components/container-capabilities/containerCapabilities.html b/app/docker/components/container-capabilities/containerCapabilities.html new file mode 100644 index 000000000..5d1f8401e --- /dev/null +++ b/app/docker/components/container-capabilities/containerCapabilities.html @@ -0,0 +1,22 @@ +
\ No newline at end of file diff --git a/app/docker/models/containerCapabilities.js b/app/docker/models/containerCapabilities.js new file mode 100644 index 000000000..0ff22239e --- /dev/null +++ b/app/docker/models/containerCapabilities.js @@ -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]; +} \ No newline at end of file diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index d45b989be..530c22dd7 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -16,7 +16,8 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai CpuLimit: 0, MemoryLimit: 0, MemoryReservation: 0, - NodeName: null + NodeName: null, + capabilities: [] }; $scope.extraNetworks = {}; @@ -48,7 +49,9 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai NetworkMode: 'bridge', Privileged: false, ExtraHosts: [], - Devices:[] + Devices: [], + CapAdd: [], + CapDrop: [] }, NetworkingConfig: { 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() { var config = angular.copy($scope.config); config.Cmd = ContainerHelper.commandStringToArray(config.Cmd); @@ -263,6 +275,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai prepareLabels(config); prepareDevices(config); prepareResources(config); + prepareCapabilities(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() { // Get container Container.get({ id: $transition$.params().from }).$promise @@ -498,6 +527,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai loadFromContainerDevices(d); loadFromContainerImageConfig(d); loadFromContainerResources(d); + loadFromContainerCapabilities(d); }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to retrieve container'); @@ -543,6 +573,7 @@ function ($q, $scope, $state, $timeout, $transition$, $filter, Container, Contai } else { $scope.fromContainer = {}; $scope.formValues.Registry = {}; + $scope.formValues.capabilities = new ContainerCapabilities(); } }, function(e) { Notifications.error('Failure', e, 'Unable to retrieve running containers'); diff --git a/app/docker/views/containers/create/createcontainer.html b/app/docker/views/containers/create/createcontainer.html index 4c6f957c1..e466f3249 100644 --- a/app/docker/views/containers/create/createcontainer.html +++ b/app/docker/views/containers/create/createcontainer.html @@ -152,6 +152,7 @@