From a158001829667d4e4505abac11f86f8c84b893fa Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Tue, 13 Oct 2015 12:38:43 -0400 Subject: [PATCH 1/4] Added DockerSocketRequirement --- draft-3/cwl-avro.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/draft-3/cwl-avro.yml b/draft-3/cwl-avro.yml index d5af7d5dd..c92e33c25 100644 --- a/draft-3/cwl-avro.yml +++ b/draft-3/cwl-avro.yml @@ -2044,3 +2044,29 @@ the expression engine. The semantics of this field are defined by the underlying expression engine. Intended for uses such as providing function definitions that will be called from CWL expressions. + + +- name: DockerSocketRequirement + type: record + extends: "#ProcessRequirement" + fields: + name: additionalDockerImages + type: + - "null" + - type: array + items: "#DockerRequirement" + doc: | + Require that the command should have access to the Docker socket at + `/var/run/docker.sock`. If the command is itself run in Docker, this means + the Docker socket must be made available inside the Docker container + through a bind mount. + + If this requirement is present, the platform is strongly encouraged to + arrange that file paths are consistent inside and outside the container, as + paths that exist only inside the container cannot be passed to other + containers. + + Note there are inherent security problems with providing access to the + Docker socket, so use of this feature should be limited to trusted + environments running trusted workflows. This feature is provided to support + existing container schemes that rely on Docker-in-Docker functionality. From d989a6becc6ec525c06fa5c610aca9cb42868932 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Tue, 13 Oct 2015 13:29:34 -0400 Subject: [PATCH 2/4] cwltool support for DockerSocketRequirement. --- cwltool/job.py | 12 ++++++++++++ cwltool/main.py | 9 ++++++++- cwltool/process.py | 5 ++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/cwltool/job.py b/cwltool/job.py index d6d7109c7..60427879b 100644 --- a/cwltool/job.py +++ b/cwltool/job.py @@ -55,6 +55,15 @@ def run(self, dry_run=False, pull_image=True, rm_container=True, rm_tmpdir=True, env = os.environ img_id = docker.get_from_requirements(docker_req, docker_is_req, pull_image) + (docker_socket_req, _) = get_feature(self, "DockerSocketRequirement") + + if docker_socket_req: + if kwargs.get("enable_docker_socket") is not True: + raise WorkflowException("DockerSocketRequirement is present but enable_docker_socket is not True") + if docker_socket_req["additionalDockerImages"]: + for d in docker_socket_req["additionalDockerImages"]: + docker.get_from_requirements(docker_req, True, pull_image) + if docker_is_req and img_id is None: raise WorkflowException("Docker is required for running this tool.") @@ -77,6 +86,9 @@ def run(self, dry_run=False, pull_image=True, rm_container=True, rm_tmpdir=True, for t,v in self.environment.items(): runtime.append("--env=%s=%s" % (t, v)) + if docker_socket_req: + runtime.append("--volume=/var/run/docker.sock:/var/run/docker.sock") + runtime.append(img_id) else: env = self.environment diff --git a/cwltool/main.py b/cwltool/main.py index d7db057cb..3efc33bd4 100755 --- a/cwltool/main.py +++ b/cwltool/main.py @@ -77,7 +77,14 @@ def arg_parser(): dest="move_outputs") exgroup = parser.add_mutually_exclusive_group() - exgroup.add_argument("--enable-pull", default=True, action="store_true", + exgroup.add_argument("--enable-docker-socket", default=False, action="store_true", + help="Permit DockerSocketRequirement", dest="enable_docker_socket") + + exgroup.add_argument("--disable-docker-socket", default=False, action="store_false", + help="Disallow DockerSocketRequirement", dest="enable_docker_socket") + + exgroup = parser.add_mutually_exclusive_group() + exgroup.add_argument("--enable-", default=True, action="store_true", help="Try to pull Docker images", dest="enable_pull") exgroup.add_argument("--disable-pull", default=True, action="store_false", diff --git a/cwltool/process.py b/cwltool/process.py index 1d5bbccc0..839489d21 100644 --- a/cwltool/process.py +++ b/cwltool/process.py @@ -28,7 +28,8 @@ "CreateFileRequirement", "ScatterFeatureRequirement", "SubworkflowFeatureRequirement", - "MultipleInputFeatureRequirement"] + "MultipleInputFeatureRequirement", + "DockerSocketRequirement"] def get_schema(): f = resource_stream(__name__, 'schemas/draft-3/cwl-avro.yml') @@ -167,6 +168,8 @@ def _init_job(self, joborder, input_basedir, **kwargs): for r in self.requirements: if r["class"] not in supportedProcessRequirements: raise WorkflowException("Unsupported process requirement %s" % (r["class"])) + if r["class"] == "DockerSocketRequirement" and kwargs.get("enable_docker_socket") is not True: + raise WorkflowException("DockerSocketRequirement is present but enable_docker_socket is not True") builder.files = [] builder.bindings = [] From 17b7bd4d5333a01b3725c3a2a46c6e9bc7d46e78 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Tue, 13 Oct 2015 13:57:30 -0400 Subject: [PATCH 3/4] Fix typo. Add test case. --- draft-2/draft-2/dndtool.cwl | 29 +++++++++++++++++++++++++++++ draft-3/cwl-avro.yml | 10 +++++----- 2 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 draft-2/draft-2/dndtool.cwl diff --git a/draft-2/draft-2/dndtool.cwl b/draft-2/draft-2/dndtool.cwl new file mode 100644 index 000000000..756494f64 --- /dev/null +++ b/draft-2/draft-2/dndtool.cwl @@ -0,0 +1,29 @@ +class: CommandLineTool +description: "Reverse each line using the `rev` command" +reqirements: + - class: DockerRequirement + dockerId: docker_in_docker + dockerFile: | + FROM ubuntu:14.04 + MAINTAINER peter.amstutz@curoverse.com + + RUN apt-get update -qq && apt-get install -qqy \ + apt-transport-https \ + ca-certificates \ + curl \ + lxc \ + iptables \ + python-setuptools + + # Install Docker from Docker Inc. repositories. + RUN curl -sSL https://get.docker.com/ubuntu/ | sh + + - class: DockerSocketRequirement +inputs: [] +outputs: + - id: "#output" + type: File + outputBinding: + glob: output.txt +baseCommand: [docker, version] +stdout: output.txt \ No newline at end of file diff --git a/draft-3/cwl-avro.yml b/draft-3/cwl-avro.yml index c92e33c25..dd8ede900 100644 --- a/draft-3/cwl-avro.yml +++ b/draft-3/cwl-avro.yml @@ -2050,11 +2050,11 @@ type: record extends: "#ProcessRequirement" fields: - name: additionalDockerImages - type: - - "null" - - type: array - items: "#DockerRequirement" + - name: additionalDockerImages + type: + - "null" + - type: array + items: "#DockerRequirement" doc: | Require that the command should have access to the Docker socket at `/var/run/docker.sock`. If the command is itself run in Docker, this means From 1a7bf2a265275724f128418c9d47115fdf6893f2 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Tue, 13 Oct 2015 14:35:59 -0400 Subject: [PATCH 4/4] Set group id to permit accessing docker socket. --- cwltool/job.py | 9 ++++++--- cwltool/main.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cwltool/job.py b/cwltool/job.py index 60427879b..8d2b74d92 100644 --- a/cwltool/job.py +++ b/cwltool/job.py @@ -57,12 +57,15 @@ def run(self, dry_run=False, pull_image=True, rm_container=True, rm_tmpdir=True, (docker_socket_req, _) = get_feature(self, "DockerSocketRequirement") + gid = os.getgid() + if docker_socket_req: if kwargs.get("enable_docker_socket") is not True: raise WorkflowException("DockerSocketRequirement is present but enable_docker_socket is not True") - if docker_socket_req["additionalDockerImages"]: + if docker_socket_req.get("additionalDockerImages"): for d in docker_socket_req["additionalDockerImages"]: docker.get_from_requirements(docker_req, True, pull_image) + gid = os.stat("/var/run/docker.sock").st_gid if docker_is_req and img_id is None: raise WorkflowException("Docker is required for running this tool.") @@ -76,7 +79,7 @@ def run(self, dry_run=False, pull_image=True, rm_container=True, rm_tmpdir=True, runtime.append("--volume=%s:%s:rw" % (os.path.abspath(self.tmpdir), "/tmp/job_tmp")) runtime.append("--workdir=%s" % ("/tmp/job_output")) euid = docker_vm_uid() or os.geteuid() - runtime.append("--user=%s" % (euid)) + runtime.append("--user=%s:%s" % (euid, gid)) if rm_container: runtime.append("--rm") @@ -87,7 +90,7 @@ def run(self, dry_run=False, pull_image=True, rm_container=True, rm_tmpdir=True, runtime.append("--env=%s=%s" % (t, v)) if docker_socket_req: - runtime.append("--volume=/var/run/docker.sock:/var/run/docker.sock") + runtime.append("--volume=/var/run/docker.sock:/var/run/docker.sock:rw") runtime.append(img_id) else: diff --git a/cwltool/main.py b/cwltool/main.py index 3efc33bd4..aa170cd05 100755 --- a/cwltool/main.py +++ b/cwltool/main.py @@ -438,7 +438,8 @@ def main(args=None, tmpdir_prefix=args.tmpdir_prefix, rm_tmpdir=args.rm_tmpdir, makeTool=makeTool, - move_outputs=args.move_outputs + move_outputs=args.move_outputs, + enable_docker_socket=args.enable_docker_socket ) # This is the workflow output, it needs to be written stdout.write(json.dumps(out, indent=4))