Skip to content

Commit

Permalink
fix: [torrust#295] update docker image
Browse files Browse the repository at this point in the history
- Update bash scritps to follow conventions on Tracker and Index.
- Update build base image to `node:21-bookworm` (latest). Fix security
  vunerabilities.
- Use `entrypoint` to run container as non root user.
- Use distroless image for production.
  • Loading branch information
josecelano committed Nov 27, 2023
1 parent b501746 commit 879f5c9
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 54 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/publish_docker_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ on:
tags:
- "v*"

env:
TORRUST_INDEX_GUI_RUN_AS_USER: appuser

jobs:
check-secret:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -60,8 +57,6 @@ jobs:
with:
context: .
file: ./Containerfile
build-args: |
RUN_AS_USER=${{ env.TORRUST_INDEX_GUI_RUN_AS_USER }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down
86 changes: 45 additions & 41 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,65 @@

# Torrust Index GUI

## Su Exe Compile
FROM docker.io/library/gcc:bookworm as gcc
COPY ./contrib/dev-tools/su-exec/ /usr/local/src/su-exec/
RUN cc -Wall -Werror -g /usr/local/src/su-exec/su-exec.c -o /usr/local/bin/su-exec; chmod +x /usr/local/bin/su-exec


## Builder Image
FROM node:21-bookworm as base
FROM node:21-bookworm as builder

RUN mkdir -p /app
WORKDIR /app


## Build dependencies
FROM builder as dependencies
COPY package.json .
COPY package-lock.json .
RUN npm install-clean


FROM base as development
WORKDIR /app
ARG UID=1001
ARG RUN_AS_USER=appuser
ARG IDX_FRONT_PORT=3000
ARG API_BASE_URL=http://localhost:3001/v1
# Add the app user for development
ENV USER=appuser
ENV UID=$UID
RUN adduser --uid "${UID}" "${USER}"
# Build the app
## Build application
FROM dependencies as test
COPY . .
ENV API_BASE_URL=$API_BASE_URL
RUN npm run build
USER $RUN_AS_USER:$RUN_AS_USER
EXPOSE $IDX_FRONT_PORT/tcp
CMD ["npm", "run", "dev"]


FROM base as release
WORKDIR /app
ARG UID=1001
ARG RUN_AS_USER=appuser
ARG IDX_FRONT_PORT=3000
## Runtime
FROM gcr.io/distroless/nodejs20-debian12:debug as runtime
RUN ["/busybox/cp", "-sp", "/busybox/sh","/busybox/cat","/busybox/ls","/busybox/env", "/bin/"]
COPY --from=gcc --chmod=0555 /usr/local/bin/su-exec /bin/su-exec

ARG USER_ID=1000
ARG INDEX_GUI_PORT=3000
ARG API_BASE_URL=http://localhost:3001/v1
# Add the app user for production
ENV USER=appuser
ENV UID=$UID
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
# Build the app
COPY . .
ENV API_BASE_URL=$API_BASE_URL
RUN npm run build
# Timezone

ENV TZ=Etc/UTC
ENV RUN_AS_USER=$RUN_AS_USER
RUN chown -R $RUN_AS_USER:$RUN_AS_USER /app
USER $RUN_AS_USER:$RUN_AS_USER
EXPOSE $IDX_FRONT_PORT/tcp
CMD [ "node", "/app/.output/server/index.mjs" ]
ENV USER_ID=${USER_ID}
ENV INDEX_GUI_PORT=${INDEX_GUI_PORT}
ENV API_BASE_URL=${API_BASE_URL}

EXPOSE $INDEX_GUI_PORT/tcp

RUN mkdir -p /var/log/torrust/tracker

ENV ENV=/etc/profile
COPY --chmod=0555 ./share/container/entry_script_sh /usr/local/bin/entry.sh

VOLUME ["/var/log/torrust/index-gui"]

ENV RUNTIME="runtime"
ENTRYPOINT ["/usr/local/bin/entry.sh"]


## Torrust-Index-GUI (release) (default)
FROM runtime as release
ENV RUNTIME="release"
COPY --from=test /app/.output /app/.output
#HEALTHCHECK --interval=5s --timeout=5s --start-period=3s --retries=3 \
# CMD /usr/bin/http_health_check http://localhost:${HEALTH_CHECK_API_PORT}/health_check \
# || exit 1
CMD [ "/nodejs/bin/node", "/app/.output/server/index.mjs" ]
1 change: 0 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ services:
context: .
dockerfile: ./Containerfile
args:
RUN_AS_USER: appuser
UID: ${TORRUST_INDEX_GUI_USER_UID:-1001}
target: development
user: ${TORRUST_INDEX_GUI_USER_UID:-1000}:${TORRUST_INDEX_GUI_USER_UID:-1000}
Expand Down
5 changes: 1 addition & 4 deletions contrib/dev-tools/container/docker-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
echo "Building docker image ..."

TORRUST_INDEX_GUI_USER_UID=${TORRUST_INDEX_GUI_USER_UID:-1001}
TORRUST_INDEX_GUI_RUN_AS_USER=${TORRUST_INDEX_GUI_RUN_AS_USER:-appuser}
TORRUST_INDEX_GUI_API_BASE_URL=${TORRUST_INDEX_GUI_API_BASE_URL:-http://localhost:3001/v1}

echo "TORRUST_INDEX_GUI_USER_UID: $TORRUST_INDEX_GUI_USER_UID"
echo "TORRUST_INDEX_GUI_RUN_AS_USER: $TORRUST_INDEX_GUI_RUN_AS_USER"
echo "TORRUST_INDEX_GUI_API_BASE_URL: $TORRUST_INDEX_GUI_API_BASE_URL"

docker build \
--build-arg UID="$TORRUST_INDEX_GUI_USER_UID" \
--build-arg RUN_AS_USER="$TORRUST_INDEX_GUI_RUN_AS_USER" \
--build-arg USER_ID="$TORRUST_INDEX_GUI_USER_UID" \
--build-arg API_BASE_URL="$TORRUST_INDEX_GUI_API_BASE_URL" \
--target release \
--tag torrust-index-gui:release \
Expand Down
2 changes: 1 addition & 1 deletion contrib/dev-tools/container/docker-run-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
mkdir -p ./storage/index-gui/lib/ ./storage/index-gui/log/ ./storage/index-gui/etc/

docker run -it \
--env USER_ID"$(id -u)" \
--env USER_ID="$(id -u)" \
--publish 3000:3000/tcp \
torrust-index-gui:release
4 changes: 2 additions & 2 deletions contrib/dev-tools/container/docker-run-public.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/bash

mkdir -p ./storage/index-gui/lib/ ./storage/index-gui/log/ ./storage/index-gui/etc/
mkdir -p ./storage/index-gui/log/

docker run -it \
--env USER_ID"$(id -u)" \
--env USER_ID="$(id -u)" \
--publish 3000:3000/tcp \
torrust/index-gui:latest
22 changes: 22 additions & 0 deletions contrib/dev-tools/su-exec/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2015 ncopa

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17 changes: 17 additions & 0 deletions contrib/dev-tools/su-exec/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

CFLAGS ?= -Wall -Werror -g
LDFLAGS ?=

PROG := su-exec
SRCS := $(PROG).c

all: $(PROG)

$(PROG): $(SRCS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

$(PROG)-static: $(SRCS)
$(CC) $(CFLAGS) -o $@ $^ -static $(LDFLAGS)

clean:
rm -f $(PROG) $(PROG)-static
46 changes: 46 additions & 0 deletions contrib/dev-tools/su-exec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# su-exec
switch user and group id, setgroups and exec

## Purpose

This is a simple tool that will simply execute a program with different
privileges. The program will be executed directly and not run as a child,
like su and sudo does, which avoids TTY and signal issues (see below).

Notice that su-exec depends on being run by the root user, non-root
users do not have permission to change uid/gid.

## Usage

```shell
su-exec user-spec command [ arguments... ]
```

`user-spec` is either a user name (e.g. `nobody`) or user name and group
name separated with colon (e.g. `nobody:ftp`). Numeric uid/gid values
can be used instead of names. Example:

```shell
$ su-exec apache:1000 /usr/sbin/httpd -f /opt/www/httpd.conf
```

## TTY & parent/child handling

Notice how `su` will make `ps` be a child of a shell while `su-exec`
just executes `ps` directly.

```shell
$ docker run -it --rm alpine:edge su postgres -c 'ps aux'
PID USER TIME COMMAND
1 postgres 0:00 ash -c ps aux
12 postgres 0:00 ps aux
$ docker run -it --rm -v $PWD/su-exec:/sbin/su-exec:ro alpine:edge su-exec postgres ps aux
PID USER TIME COMMAND
1 postgres 0:00 ps aux
```

## Why reinvent gosu?

This does more or less exactly the same thing as [gosu](https://github.com/tianon/gosu)
but it is only 10kb instead of 1.8MB.

109 changes: 109 additions & 0 deletions contrib/dev-tools/su-exec/su-exec.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* set user and group id and exec */

#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static char *argv0;

static void usage(int exitcode)
{
printf("Usage: %s user-spec command [args]\n", argv0);
exit(exitcode);
}

int main(int argc, char *argv[])
{
char *user, *group, **cmdargv;
char *end;

uid_t uid = getuid();
gid_t gid = getgid();

argv0 = argv[0];
if (argc < 3)
usage(0);

user = argv[1];
group = strchr(user, ':');
if (group)
*group++ = '\0';

cmdargv = &argv[2];

struct passwd *pw = NULL;
if (user[0] != '\0') {
uid_t nuid = strtol(user, &end, 10);
if (*end == '\0')
uid = nuid;
else {
pw = getpwnam(user);
if (pw == NULL)
err(1, "getpwnam(%s)", user);
}
}
if (pw == NULL) {
pw = getpwuid(uid);
}
if (pw != NULL) {
uid = pw->pw_uid;
gid = pw->pw_gid;
}

setenv("HOME", pw != NULL ? pw->pw_dir : "/", 1);

if (group && group[0] != '\0') {
/* group was specified, ignore grouplist for setgroups later */
pw = NULL;

gid_t ngid = strtol(group, &end, 10);
if (*end == '\0')
gid = ngid;
else {
struct group *gr = getgrnam(group);
if (gr == NULL)
err(1, "getgrnam(%s)", group);
gid = gr->gr_gid;
}
}

if (pw == NULL) {
if (setgroups(1, &gid) < 0)
err(1, "setgroups(%i)", gid);
} else {
int ngroups = 0;
gid_t *glist = NULL;

while (1) {
int r = getgrouplist(pw->pw_name, gid, glist, &ngroups);

if (r >= 0) {
if (setgroups(ngroups, glist) < 0)
err(1, "setgroups");
break;
}

glist = realloc(glist, ngroups * sizeof(gid_t));
if (glist == NULL)
err(1, "malloc");
}
}

if (setgid(gid) < 0)
err(1, "setgid(%i)", gid);

if (setuid(uid) < 0)
err(1, "setuid(%i)", uid);

execvp(cmdargv[0], cmdargv);
err(1, "%s", cmdargv[0]);

return 1;
}
Loading

0 comments on commit 879f5c9

Please sign in to comment.