mirror of https://github.com/docker/compose.git
Add support for seccomp files
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
ec0de7eb68
commit
a6c31b80fe
|
@ -13,9 +13,9 @@ from docker.utils.config import home_dir
|
|||
|
||||
from ..config.environment import Environment
|
||||
from ..const import HTTP_TIMEOUT
|
||||
from ..utils import unquote_path
|
||||
from .errors import UserError
|
||||
from .utils import generate_user_agent
|
||||
from .utils import unquote_path
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -131,14 +131,6 @@ def generate_user_agent():
|
|||
return " ".join(parts)
|
||||
|
||||
|
||||
def unquote_path(s):
|
||||
if not s:
|
||||
return s
|
||||
if s[0] == '"' and s[-1] == '"':
|
||||
return s[1:-1]
|
||||
return s
|
||||
|
||||
|
||||
def human_readable_file_size(size):
|
||||
suffixes = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', ]
|
||||
order = int(math.log(size, 2) / 10) if size else 0
|
||||
|
|
|
@ -40,6 +40,7 @@ from .sort_services import sort_service_dicts
|
|||
from .types import MountSpec
|
||||
from .types import parse_extra_hosts
|
||||
from .types import parse_restart_spec
|
||||
from .types import SecurityOpt
|
||||
from .types import ServiceLink
|
||||
from .types import ServicePort
|
||||
from .types import VolumeFromSpec
|
||||
|
@ -734,9 +735,9 @@ def process_service(service_config):
|
|||
if field in service_dict:
|
||||
service_dict[field] = to_list(service_dict[field])
|
||||
|
||||
service_dict = process_blkio_config(process_ports(
|
||||
service_dict = process_security_opt(process_blkio_config(process_ports(
|
||||
process_healthcheck(service_dict)
|
||||
))
|
||||
)))
|
||||
|
||||
return service_dict
|
||||
|
||||
|
@ -1376,6 +1377,16 @@ def split_path_mapping(volume_path):
|
|||
return (volume_path, None)
|
||||
|
||||
|
||||
def process_security_opt(service_dict):
|
||||
security_opts = service_dict.get('security_opt', [])
|
||||
result = []
|
||||
for value in security_opts:
|
||||
result.append(SecurityOpt.parse(value))
|
||||
if result:
|
||||
service_dict['security_opt'] = result
|
||||
return service_dict
|
||||
|
||||
|
||||
def join_path_mapping(pair):
|
||||
(container, host) = pair
|
||||
if isinstance(host, dict):
|
||||
|
|
|
@ -42,6 +42,7 @@ def serialize_string(dumper, data):
|
|||
yaml.SafeDumper.add_representer(types.MountSpec, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(types.VolumeFromSpec, serialize_config_type)
|
||||
yaml.SafeDumper.add_representer(types.VolumeSpec, serialize_config_type)
|
||||
yaml.SafeDumper.add_representer(types.SecurityOpt, serialize_config_type)
|
||||
yaml.SafeDumper.add_representer(types.ServiceSecret, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(types.ServiceConfig, serialize_dict_type)
|
||||
yaml.SafeDumper.add_representer(types.ServicePort, serialize_dict_type)
|
||||
|
|
|
@ -4,6 +4,7 @@ Types for objects parsed from the configuration.
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import ntpath
|
||||
import os
|
||||
import re
|
||||
|
@ -13,6 +14,7 @@ import six
|
|||
from docker.utils.ports import build_port_bindings
|
||||
|
||||
from ..const import COMPOSEFILE_V1 as V1
|
||||
from ..utils import unquote_path
|
||||
from .errors import ConfigurationError
|
||||
from compose.const import IS_WINDOWS_PLATFORM
|
||||
from compose.utils import splitdrive
|
||||
|
@ -457,3 +459,30 @@ def normalize_port_dict(port):
|
|||
external_ip=port.get('external_ip', ''),
|
||||
has_ext_ip=(':' if port.get('external_ip') else ''),
|
||||
)
|
||||
|
||||
|
||||
class SecurityOpt(namedtuple('_SecurityOpt', 'value src_file')):
|
||||
@classmethod
|
||||
def parse(cls, value):
|
||||
# based on https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697
|
||||
con = value.split('=', 2)
|
||||
if len(con) == 1 and con[0] != 'no-new-privileges':
|
||||
if ':' not in value:
|
||||
raise ConfigurationError('Invalid security_opt: {}'.format(value))
|
||||
con = value.split(':', 2)
|
||||
|
||||
if con[0] == 'seccomp' and con[1] != 'unconfined':
|
||||
try:
|
||||
with open(unquote_path(con[1]), 'r') as f:
|
||||
seccomp_data = json.load(f)
|
||||
except (IOError, ValueError) as e:
|
||||
raise ConfigurationError('Error reading seccomp profile: {}'.format(e))
|
||||
return cls(
|
||||
'seccomp={}'.format(json.dumps(seccomp_data)), con[1]
|
||||
)
|
||||
return cls(value, None)
|
||||
|
||||
def repr(self):
|
||||
if self.src_file is not None:
|
||||
return 'seccomp:{}'.format(self.src_file)
|
||||
return self.value
|
||||
|
|
|
@ -881,6 +881,10 @@ class Service(object):
|
|||
init_path = options.get('init')
|
||||
options['init'] = True
|
||||
|
||||
security_opt = [
|
||||
o.value for o in options.get('security_opt')
|
||||
] if options.get('security_opt') else None
|
||||
|
||||
nano_cpus = None
|
||||
if 'cpus' in options:
|
||||
nano_cpus = int(options.get('cpus') * NANOCPUS_SCALE)
|
||||
|
@ -910,7 +914,7 @@ class Service(object):
|
|||
extra_hosts=options.get('extra_hosts'),
|
||||
read_only=options.get('read_only'),
|
||||
pid_mode=self.pid_mode.mode,
|
||||
security_opt=options.get('security_opt'),
|
||||
security_opt=security_opt,
|
||||
ipc_mode=options.get('ipc'),
|
||||
cgroup_parent=options.get('cgroup_parent'),
|
||||
cpu_quota=options.get('cpu_quota'),
|
||||
|
|
|
@ -143,3 +143,11 @@ def parse_bytes(n):
|
|||
return sdk_parse_bytes(n)
|
||||
except DockerException:
|
||||
return None
|
||||
|
||||
|
||||
def unquote_path(s):
|
||||
if not s:
|
||||
return s
|
||||
if s[0] == '"' and s[-1] == '"':
|
||||
return s[1:-1]
|
||||
return s
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import tempfile
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
@ -1834,3 +1836,35 @@ class ProjectTest(DockerClientTestCase):
|
|||
assert 'svc1' in svc2.get_dependency_names()
|
||||
with pytest.raises(NoHealthCheckConfigured):
|
||||
svc1.is_healthy()
|
||||
|
||||
def test_project_up_seccomp_profile(self):
|
||||
seccomp_data = {
|
||||
'defaultAction': 'SCMP_ACT_ALLOW',
|
||||
'syscalls': []
|
||||
}
|
||||
fd, profile_path = tempfile.mkstemp('_seccomp.json')
|
||||
self.addCleanup(os.remove, profile_path)
|
||||
with os.fdopen(fd, 'w') as f:
|
||||
json.dump(seccomp_data, f)
|
||||
|
||||
config_dict = {
|
||||
'version': '2.3',
|
||||
'services': {
|
||||
'svc1': {
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top',
|
||||
'security_opt': ['seccomp:"{}"'.format(profile_path)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_data = load_config(config_dict)
|
||||
project = Project.from_config(name='composetest', config_data=config_data, client=self.client)
|
||||
project.up()
|
||||
containers = project.containers()
|
||||
assert len(containers) == 1
|
||||
|
||||
remote_secopts = containers[0].get('HostConfig.SecurityOpt')
|
||||
assert len(remote_secopts) == 1
|
||||
assert remote_secopts[0].startswith('seccomp=')
|
||||
assert json.loads(remote_secopts[0].lstrip('seccomp=')) == seccomp_data
|
||||
|
|
|
@ -23,6 +23,7 @@ from .testcases import SWARM_SKIP_CONTAINERS_ALL
|
|||
from .testcases import SWARM_SKIP_CPU_SHARES
|
||||
from compose import __version__
|
||||
from compose.config.types import MountSpec
|
||||
from compose.config.types import SecurityOpt
|
||||
from compose.config.types import VolumeFromSpec
|
||||
from compose.config.types import VolumeSpec
|
||||
from compose.const import IS_WINDOWS_PLATFORM
|
||||
|
@ -238,11 +239,11 @@ class ServiceTest(DockerClientTestCase):
|
|||
}]
|
||||
|
||||
def test_create_container_with_security_opt(self):
|
||||
security_opt = ['label:disable']
|
||||
security_opt = [SecurityOpt.parse('label:disable')]
|
||||
service = self.create_service('db', security_opt=security_opt)
|
||||
container = service.create_container()
|
||||
service.start_container(container)
|
||||
assert set(container.get('HostConfig.SecurityOpt')) == set(security_opt)
|
||||
assert set(container.get('HostConfig.SecurityOpt')) == set([o.repr() for o in security_opt])
|
||||
|
||||
@pytest.mark.xfail(True, reason='Not supported on most drivers')
|
||||
def test_create_container_with_storage_opt(self):
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import unittest
|
||||
|
||||
from compose.cli.utils import unquote_path
|
||||
from compose.utils import unquote_path
|
||||
|
||||
|
||||
class UnquotePathTest(unittest.TestCase):
|
||||
|
|
Loading…
Reference in New Issue