From 8be619448aec22cec8a67eaed6e5d73bc3df8687 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Fri, 8 Jan 2016 07:15:09 -0500 Subject: [PATCH 1/5] add FileCleanup class for centralized file cleanup at exit --- src/scuba | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/scuba b/src/scuba index fc2d0150..82f5fe80 100755 --- a/src/scuba +++ b/src/scuba @@ -31,6 +31,22 @@ def verbose_msg(fmt, *args): if g_verbose: appmsg(fmt, *args) +class FileCleanup(object): + def __init__(self, skip=False): + self.files = [] + if not skip: + atexit.register(self.__cleanup) + + def add(self, path): + self.files.append(path) + + def __cleanup(self): + for f in self.files: + try: + os.remove(f) + except OSError: + pass + # http://stackoverflow.com/a/9577670 class Loader(yaml.Loader): @@ -221,8 +237,7 @@ def generate_scubainit(config, command): # Open a temporary file with NamedTemporaryFile(prefix='scubainit', delete=False) as f: - # Delete this file when scuba exits - atexit.register(os.remove, f.name) + cleanup.add(f.name) def write_cmd(*args): f.write(' '.join(c for c in args) + '\n') @@ -276,6 +291,9 @@ def parse_args(): def main(): args = parse_args() + global cleanup + cleanup = FileCleanup() + # top_path is where .scuba.yml is found, and becomes the top of our bind mount. # top_rel is the relative path from top_path to the current working directory, # and is where we'll set the working directory in the container (relative to From ce402b1bf332f5d808745cef504f3e1a2edb6be1 Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Fri, 8 Jan 2016 09:04:39 -0500 Subject: [PATCH 2/5] inject /etc/passwd and friends instead of .scubainit This is to avoid the fact that .scubainit uses `su -c` to run the user command as scubauser, but now it is no longer PID 1. This fixes #22. --- src/scuba | 97 +++++++++++++++++++++++++------------------------------ 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/src/scuba b/src/scuba index 82f5fe80..23949254 100755 --- a/src/scuba +++ b/src/scuba @@ -216,65 +216,63 @@ def get_umask(): os.umask(val) return val -def generate_scubainit(config, command): - '''Generate a .scubainit script - - This script is executed by /bin/sh, as passed to "docker run". - It is responsible for setting up the environment, and running the - user-specified command. Normally, if the user provides no command to - "docker run", the image's default CMD is run. Because we force - "/bin/sh .scubainit" to be run, scuba must emulate the default behavior - itself. - ''' - if len(command) == 0: - # No user-provided command; we want to run the image's default command - verbose_msg('No user command; getting command from image') - command = get_image_command(config['image']) - verbose_msg('{0} Cmd: "{1}"'.format(config['image'], command)) - # Turn command into a string which you would enter in a shell - command = ' '.join(shell_quote(c) for c in command) +def get_native_opts(): + opts = [] - # Open a temporary file - with NamedTemporaryFile(prefix='scubainit', delete=False) as f: - cleanup.add(f.name) + uid = os.getuid() + gid = os.getgid() + + opts.append('--user={uid}:{gid}'.format(uid=uid, gid=gid)) + + def writeln(f, line): + f.write(line + '\n') - def write_cmd(*args): - f.write(' '.join(c for c in args) + '\n') + # /etc/passwd + with NamedTemporaryFile(prefix='scuba', delete=False) as f: + cleanup.add(f.name) + opts.append(make_vol_opt(f.name, '/etc/passwd', 'z')) - write_cmd('#!/bin/sh') + writeln(f, passwd_entry( + username = 'root', + password = 'x', + uid = 0, + gid = 0, + gecos = 'root', + homedir = '/root', + shell = '/bin/sh', + )) - # Add scubauser with current uid/gid - # BusyBox only has 'adduser', with arguments incompatible with - # that of the standard 'useradd'. - # Instead, we'll just write the entry ourselves. - entry = passwd_entry( + writeln(f, passwd_entry( username = SCUBA_USER, password = 'x', - uid = os.getuid(), - gid = os.getgid(), + uid = uid, + gid = gid, gecos = 'Scuba User', homedir = '/', # Docker sets $HOME=/ shell = '/bin/sh', - ) - write_cmd('echo', shell_quote(entry), '>>', '/etc/passwd') + )) - # Add scubauser group - entry = group_entry( + # /etc/group + with NamedTemporaryFile(prefix='scuba', delete=False) as f: + cleanup.add(f.name) + opts.append(make_vol_opt(f.name, '/etc/group', 'z')) + + writeln(f, group_entry( + groupname = 'root', + password = 'x', + gid = 0, + )) + + writeln(f, group_entry( groupname = SCUBA_GROUP, password = 'x', - gid = os.getgid(), - users = [SCUBA_USER], - ) - write_cmd('echo', shell_quote(entry), '>>', '/etc/group') + gid = gid, + )) - # Set the umask - write_cmd('umask', oct(get_umask())) - # Execute the command indicated by the user, as the scuba user - write_cmd('su', SCUBA_USER, '-c', shell_quote(command)) - return f.name + return opts def parse_args(): ap = argparse.ArgumentParser(description='Simple Container-Utilizing Build Apparatus') @@ -327,19 +325,12 @@ def main(): We want files created inside the container (in scubaroot) to appear to the host as if they were created there (owned by the same uid/gid, with same - umask, etc.) So, we use a .scubainit script to create a user with the same - uid/gid, su(1) to that user, and run the usercommand. + umask, etc.) ''' verbose_msg('Docker running natively') - # Generate a .scubainit script - scubainit_path = generate_scubainit(config, usercmd) - - # Mount scubainit script - docker_opts = [make_vol_opt(scubainit_path, '/.scubainit', 'z')] - - # Run the .scubainit at startup - docker_cmd = ['/bin/sh', '/.scubainit'] + docker_opts = get_native_opts() + docker_cmd = usercmd # NOTE: This tells Docker to re-label the directory for compatibility # with SELinux. See `man docker-run` for more information. From f444d4bad7e2ac2d860b45af44008b7ec532018b Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Fri, 8 Jan 2016 09:05:12 -0500 Subject: [PATCH 3/5] inject /etc/shadow This adds a proper /etc/shadow entry as well, fixing the "Authentication error\n(ignored)" from su. This fixes #21. --- src/scuba | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/scuba b/src/scuba index 23949254..68559685 100755 --- a/src/scuba +++ b/src/scuba @@ -188,6 +188,19 @@ def group_entry(groupname, password, gid, users=[]): gid = gid, users = ','.join(users)) +def shadow_entry(username, **kw): + return '{username}:{password}:{lstchg}:{minchg}:{maxchg}:{warn}:{inact}:{expire}:{flag}'.format( + username = username, + password = kw.get('password', '*'), + lstchg = kw.get('lstchg', ''), + minchg = kw.get('minchg', ''), + maxchg = kw.get('maxchg', ''), + warn = kw.get('warn', ''), + inact = kw.get('inact', ''), + expire = kw.get('expire', ''), + flag = '', + ) + def shell_quote(s): # http://stackoverflow.com/a/847800/119527 return pipes.quote(s) @@ -270,7 +283,17 @@ def get_native_opts(): gid = gid, )) + # /etc/shadow + with NamedTemporaryFile(prefix='scuba', delete=False) as f: + cleanup.add(f.name) + opts.append(make_vol_opt(f.name, '/etc/shadow', 'z')) + writeln(f, shadow_entry( + username = 'root', + )) + writeln(f, shadow_entry( + username = SCUBA_USER, + )) return opts From d9151fc2e6d292ecec097f148b72ad06298e94bf Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Fri, 8 Jan 2016 09:43:03 -0500 Subject: [PATCH 4/5] remove obsolete code - `shell_quote` was only used for generating .scubainit - `get_image_command` was only needed because we were launching .scubainit instead of the usercmd - `get_umask` was used to set the umask in .scubainit, which is unfortunately not possible now --- src/scuba | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/scuba b/src/scuba index 68559685..846e1b1c 100755 --- a/src/scuba +++ b/src/scuba @@ -201,35 +201,6 @@ def shadow_entry(username, **kw): flag = '', ) -def shell_quote(s): - # http://stackoverflow.com/a/847800/119527 - return pipes.quote(s) - -def get_image_command(image): - '''Gets the default command for an image''' - args = ['docker', 'inspect', image] - try: - p = subprocess.Popen(args, stdout = subprocess.PIPE) - except OSError as e: - if e.errno == errno.ENOENT: - appmsg('Failed to execute docker. Is it installed?') - sys.exit(2) - - stdout, _ = p.communicate() - if not p.returncode == 0: - appmsg('Failed to inspect image') - sys.exit(2) - - info = json.loads(stdout)[0] - return info['Config']['Cmd'] - -def get_umask(): - # Same logic as bash/builtins/umask.def - val = os.umask(022) - os.umask(val) - return val - - def get_native_opts(): opts = [] From 2c7f3c0c0bd950fc698a2214ba0e8f04ac66a34a Mon Sep 17 00:00:00 2001 From: Jonathon Reinhart Date: Fri, 8 Jan 2016 13:02:31 -0500 Subject: [PATCH 5/5] update CHANGELOG regarding removal of `.scubainit` --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d92961a1..dcc9055a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Added `--verbose` option +### Removed +- umask is no longer set in the container. (See [#24]) + +### Fixed +- Problems with Ctrl+C in images are fixed. The user command now runs + as PID 1 again, as there is no more `.scubainit` script. + + ## [1.3.0] - 2016-01-07 ### Added - Set umask in container to the same as the host (local Docker only) @@ -68,3 +76,4 @@ First versioned release [1.0.0]: https://github.com/JonathonReinhart/scuba/compare/v0.1.0...v1.0.0 [issue 11]: https://github.com/JonathonReinhart/scuba/issues/11 +[#24]: https://github.com/JonathonReinhart/scuba/pull/24