Skip to content

Commit

Permalink
chown opt-out
Browse files Browse the repository at this point in the history
  • Loading branch information
mlin committed Dec 9, 2022
1 parent ece0d9d commit 2882743
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 68 deletions.
75 changes: 41 additions & 34 deletions WDL/runtime/backend/docker_swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,42 +554,49 @@ def chown(self, logger: logging.Logger, client: docker.DockerClient, success: bo
dependent). We do this in a funny way via Docker; see GitHub issue #271 for discussion of
alternatives and their problems.
"""
if not self.cfg["task_runtime"].get_bool("as_user") and (os.geteuid() or os.getegid()):
paste = shlex.quote(
os.path.join(
self.container_dir, f"work{self.try_counter if self.try_counter > 1 else ''}"
)
if (
not self.cfg.get_bool("file_io", "chown")
or self.cfg["task_runtime"].get_bool("as_user")
or (os.geteuid() == 0 and os.getegid() == 0)
):
return
paste = shlex.quote(
os.path.join(
self.container_dir, f"work{self.try_counter if self.try_counter > 1 else ''}"
)
script = f"""
(find {paste} -type d -print0 && find {paste} -type f -print0 \
&& find {paste} -type l -print0) \
| xargs -0 -P 10 chown -Ph {os.geteuid()}:{os.getegid()}
""".strip()
volumes = {self.host_dir: {"bind": self.container_dir, "mode": "rw"}}
logger.debug(_("post-task chown", script=script, volumes=volumes))
)
script = f"""
(find {paste} -type d -print0 && find {paste} -type f -print0 \
&& find {paste} -type l -print0) \
| xargs -0 -P 10 chown -Ph {os.geteuid()}:{os.getegid()}
""".strip()
volumes = {self.host_dir: {"bind": self.container_dir, "mode": "rw"}}
logger.debug(_("post-task chown", script=script, volumes=volumes))
try:
chowner = None
try:
chowner = None
try:
chowner = client.containers.run(
"alpine:3",
name=self.unique_service_name("chown-" + self.run_id),
command=["/bin/ash", "-eo", "pipefail", "-c", script],
volumes=volumes,
detach=True,
)
chowner_status = chowner.wait()
assert (
isinstance(chowner_status, dict)
and chowner_status.get("StatusCode", -1) == 0
), f"post-task chown failed: {chowner_status}"
finally:
if chowner:
chowner.remove()
except Exception as exn:
logger.debug(traceback.format_exc())
if success:
raise
logger.error(_("post-task chown also failed", exception=str(exn)))
chowner = client.containers.run(
"alpine:3",
name=self.unique_service_name("chown-" + self.run_id),
command=["/bin/ash", "-eo", "pipefail", "-c", script],
volumes=volumes,
detach=True,
)
chowner_status = chowner.wait()
assert (
isinstance(chowner_status, dict) and chowner_status.get("StatusCode", -1) == 0
), (
f"post-task chown failed: {chowner_status}"
+ "; try setting [file_io] chown = false"
)
finally:
if chowner:
chowner.remove()
except Exception as exn:
logger.debug(traceback.format_exc())
if success:
raise
logger.error(_("post-task chown also failed", exception=str(exn)))

def unique_service_name(self, run_id: str) -> str:
# We need to give each service a name unique on the swarm; collisions cause the service
Expand Down
78 changes: 44 additions & 34 deletions WDL/runtime/backend/podman.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def global_init(cls, cfg: config.Loader, logger: logging.Logger) -> None:
except subprocess.CalledProcessError as cpe:
logger.error(_(" ".join(podman_version_cmd), stderr=cpe.stderr.strip().split("\n")))
msg = f"Unable to check `{' '.join(podman_version_cmd)}`; verify Podman installation"
if "sudo" in podman_version_cmd:
if podman_version_cmd[0] == "sudo":
msg += " with no-password sudo"
raise RuntimeError(msg) from None

Expand All @@ -54,7 +54,7 @@ def cli_exe(self) -> List[str]:

def _pull_invocation(self, logger: logging.Logger, cleanup: ExitStack) -> Tuple[str, List[str]]:
image, invocation = super()._pull_invocation(logger, cleanup)
if "sudo" in invocation:
if invocation[0] == "sudo":
_sudo_canary()
return (image, invocation)

Expand All @@ -68,7 +68,7 @@ def _run_invocation(self, logger: logging.Logger, cleanup: ExitStack, image: str
"--workdir",
os.path.join(self.container_dir, "work"),
]
if "sudo" in ans:
if ans[0] == "sudo":
_sudo_canary()

cpu = self.runtime_values.get("cpu", 0)
Expand Down Expand Up @@ -111,39 +111,49 @@ def _run_invocation(self, logger: logging.Logger, cleanup: ExitStack, image: str
return ans

def _chown(self, logger: logging.Logger):
if not self.cfg.get_bool("task_runtime", "as_user") and (os.geteuid() or os.getegid()):
paste = shlex.quote(
os.path.join(
self.container_dir, f"work{self.try_counter if self.try_counter > 1 else ''}"
)
if (
not self.cfg.get_bool("file_io", "chown")
or self.cfg.get_bool("task_runtime", "as_user")
or (os.geteuid() == 0 and os.getegid() == 0)
):
return
paste = shlex.quote(
os.path.join(
self.container_dir, f"work{self.try_counter if self.try_counter > 1 else ''}"
)
)
script = f"""
(find {paste} -type d -print0 && find {paste} -type f -print0 \
&& find {paste} -type l -print0) \
| xargs -0 -P 10 chown -Ph {os.geteuid()}:{os.getegid()}
""".strip()
try:
subprocess.run(
self.cli_exe
+ [
"run",
"--rm",
"-v",
shlex.quote(f"{self.host_dir}:{self.container_dir}"),
"alpine:3",
"/bin/ash",
"-eo",
"pipefail",
"-c",
script,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=True,
)
script = f"""
(find {paste} -type d -print0 && find {paste} -type f -print0 \
&& find {paste} -type l -print0) \
| xargs -0 -P 10 chown -Ph {os.geteuid()}:{os.getegid()}
""".strip()
try:
subprocess.run(
self.cli_exe
+ [
"run",
"--rm",
"-v",
shlex.quote(f"{self.host_dir}:{self.container_dir}"),
"alpine:3",
"/bin/ash",
"-eo",
"pipefail",
"-c",
script,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=True,
except subprocess.CalledProcessError as cpe:
logger.error(
_(
"post-task chown failed; try setting [file_io] chown = false",
error=cpe.stderr.strip().split("\n"),
)
except subprocess.CalledProcessError as cpe:
logger.error(_("post-task chown failed", error=cpe.stderr.strip().split("\n")))
)


def _sudo_canary():
Expand Down
5 changes: 5 additions & 0 deletions WDL/runtime/config_templates/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ allow_any_input = false
copy_input_files = false
# Selectively copy_input_files for those tasks whose names appear in this list. (New in v1.3.1)
copy_input_files_for = []
# On task container exit, recursively chown the task working directory to the invoking user/group
# (if not root). This avoids leaving root-owned output files when the process(es) inside the
# container run as root. It can be disabled if the host configuration ensures user-owned output
# files in some other way (e.g. "rootless" container engine with user namespacing).
chown = true
# Each succeeded run directory has an "out/" folder containing (by default) a symbolic link to each
# output file in its original working location. If output_hardlinks is true, then out/ is populated
# with hardlinks instead of symlinks. Beware the potential confusion arising from files with
Expand Down

0 comments on commit 2882743

Please sign in to comment.