Skip to content

Commit

Permalink
Simplify entrypoint script as per feedback.
Browse files Browse the repository at this point in the history
No argument parsing is done in the Python script anymore;
instead, a few environment variables do the trick.
  • Loading branch information
EtiennePerot committed May 19, 2024
1 parent 007270d commit a395281
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 114 deletions.
6 changes: 2 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,8 @@ COPY conversion /opt/dangerzone/dangerzone/conversion
# Add the unprivileged user.
# NOTE: A tmpfs will be mounted over /home/dangerzone directory,
# so nothing within it from the image will be persisted.
ARG DANGERZONE_UID=65042
ARG DANGERZONE_GID=65042
RUN addgroup -g "$DANGERZONE_GID" dangerzone && \
adduser -u "$DANGERZONE_UID" -s /bin/true -G dangerzone -h /home/dangerzone -D dangerzone
RUN addgroup dangerzone && \
adduser -s /bin/true -G dangerzone -h /home/dangerzone -D dangerzone

###########################################
# gVisor wrapper image
Expand Down
149 changes: 39 additions & 110 deletions dangerzone/gvisor_wrapper/entrypoint.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/python3

import argparse
import json
import os
import shlex
Expand All @@ -10,76 +9,25 @@

# This script wraps the command-line arguments passed to it to run as an
# unprivileged user in a gVisor sandbox.
# Its behavior can be modified with the following environment variables:
# RUNSC_DEBUG: If set, print debug messages to stderr, and log all gVisor
# output to stderr.
# RUNSC_FLAGS: If set, pass these flags to the `runsc` invocation.
# These environment variables are not passed on to the sandboxed process.

# Define flags.
parser = argparse.ArgumentParser(
prog="gvisor_wrapper",
description="Run a command in a nested gVisor sandbox",
prefix_chars="-",
fromfile_prefix_chars=None,
)
flags_with_values = []
parser.add_argument(
"--pre_gvisor", action="store_true", help="Run command without gVisor wrapping"
)
parser.add_argument(
"--gvisor_debug", action="store_true", help="Enable gVisor debug logging"
)
parser.add_argument(
"--gvisor_strace", action="store_true", help="Enable system call tracing in gVisor"
)
flags_with_values.append(
parser.add_argument(
"--gvisor_flag",
action="append",
help="Add gVisor flag, may be specified multiple times",
)
)
parser.add_argument(
"command",
nargs=argparse.PARSER,
help="Command to run in the sandbox (or outside if --pre_gvisor is specified)",
)

if len(sys.argv) <= 1: # No arguments: bail.
print("No command line specified.", file=sys.stderr)
parser.print_help()
sys.exit(2)
def log(message: str, *values: typing.Any) -> None:
"""Helper function to log messages if RUNSC_DEBUG is set."""
if os.environ.get("RUNSC_DEBUG"):
print(message.format(*values), file=sys.stderr)

# We can't pass `sys.argv` directly to `parser` here because this may end up
# processing flags from the wrapped command line that happen to match flags
# that are defined in `parser`. For example, if the user were to somehow
# make a file named `--pre_gvisor`, that would be pretty bad.
# So we scan for the first argument that does not look like it is intended
# for this entrypoint script, and we only pass this subset to `parser`.
parser_args = None
wrapped_command = None
next_arg_is_value_of_previous_flag = False
for i, arg in enumerate(sys.argv):
if i == 0:
continue # Skip argv[0]
if next_arg_is_value_of_previous_flag:
next_arg_is_value_of_previous_flag = False
continue
if arg == "--":
parser_args = sys.argv[1:i]
wrapped_command = sys.argv[i + 1 :]
break
if not arg.startswith(parser.prefix_chars):
parser_args = sys.argv[1:i]
wrapped_command = sys.argv[i:]
break
if "=" not in arg:
arg_name = arg.lstrip(parser.prefix_chars)
if any(arg_name == flag.dest for flag in flags_with_values):
next_arg_is_value_of_previous_flag = True
if parser_args is None or wrapped_command is None: # No command specified.
parser_args = sys.argv[1:]
wrapped_command = []

wrapped_command = sys.argv[1:]
if len(wrapped_command) == 0:
log("Invoked without a command; will execute 'sh'.")
wrapped_command = ["sh"]
parser_args.append("command") # To satisfy the parser's `command` argument.
args = parser.parse_args(parser_args)
else:
log("Invoked with command: {}", " ".join(shlex.quote(s) for s in wrapped_command))

# Find the UID/GID of who we should run as within the sandbox.
sandboxed_uid = int(
Expand Down Expand Up @@ -114,7 +62,7 @@
# This can all be removed and simplified once gvisor.dev/issue/9918 is fixed.
gvisor_issue_9918_is_fixed = False
sandbox_capabilities = []
if not gvisor_issue_9918_is_fixed and not args.pre_gvisor:
if not gvisor_issue_9918_is_fixed:
wrapped_command = [
"su-exec",
"%d:%d" % (sandboxed_uid, sandboxed_gid),
Expand Down Expand Up @@ -201,67 +149,48 @@
],
},
}
not_forwarded_env = set(("PATH", "HOME", "SHLVL", "HOSTNAME", "TERM", "PWD"))
not_forwarded_env = set(
(
"PATH",
"HOME",
"SHLVL",
"HOSTNAME",
"TERM",
"PWD",
"RUNSC_FLAGS",
"RUNSC_DEBUG",
)
)
for key_val in oci_config["process"]["env"]:
not_forwarded_env.add(key_val[: key_val.index("=")])
for key, val in os.environ.items():
if key in not_forwarded_env:
continue
oci_config["process"]["env"].append("%s=%s" % (key, val))
if args.gvisor_debug:
print("Command inside gVisor sandbox:", wrapped_command, file=sys.stderr)
print("OCI config:", file=sys.stderr)
if os.environ.get("RUNSC_DEBUG"):
log("Command inside gVisor sandbox: {}", wrapped_command)
log("OCI config:")
json.dump(oci_config, sys.stderr, indent=2, sort_keys=True)
# json.dump doesn't print a trailing newline, so print one here:
print("", file=sys.stderr)
log("")
with open("/dangerzone-image/config.json", "w") as oci_config_out:
json.dump(oci_config, oci_config_out, indent=2, sort_keys=True)

# Run gVisor.
runsc_binary = "/usr/bin/runsc"
runsc_argv = [os.path.basename(runsc_binary), "--rootless=true", "--network=none"]
if args.gvisor_debug:
runsc_argv = ["/usr/bin/runsc", "--rootless=true", "--network=none"]
if os.environ.get("RUNSC_DEBUG"):
runsc_argv += ["--debug=true", "--alsologtostderr=true"]
if args.gvisor_strace:
runsc_argv += ["--strace=true"]
if args.gvisor_flag is not None:
for gvisor_flag in args.gvisor_flag:
if gvisor_flag:
runsc_argv += [gvisor_flag]
if os.environ.get("RUNSC_FLAGS"):
runsc_argv += [x for x in shlex.split(os.environ.get("RUNSC_FLAGS", "")) if x]
runsc_argv += ["run", "--bundle=/dangerzone-image", "dangerzone"]

# Check for `--pre_gvisor` which can be used to run commands without wrapping.
if args.pre_gvisor:
print(
"Would be running the following command if --pre_gvisor had not been specified:",
" ".join(shlex.quote(s) for s in runsc_argv),
file=sys.stderr,
)
print(
"Executing this command instead:",
" ".join(shlex.quote(s) for s in wrapped_command),
file=sys.stderr,
)
try:
os.execvp(wrapped_command[0], wrapped_command)
except Exception as e:
raise e.__class__("Process %s failed: %s" % (wrapped_command, e))
else:
assert False, "This code should never be reachable"

if args.gvisor_debug:
print(
"Running gVisor with command line:",
" ".join(shlex.quote(s) for s in runsc_argv),
file=sys.stderr,
)
log(
"Running gVisor with command line: {}", " ".join(shlex.quote(s) for s in runsc_argv)
)
runsc_process = subprocess.run(
runsc_argv,
executable=runsc_binary,
check=False,
)
if args.gvisor_debug:
print("gVisor quit with exit code:", runsc_process.returncode, file=sys.stderr)
log("gVisor quit with exit code: {}", runsc_process.returncode)

# We're done.
sys.exit(runsc_process.returncode)

0 comments on commit a395281

Please sign in to comment.