diff --git a/Dockerfile b/Dockerfile index 2857696..1bd6d38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,14 +14,9 @@ ARG ACAP3_SDK_VERSION=3.5 ARG ACAP3_UBUNTU_VERSION=20.04 ARG ACAP3_SDK=acap-sdk -FROM ${REPO}/${NATIVE_SDK}:${VERSION}-${ACAPARCH}-ubuntu${UBUNTU_VERSION} as build_image - FROM ${REPO}/${ACAP3_SDK}:${ACAP3_SDK_VERSION}-${ACAPARCH}-ubuntu${ACAP3_UBUNTU_VERSION} as acap-sdk -FROM build_image AS ps -ARG PROCPS_VERSION=v3.3.17 -ARG BUILD_DIR=/build -ARG EXPORT_DIR=/export +FROM ${REPO}/${NATIVE_SDK}:${VERSION}-${ACAPARCH}-ubuntu${UBUNTU_VERSION} as build_image RUN <$BUILD_CACHE \ + && echo ac_cv_func_malloc_0_nonnull=yes >>$BUILD_CACHE +RUN < #include #include #include #include +#include +#include #include #include #include @@ -48,6 +51,12 @@ static const char *sd_card_path = "/var/spool/storage/SD_DISK"; // True if the dockerd_exited_callback should restart dockerd static bool restart_dockerd = false; +// All ax_parameters the acap has +static const char *ax_parameters[] = {"IPCSocket", + "SDCardSupport", + "UseTLS", + "Verbose"}; + /** * @brief Signals handling * @@ -250,13 +259,15 @@ start_dockerd(void) char *use_sd_card_value = get_parameter_value("SDCardSupport"); char *use_tls_value = get_parameter_value("UseTLS"); char *use_ipc_socket_value = get_parameter_value("IPCSocket"); + char *use_verbose_value = get_parameter_value("Verbose"); if (use_sd_card_value == NULL || use_tls_value == NULL || - use_ipc_socket_value == NULL) { + use_ipc_socket_value == NULL || use_verbose_value == NULL) { goto end; } bool use_sdcard = strcmp(use_sd_card_value, "yes") == 0; bool use_tls = strcmp(use_tls_value, "yes") == 0; bool use_ipc_socket = strcmp(use_ipc_socket_value, "yes") == 0; + bool use_verbose = strcmp(use_verbose_value, "yes") == 0; if (use_sdcard) { // Confirm that the SD card is usable @@ -279,18 +290,82 @@ start_dockerd(void) goto end; } + char card_path[100]; + strcpy(card_path, sd_card_path); + strcat(card_path, "/dockerd"); + + if (access(card_path, F_OK) == 0 && access(card_path, W_OK) != 0) { + syslog(LOG_ERR, + "The application user does not have write permissions to the SD " + "card directory at %s. Please change the directory permissions or " + "remove the directory.", + card_path); + goto end; + } + if (!setup_sdcard()) { syslog(LOG_ERR, "Failed to setup SD card."); goto end; } } + + // get host ip + char host_buffer[256]; + char *IPbuffer; + struct hostent *host_entry; + gethostname(host_buffer, sizeof(host_buffer)); + host_entry = gethostbyname(host_buffer); + IPbuffer = inet_ntoa(*((struct in_addr *)host_entry->h_addr_list[0])); + + // construct the rootlesskit command + args_offset += g_snprintf(args + args_offset, + args_len - args_offset, + "%s %s %s %s %s %s %s %s %s", + "rootlesskit", + "--subid-source=static", + "--net=slirp4netns", + "--disable-host-loopback", + "--copy-up=/etc", + "--copy-up=/run", + "--propagation=rslave", + "--port-driver slirp4netns", + /* don't use same range as company proxy */ + "--cidr=10.0.3.0/24"); + + if (use_verbose) { + args_offset += g_snprintf( + args + args_offset, args_len - args_offset, " %s", "--debug"); + } + + if (use_tls) { + args_offset += g_snprintf(args + args_offset, + args_len - args_offset, + " %s %s%s", + "-p", + IPbuffer, + ":2376:2376/tcp"); + } else { + args_offset += g_snprintf(args + args_offset, + args_len - args_offset, + " %s %s%s", + "-p", + IPbuffer, + ":2375:2375/tcp"); + } + + // add dockerd arguments args_offset += g_snprintf( args + args_offset, args_len - args_offset, - "%s %s", + " %s %s %s", "dockerd", - "--config-file " - "/usr/local/packages/dockerdwrapperwithcompose/localdata/daemon.json"); + "--iptables=false", + "--config-file /usr/local/packages/dockerdwrapperwithcompose/localdata/daemon.json"); + + if (!use_verbose) { + args_offset += g_snprintf( + args + args_offset, args_len - args_offset, " %s", "--log-level=warn"); + } g_strlcpy(msg, "Starting dockerd", msg_len); @@ -362,11 +437,20 @@ start_dockerd(void) } if (use_ipc_socket) { + uid_t uid; + uid = getuid(); + uid_t gid; + gid = getgid(); + // The socket should reside in the user directory and have same group as + // user args_offset += g_snprintf(args + args_offset, args_len - args_offset, - " %s", - "-H unix:///var/run/docker.sock"); - + " %s %d %s%d%s", + "--group", + gid, + "-H unix:///var/run/user/", + uid, + "/docker.sock"); g_strlcat(msg, " with IPC socket.", msg_len); } else { g_strlcat(msg, " without IPC socket.", msg_len); @@ -374,6 +458,7 @@ start_dockerd(void) // Log startup information to syslog. syslog(LOG_INFO, "%s", msg); + syslog(LOG_INFO, "%s", args); // TODO Remove this before release of rootless args_split = g_strsplit(args, " ", 0); result = g_spawn_async(NULL, @@ -479,7 +564,12 @@ dockerd_process_exited_callback(__attribute__((unused)) GPid pid, // The lockfile might have been left behind if dockerd shut down in a bad // manner. Remove it manually. - remove("/var/run/docker.pid"); + gsize path_len = 128; + gchar path[path_len]; + uid_t uid; + uid = getuid(); + g_snprintf(path, path_len, "/var/run/user/%d/docker.pid", uid); + remove(path); if (restart_dockerd) { restart_dockerd = false; @@ -507,14 +597,18 @@ parameter_changed_callback(const gchar *name, __attribute__((unused)) gpointer data) { const gchar *parname = name += strlen("root.dockerdwrapperwithcompose."); - // bool dockerd_started_correctly = false; - if (strcmp(parname, "SDCardSupport") == 0) { - syslog(LOG_INFO, "SDCardSupport changed to: %s", value); - restart_dockerd = true; - } else if (strcmp(parname, "UseTLS") == 0) { - syslog(LOG_INFO, "UseTLS changed to: %s", value); - restart_dockerd = true; - } else { + + bool unknown_parameter = true; + for (size_t i = 0; i < sizeof(ax_parameters) / sizeof(ax_parameters[0]); + ++i) { + if (strcmp(parname, ax_parameters[i]) == 0) { + syslog(LOG_INFO, "%s changed to: %s", ax_parameters[i], value); + restart_dockerd = true; + unknown_parameter = false; + } + } + + if (unknown_parameter) { syslog(LOG_WARNING, "Parameter %s is not recognized", name); restart_dockerd = false; @@ -543,34 +637,21 @@ setup_axparameter(void) goto end; } - gboolean geresult = ax_parameter_register_callback( - ax_parameter, - "root.dockerdwrapperwithcompose.SDCardSupport", - parameter_changed_callback, - NULL, - &error); + for (size_t i = 0; i < sizeof(ax_parameters) / sizeof(ax_parameters[0]); + ++i) { + char *parameter_path = + g_strdup_printf("%s.%s", "root.dockerdwrapperwithcompose", ax_parameters[i]); + gboolean geresult = ax_parameter_register_callback( + ax_parameter, parameter_path, parameter_changed_callback, NULL, &error); + free(parameter_path); - if (geresult == FALSE) { - syslog(LOG_ERR, - "Could not register SDCardSupport callback. Error: %s", - error->message); - goto end; - } - - geresult = - ax_parameter_register_callback(ax_parameter, - "root.dockerdwrapperwithcompose.UseTLS", - parameter_changed_callback, - NULL, - &error); - - if (geresult == FALSE) { - syslog(LOG_ERR, - "Could not register UseTLS callback. Error: %s", - error->message); - ax_parameter_unregister_callback( - ax_parameter, "root.dockerdwrapperwithcompose.SDCardSupport"); - goto end; + if (geresult == FALSE) { + syslog(LOG_ERR, + "Could not register %s callback. Error: %s", + ax_parameters[i], + error->message); + goto end; + } } success = true; @@ -624,10 +705,13 @@ main(void) } if (ax_parameter != NULL) { - ax_parameter_unregister_callback( - ax_parameter, "root.dockerdwrapperwithcompose.SDCardSupport"); - ax_parameter_unregister_callback(ax_parameter, - "root.dockerdwrapperwithcompose.UseTLS"); + for (size_t i = 0; i < sizeof(ax_parameters) / sizeof(ax_parameters[0]); + ++i) { + char *parameter_path = + g_strdup_printf("%s.%s", "root.dockerdwrapperwithcompose", ax_parameters[i]); + ax_parameter_unregister_callback(ax_parameter, parameter_path); + free(parameter_path); + } ax_parameter_free(ax_parameter); } diff --git a/app/manifest.json b/app/manifest.json index 490b458..8657bf1 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,19 +1,25 @@ { "schemaVersion": "1.3", + "resources": { + "linux": { + "user": { + "groups": [ + "storage", + "vdo" + ] + } + } + }, "acapPackageConf": { "setup": { - "friendlyName": "Docker Daemon with Compose", + "friendlyName": "Docker Rootless Daemon with Compose", "appId": "414150", "appName": "dockerdwrapperwithcompose", "vendor": "Axis Communications", "embeddedSdkVersion": "3.0", - "user": { - "group": "root", - "username": "root" - }, "vendorUrl": "https://www.axis.com", "runMode": "once", - "version": "1.3.0" + "version": "2.0.0-preview" }, "installation": { "postInstallScript": "postinstallscript.sh" @@ -37,7 +43,13 @@ "name": "IPCSocket", "default": "yes", "type": "enum:no|No, yes|Yes" + }, + { + "name": "Verbose", + "default": "no", + "type": "enum:no|No, yes|Yes" } + ] } } diff --git a/app/postinstallscript.sh b/app/postinstallscript.sh index 5544f7f..56c654c 100644 --- a/app/postinstallscript.sh +++ b/app/postinstallscript.sh @@ -1,27 +1,79 @@ -#!/bin/sh +#!/bin/sh -e -# Move the daemon.json file into localdata folder -if [ ! -e localdata/daemon.json ] -then - mv empty_daemon.json localdata/daemon.json -else - rm empty_daemon.json +if [ "$(id -un)" != "root" ]; then + logger -p user.warn "$0: Must be run as 'root' instead of user '$(id -un)'." + exit 77 # EX_NOPERM fi -# Make sure containerd is started before dockerd and set PATH +# Get name and uid of acap user and group +_appname=dockerdwrapperwithcompose +_appdirectory=/usr/local/packages/$_appname +_uname="$(stat -c '%U' "$_appdirectory")" +_uid="$(id "$_uname" -u)" +_gname="$(id "$_uname" -gn)" + +# If the device supports cgroups v2 we need to start the user.service +if [ ! -d /sys/fs/cgroup/unified ]; then +# Move systemd-user-runtime-dir to /usr/lib/systemd + mv acap-user-runtime-dir@.service /etc/systemd/system/acap-user-runtime-dir@.service + mv acap-user@.service /etc/systemd/system/acap-user@.service + + chown root:root /etc/systemd/system/acap-user-runtime-dir@.service + chown root:root /etc/systemd/system/acap-user@.service + + # Update the app service file to Want acap-user@.service + echo "[Unit] +Wants=acap-user@$_uid.service" >> /etc/systemd/system/sdkdockerdwrapperwithcompose.service + +fi + +# Create mapping for subuid and subgid - both shall use user name! +echo "$_uname:100000:65536" > /etc/subuid +echo "$_uname:100000:65536" > /etc/subgid + +# Let root own these two utilities and make the setuid +chown root:root newuidmap +chown root:root newgidmap +chmod u+s newuidmap +chmod u+s newgidmap + +# Update the app service file to work for our special case cat >> /etc/systemd/system/sdkdockerdwrapperwithcompose.service << EOF [Unit] BindsTo=containerd.service After=network-online.target containerd.service var-spool-storage-SD_DISK.mount Wants=network-online.target [Service] -Environment=PATH=/usr/local/packages/dockerdwrapperwithcompose:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +Environment=PATH=/bin:/usr/bin:$_appdirectory:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin +Environment=HOME=$_appdirectory +Environment=DOCKER_HOST=unix://run/user/$_uid/docker.sock +Environment=XDG_RUNTIME_DIR=/run/user/$_uid +ExecStartPre=+$_appdirectory/handle_directories.sh $_uid $_uname $_gname EOF +# reload daemon for service file changes to take effect +systemctl daemon-reload +# *** non-root user should be able to do this **** + +# Move the daemon.json file into localdata folder +if [ ! -e localdata/daemon.json ] +then + mv empty_daemon.json localdata/daemon.json +else + rm empty_daemon.json +fi + +# TODO Are these paths correct for non-root? # Create docker symbolic link mkdir -p /usr/local/bin ln -s /usr/local/packages/dockerdwrapperwithcompose/docker /usr/local/bin/docker # Create docker-compose symbolic link mkdir -p /usr/local/lib/docker/cli-plugins -ln -s /usr/local/packages/dockerdwrapperwithcompose/docker-compose /usr/local/lib/docker/cli-plugins/docker-compose \ No newline at end of file +ln -s /usr/local/packages/dockerdwrapperwithcompose/docker-compose /usr/local/lib/docker/cli-plugins/docker-compose + +# Create docker socket symbolic link +ln -s /run/user/"$_uid"/docker.sock /var/run/docker.sock +# Allow users in ssh-users group to access the socket +# NOTE! This will (probably) not work for sdk or acap- users - this need to be addressed +chgrp ssh-users /run/user/"$_uid"/docker.sock diff --git a/app/preuninstallscript.sh b/app/preuninstallscript.sh index 7671b4f..2de55ba 100644 --- a/app/preuninstallscript.sh +++ b/app/preuninstallscript.sh @@ -1,6 +1,34 @@ -#!/bin/sh +#!/bin/sh -e + +if [ "$(id -un)" != "root" ]; then + logger -p user.warn "$0: Must be run as 'root' instead of user '$(id -un)'." + exit 77 # EX_NOPERM +fi + # Remove docker symbolic link rm /usr/local/bin/docker # Remove docker-compose symbolic link -rm /usr/local/lib/docker/cli-plugins/docker-compose \ No newline at end of file +rm /usr/local/lib/docker/cli-plugins/docker-compose + +rm /var/run/docker.sock + +# *** root user required **** +# TODO Add a check of who the user is and log warning if not root + +# Get name and uid of acap user +_appname=dockerdwrapperwithcompose +_appdirectory=/usr/local/packages/$_appname +_uname="$(stat -c '%U' "$_appdirectory")" +_uid="$(id "$_uname" -u)" + +# Remove the user folder (this step should only be needed for cgroups v1 system) +rm -Rf "/run/user/$_uid" + +# Remove the service files (this step should only be needed for cgroups v2 system) +rm -Rf /etc/systemd/system/acap-user-runtime-dir@.service +rm -Rf /etc/systemd/system/acap-user@.service + +# Remove the subuid/subgid mappings +sed -i "/$_uname:100000:65536/d" /etc/subuid +sed -i "/$_uname:100000:65536/d" /etc/subgid diff --git a/binaries/aarch64/newgidmap b/binaries/aarch64/newgidmap new file mode 100755 index 0000000..93dc029 Binary files /dev/null and b/binaries/aarch64/newgidmap differ diff --git a/binaries/aarch64/newuidmap b/binaries/aarch64/newuidmap new file mode 100755 index 0000000..a81cc32 Binary files /dev/null and b/binaries/aarch64/newuidmap differ diff --git a/binaries/acap-user-runtime-dir@.service b/binaries/acap-user-runtime-dir@.service new file mode 100644 index 0000000..2825a30 --- /dev/null +++ b/binaries/acap-user-runtime-dir@.service @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=User Runtime Directory /run/user/%i +Documentation=man:user@.service(5) +After=dbus.service +StopWhenUnneeded=yes +IgnoreOnIsolate=yes + +[Service] +ExecStart=/usr/local/packages/dockerdwrapperwithcompose/systemd-user-runtime-dir start %i +ExecStop=/usr/local/packages/dockerdwrapperwithcompose/systemd-user-runtime-dir stop %i +Type=oneshot +RemainAfterExit=yes +Slice=user-%i.slice diff --git a/binaries/acap-user@.service b/binaries/acap-user@.service new file mode 100644 index 0000000..1d2fc18 --- /dev/null +++ b/binaries/acap-user@.service @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=User Manager for UID %i +Documentation=man:user@.service(5) +After=acap-user-runtime-dir@%i.service dbus.service systemd-oomd.service +Requires=acap-user-runtime-dir@%i.service +IgnoreOnIsolate=yes +StopWhenUnneeded=yes + +[Service] +User=%i +PAMName=systemd-user +Type=notify-reload +ExecStart=/usr/lib/systemd/systemd --user +Slice=user-%i.slice +KillMode=mixed +Delegate=yes +TasksMax=infinity +TimeoutStopSec=120s +KeyringMode=inherit +OOMScoreAdjust=100 +ExecStartPre=+systemctl set-environment XDG_RUNTIME_DIR=/run/user/%i diff --git a/binaries/armv7hf/newgidmap b/binaries/armv7hf/newgidmap new file mode 100755 index 0000000..1e94600 Binary files /dev/null and b/binaries/armv7hf/newgidmap differ diff --git a/binaries/armv7hf/newuidmap b/binaries/armv7hf/newuidmap new file mode 100755 index 0000000..15758f2 Binary files /dev/null and b/binaries/armv7hf/newuidmap differ diff --git a/binaries/handle_directories.sh b/binaries/handle_directories.sh new file mode 100755 index 0000000..6857d6e --- /dev/null +++ b/binaries/handle_directories.sh @@ -0,0 +1,14 @@ +#!/bin/sh -e + +# These directories will be owned by root if they exist so remove them +# so that our rootless setup works as intended +rm -Rf /run/docker +rm -Rf /run/containerd +rm -Rf /run/xtables.lock + +# Create /usr/run/ on cgroups v1 system since user service will not run there +# since script is run by root the required inputs are uid, user, group +if [ -d /sys/fs/cgroup/unified ]; then + mkdir -p /run/user/"$1" + chown "$2":"$3" /run/user/"$1" +fi diff --git a/binaries/systemd-user-runtime-dir b/binaries/systemd-user-runtime-dir new file mode 100755 index 0000000..0d15523 Binary files /dev/null and b/binaries/systemd-user-runtime-dir differ