Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rootless Docker ACAP requiring AllowRoot to install #107

Merged
merged 13 commits into from
Nov 10, 2023
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
build-*
tmp
.vscode
2 changes: 2 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
push:
branches:
- 'main'
- 'rootless-preview'
tags:
# semver, e.g. 1.2.0 (does not match 0.1.2)
- '[1-9]+.[0-9]+.[0-9]+'
Expand All @@ -22,6 +23,7 @@ on:
pull_request:
branches:
- 'main'
- 'rootless-preview'

jobs:
# Builds docker ACAP using the build.sh script, then signs the eap-file in
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ dockerd
docker-proxy
dockerdwrapper
package.conf.orig
.vscode
*.eap
*.eap.old
*.pem
Expand Down
87 changes: 75 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,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 <<EOF
apt-get update
Expand All @@ -29,10 +24,46 @@ RUN <<EOF
autopoint \
gettext \
git \
libtool
libtool \
bison
ln -s /usr/bin/libtoolize /usr/bin/libtool
apt-get clean
jojju marked this conversation as resolved.
Show resolved Hide resolved
rm -rf /var/lib/apt/lists/*
EOF

FROM build_image AS nsenter

ARG NSENTER_VERSION=v2.39.1
ARG BUILD_DIR=/build
ARG EXPORT_DIR=/export

WORKDIR $BUILD_DIR
RUN git clone -b $NSENTER_VERSION 'https://github.com/util-linux/util-linux.git'

ARG BUILD_CACHE=build.cache
RUN echo ac_cv_func_realloc_0_nonnull=yes >$BUILD_CACHE \
&& echo ac_cv_func_malloc_0_nonnull=yes >>$BUILD_CACHE
madelen-at-work marked this conversation as resolved.
Show resolved Hide resolved
RUN <<EOF
cd util-linux
. /opt/axis/acapsdk/environment-setup*
./autogen.sh
./configure --host="${TARGET_PREFIX%*-}" \
--disable-shared \
--without-ncurses \
--cache-file="$BUILD_CACHE"
make nsenter
$STRIP nsenter
EOF

WORKDIR $EXPORT_DIR
RUN cp $BUILD_DIR/util-linux/nsenter nsenter

FROM build_image AS ps

ARG PROCPS_VERSION=v3.3.17
ARG BUILD_DIR=/build
ARG EXPORT_DIR=/export

WORKDIR $BUILD_DIR
RUN git clone --depth 1 -b $PROCPS_VERSION 'https://gitlab.com/procps-ng/procps' .

Expand All @@ -56,9 +87,9 @@ RUN cp $BUILD_DIR/ps/pscommand ps
FROM build_image as build

ARG DOCKER_IMAGE_VERSION
ENV DOCKERVERSION ${DOCKER_IMAGE_VERSION}
ARG ACAPARCH
ENV ARCH ${ACAPARCH}
ARG SLIRP4NETNS_VERSION=1.2.0
ARG ROOTLESS_EXTRAS_VERSION=${DOCKER_IMAGE_VERSION}

# Copy over axparameter from the acap-sdk
COPY --from=acap-sdk /opt/axis/acapsdk/sysroots/${ACAPARCH}/usr/include/axsdk/ax_parameter /opt/axis/acapsdk/sysroots/${ACAPARCH}/usr/include/axsdk
Expand All @@ -69,8 +100,29 @@ COPY --from=acap-sdk /opt/axis/acapsdk/sysroots/${ACAPARCH}/usr/lib/pkgconfig/ax

COPY app /opt/app
COPY --from=ps /export/ps /opt/app
COPY --from=nsenter /export/nsenter /opt/app

COPY ./binaries/${ACAPARCH}/* /opt/app

# Download and extract dockerd and its dependencies
# Temp fix to get binary onto aarch64 master fw
COPY ./binaries/systemd-user-runtime-dir /opt/app
COPY ./binaries/*.service /opt/app
COPY ./binaries/handle_directories.sh /opt/app

WORKDIR /opt/app

# Download and extract slirp4netns
RUN <<EOF
if [ "$ACAPARCH" = "armv7hf" ]; then
export SLIRP4NETNS_ARCH="armv7l";
elif [ "$ACAPARCH" = "aarch64" ]; then
export SLIRP4NETNS_ARCH="aarch64";
fi;
curl -Lo slirp4netns "https://github.com/rootless-containers/slirp4netns/releases/download/v${SLIRP4NETNS_VERSION}/slirp4netns-${SLIRP4NETNS_ARCH}";
chmod +x slirp4netns
EOF

# Download and extract docker scripts and docker-rootless-extras scripts
RUN <<EOF
if [ "$ACAPARCH" = "armv7hf" ]; then
export DOCKER_ARCH="armhf";
Expand All @@ -81,17 +133,28 @@ RUN <<EOF
tar -xz -f docker_binaries.tgz --strip-components=1 docker/dockerd ;
tar -xz -f docker_binaries.tgz --strip-components=1 docker/docker-init ;
tar -xz -f docker_binaries.tgz --strip-components=1 docker/docker-proxy ;
curl -Lo docker-rootless-extras.tgz "https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-rootless-extras-${ROOTLESS_EXTRAS_VERSION}.tgz" ;
tar -xz -f docker-rootless-extras.tgz --strip-components=1 ;
EOF

WORKDIR /opt/app
RUN <<EOF
. /opt/axis/acapsdk/environment-setup*
acap-build . \
-a dockerd \
-a docker-init \
-a docker-proxy \
-a empty_daemon.json \
-a ps
-a ps \
-a slirp4netns \
-a rootlesskit \
-a rootlesskit-docker-proxy \
-a nsenter \
-a newgidmap \
-a newuidmap \
-a systemd-user-runtime-dir \
-a [email protected] \
-a [email protected] \
-a handle_directories.sh
EOF

ENTRYPOINT [ "/opt/axis/acapsdk/sysroots/x86_64-pokysdk-linux/usr/bin/eap-install.sh" ]
102 changes: 95 additions & 7 deletions app/dockerdwrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
* under the License.
*/

#include <arpa/inet.h>
#include <axsdk/ax_parameter.h>
#include <errno.h>
#include <glib.h>
#include <mntent.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/stat.h>
Expand Down Expand Up @@ -49,7 +52,10 @@ static const char *sd_card_path = "/var/spool/storage/SD_DISK";
static bool restart_dockerd = false;

// All ax_parameters the acap has
static const char *ax_parameters[] = {"IPCSocket", "SDCardSupport", "UseTLS"};
static const char *ax_parameters[] = {"IPCSocket",
"SDCardSupport",
"UseTLS",
"Verbose"};

/**
* @brief Signals handling
Expand Down Expand Up @@ -252,13 +258,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
Expand All @@ -281,18 +289,83 @@ start_dockerd(void)
goto end;
}

char card_path[100];
strcpy(card_path, sd_card_path);
strcat(card_path, "/dockerd");
madelen-at-work marked this conversation as resolved.
Show resolved Hide resolved

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");
madelen-at-work marked this conversation as resolved.
Show resolved Hide resolved
}
madelen-at-work marked this conversation as resolved.
Show resolved Hide resolved

// add dockerd arguments
args_offset += g_snprintf(
args + args_offset,
args_len - args_offset,
"%s %s",
" %s %s %s",
"dockerd",
"--iptables=false",
jojju marked this conversation as resolved.
Show resolved Hide resolved
"--config-file /usr/local/packages/dockerdwrapper/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);

if (use_tls) {
Expand Down Expand Up @@ -361,18 +434,28 @@ start_dockerd(void)
}

if (use_ipc_socket) {
uid_t uid;
uid = getuid();
uid_t gid;
gid = getgid();
madelen-at-work marked this conversation as resolved.
Show resolved Hide resolved
// 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);
}

// 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,
Expand Down Expand Up @@ -478,7 +561,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();
madelen-at-work marked this conversation as resolved.
Show resolved Hide resolved
g_snprintf(path, path_len, "/var/run/user/%d/docker.pid", uid);
remove(path);

if (restart_dockerd) {
restart_dockerd = false;
Expand Down
26 changes: 20 additions & 6 deletions app/manifest.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
{
"schemaVersion": "1.3",
"resources": {
"linux": {
"user": {
"groups": [
"storage",
"vdo"
]
}
}
},
"acapPackageConf": {
"setup": {
"friendlyName": "Docker Daemon",
"friendlyName": "Docker Daemon Rootless",
"appId": "414120",
"appName": "dockerdwrapper",
"vendor": "Axis Communications",
"embeddedSdkVersion": "3.0",
"user": {
"group": "root",
"username": "root"
},
"vendorUrl": "https://www.axis.com",
"runMode": "once",
"version": "1.4.0"
"version": "2.0.0-preview"
},
"installation": {
"postInstallScript": "postinstallscript.sh"
},
"uninstallation": {
"preUninstallScript": "preuninstallscript.sh"
},
"configuration": {
"paramConfig": [
{
Expand All @@ -34,6 +43,11 @@
"name": "IPCSocket",
"default": "no",
"type": "enum:no|No, yes|Yes"
},
{
"name": "Verbose",
"default": "no",
"type": "enum:no|No, yes|Yes"
}
]
}
Expand Down
Loading