diff --git a/shipcat-wrapper.c b/shipcat-wrapper.c index 66864bafedd196d4be74501c6a6e984adedd7aa8..8083a6400058036c73c2a7d999c8be10d501b249 100644 --- a/shipcat-wrapper.c +++ b/shipcat-wrapper.c @@ -22,6 +22,7 @@ #define NONRET __attribute__((noreturn)) #define UNUSED __attribute__((unused)) +#define ARRAY_SIZE(a) (int)(sizeof(a) / sizeof(*(a))) static void NONRET die(const char *fmt, ...) { @@ -93,6 +94,8 @@ static void get_credentials(void) if (glen >= (int) sizeof(as_groups)) die("Group list too long"); } + + free(groups); } static void switch_ugid(void) @@ -104,21 +107,10 @@ static void switch_ugid(void) die("Failed to set user id: %m"); } -int main(int argc UNUSED, char **argv UNUSED) +static char **make_args(int argc, char **argv) { - sanitize_fds(); - umask(0077); - - if (geteuid()) - die("Must be run setuid"); - - get_credentials(); - switch_ugid(); - - char *program = DESTDIR "/usr/bin/shipcat"; - char **args = xmalloc(sizeof(char *) * (5 + argc)); - args[0] = program; + args[0] = DESTDIR "/usr/bin/shipcat"; args[1] = "--as-user"; args[2] = as_user; args[3] = "--as-groups"; @@ -126,14 +118,60 @@ int main(int argc UNUSED, char **argv UNUSED) for (int i=1; i<argc; i++) args[4+i] = argv[i]; args[4+argc] = NULL; + return args; +} - char * const env[] = { - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - NULL +static char **make_env(void) +{ + static char *set_env[] = { + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }; + + static char *inherit_env[] = { + "TERM=", }; - if (execve(program, args, env) < 0) - die("Cannot execute %s: %m", program); + char **env = xmalloc(sizeof(char *) * (ARRAY_SIZE(set_env) + ARRAY_SIZE(inherit_env) + 1)); + char **ep = env; + + for (int i=0; i < ARRAY_SIZE(set_env); i++) + *ep++ = set_env[i]; + + for (int i=0; environ[i]; i++) + { + char *e = environ[i]; + int elen = strlen(e); + for (int j=0; j < ARRAY_SIZE(inherit_env); j++) + { + char *p = inherit_env[j]; + int plen = strlen(p); + if (elen >= plen && !memcmp(e, p, plen)) + { + *ep++ = e; + break; + } + } + } + + *ep = NULL; + return env; +} + +int main(int argc, char **argv) +{ + sanitize_fds(); + umask(0077); + + if (geteuid()) + die("Must be run setuid"); + + get_credentials(); + switch_ugid(); + + char **args = make_args(argc, argv); + char **env = make_env(); + if (execve(args[0], args, env) < 0) + die("Cannot execute %s: %m", args[0]); die("This must never happen"); } diff --git a/shipcat.py b/shipcat.py index 42baeb6fe1d80978b61bd2c973c4a15b9c13a684..d9122cba2ba7b107ac4856156f7c368b6dbad19b 100755 --- a/shipcat.py +++ b/shipcat.py @@ -15,6 +15,7 @@ sys.path.append('/tmp/shc/usr/lib/python') from shipcat.config import GlobalConfig, ContainerConfig, ConfigError + def die(msg: str) -> NoReturn: print(msg, file=sys.stderr) sys.exit(1) @@ -24,6 +25,21 @@ def progress(msg: str) -> None: print(msg, file=sys.stderr, end="", flush=True) +def run_command(args: List[str], *rest, **kwargs) -> None: + res = subprocess.run(args, *rest, **kwargs) + if res.returncode != 0: + die('Command failed: ' + " ".join(args)) + + +def load_config() -> GlobalConfig: + try: + # FIXME: Path to config + return GlobalConfig.load('shipcat.toml') + except ConfigError as e: + print(e, file=sys.stderr) + sys.exit(1) + + def setup_container(args: argparse.Namespace, require_root: bool) -> ContainerConfig: if os.geteuid() != 0: die('This program must be run setuid root') @@ -104,7 +120,7 @@ class SubIds: return SubIdRange(i, size, user) -def do_init(args: argparse.Namespace) -> None: +def cmd_init(args: argparse.Namespace) -> None: name = args.name cc = setup_container(args, True) @@ -114,9 +130,8 @@ def do_init(args: argparse.Namespace) -> None: progress('already exists\n') except KeyError: progress('creating\n') - subprocess.run( + run_command( ['adduser', '--system', '--group', '--gecos', f'Container {name}', '--disabled-password', cc.user_name], - check=True, ) pwd = getpwnam(cc.user_name) uid = pwd.pw_uid @@ -129,9 +144,8 @@ def do_init(args: argparse.Namespace) -> None: else: progress('allocating\n') sur = subuids.alloc_range(cc.user_name, 65536) - subprocess.run( + run_command( ['usermod', '--add-subuids', f'{sur.first}-{sur.first + sur.count - 1}', cc.user_name], - check=True, ) progress('Subgid range: ') @@ -142,9 +156,8 @@ def do_init(args: argparse.Namespace) -> None: else: progress('allocating\n') sgr = subgids.alloc_range(cc.user_name, 65536) - subprocess.run( + run_command( ['usermod', '--add-subgids', f'{sgr.first}-{sgr.first + sgr.count - 1}', cc.user_name], - check=True, ) progress(f'Using user {cc.user_name}, uid {uid}, subuids {sur.first}+{sur.count}, subgids {sgr.first}+{sgr.count}\n') @@ -175,26 +188,30 @@ def do_init(args: argparse.Namespace) -> None: progress(f'{ip}\n') -def do_create(args: argparse.Namespace) -> None: +def service_action(cc: ContainerConfig, action: str) -> None: + run_command( + ['systemctl', action, f'shc@{cc.container}.service'], + ) + + +def cmd_create(args: argparse.Namespace) -> None: name = args.name cc = setup_container(args, False) data_dir = Path(cc.root_dir) / 'data' ip = socket.gethostbyname(name) - subprocess.run( + run_command( ['podman', 'pull', cc.image], - check=True, ) - # FIXME: If the container was running, stop it + service_action(cc, 'stop') - subprocess.run( + run_command( ['podman', 'rm', '-if', name], - check=True, ) - subprocess.run( + run_command( [ 'podman', 'create', '--name', name, @@ -208,12 +225,22 @@ def do_create(args: argparse.Namespace) -> None: '--subgidname', cc.user_name, cc.image, ], - check=True, ) -def do_start(args: argparse.Namespace) -> None: - pass +def cmd_start(args: argparse.Namespace) -> None: + cc = setup_container(args, False) + service_action(cc, 'start') + + +def cmd_stop(args: argparse.Namespace) -> None: + cc = setup_container(args, False) + service_action(cc, 'stop') + + +def cmd_status(args: argparse.Namespace) -> None: + cc = setup_container(args, False) + service_action(cc, 'status') def parse_int_list(s: str) -> List[int]: @@ -236,17 +263,21 @@ create_parser.add_argument('name', help='name of the container') start_parser = subparsers.add_parser('start', help='start a container', description='Start a container') start_parser.add_argument('name', help='name of the container') +status_parser = subparsers.add_parser('status', help='show container status', description='Show container status') +status_parser.add_argument('name', help='name of the container') + +stop_parser = subparsers.add_parser('stop', help='stop a container', description='Stop a container') +stop_parser.add_argument('name', help='name of the container') + args = parser.parse_args() -try: - config = GlobalConfig.load('shipcat.toml') -except ConfigError as e: - print(e, file=sys.stderr) - sys.exit(1) +actions = { + 'create': cmd_create, + 'init': cmd_init, + 'start': cmd_start, + 'status': cmd_status, + 'stop': cmd_stop, +} -if args.action == 'init': - do_init(args) -elif args.action == 'create': - do_create(args) -elif args.action == 'start': - do_start(args) +config = load_config() +actions[args.action](args)