Skip to content

Commit

Permalink
Update User Mirror to 1.0.0 (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
AJGranowski authored Jan 31, 2025
1 parent fbfceb9 commit 56a4bc8
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 205 deletions.
22 changes: 8 additions & 14 deletions compose.watch-build-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ services:
command: ["run", "watch-build"]
entrypoint: [/entrypoint, --, npm]
environment:
CAPABILITIES: # setpriv --bounding-set options. Must be a subset of cap_add. See https://www.man7.org/linux/man-pages/man1/setpriv.1.html#OPTIONS
CHOWN_LIST: # Set by Docker User Mirror
HOST_MAPPED_GROUP: # Set by Docker User Mirror
HOST_MAPPED_GID: # Set by Docker User Mirror
HOST_MAPPED_USER: # Set by Docker User Mirror
HOST_MAPPED_UID: # Set by Docker User Mirror
SERVICE_NAME: watch-build # Used to filter CHOWN_LIST with Docker User Mirror
USER_MIRROR_CAPABILITIES: # setpriv --bounding-set options. Must be a subset of cap_add. See https://www.man7.org/linux/man-pages/man1/setpriv.1.html#OPTIONS
USER_MIRROR_CHOWN_LIST: # Set by Docker User Mirror
USER_MIRROR_HOST_USER: # Set by Docker User Mirror
USER_MIRROR_SERVICE_NAME: watch-build # USER_MIRROR_CHOWN_LIST filter used by Docker User Mirror
healthcheck:
test: ["CMD", "sh", "-c", "[ $(ps | grep nodemon | grep -v grep | wc -l) -ge 1 ]"]
interval: 5s
Expand Down Expand Up @@ -89,13 +86,10 @@ services:
command: ["run", "serve-release"]
entrypoint: [/entrypoint, --, npm]
environment:
CAPABILITIES: # setpriv --bounding-set options. Must be a subset of cap_add. See https://www.man7.org/linux/man-pages/man1/setpriv.1.html#OPTIONS
CHOWN_LIST: # Set by Docker User Mirror
HOST_MAPPED_GROUP: # Set by Docker User Mirror
HOST_MAPPED_GID: # Set by Docker User Mirror
HOST_MAPPED_USER: # Set by Docker User Mirror
HOST_MAPPED_UID: # Set by Docker User Mirror
SERVICE_NAME: server # Used to filter CHOWN_LIST with Docker User Mirror
USER_MIRROR_CAPABILITIES: # setpriv --bounding-set options. Must be a subset of cap_add. See https://www.man7.org/linux/man-pages/man1/setpriv.1.html#OPTIONS
USER_MIRROR_CHOWN_LIST: # Set by Docker User Mirror
USER_MIRROR_HOST_USER: # Set by Docker User Mirror
USER_MIRROR_SERVICE_NAME: server # USER_MIRROR_CHOWN_LIST filter used by Docker User Mirror
healthcheck:
test: ["CMD", "sh", "-c", "[ $(ps | grep http-server | grep -v npm | grep -v grep | wc -l) -eq 1 ]"]
start_period: 30s
Expand Down
11 changes: 4 additions & 7 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ services:
command: ["run", "clean-verify"]
entrypoint: [/entrypoint, --, npm]
environment:
CAPABILITIES: # setpriv --bounding-set options. Must be a subset of cap_add. See https://www.man7.org/linux/man-pages/man1/setpriv.1.html#OPTIONS
CHOWN_LIST: # Set by Docker User Mirror
HOST_MAPPED_GROUP: # Set by Docker User Mirror
HOST_MAPPED_GID: # Set by Docker User Mirror
HOST_MAPPED_USER: # Set by Docker User Mirror
HOST_MAPPED_UID: # Set by Docker User Mirror
MINISIGN_CONFIG_DIR: /run/secrets
SERVICE_NAME: npm # Used to filter CHOWN_LIST with Docker User Mirror
USER_MIRROR_CAPABILITIES: # setpriv --bounding-set options. Must be a subset of cap_add. See https://www.man7.org/linux/man-pages/man1/setpriv.1.html#OPTIONS
USER_MIRROR_CHOWN_LIST: # Set by Docker User Mirror
USER_MIRROR_HOST_USER: # Set by Docker User Mirror
USER_MIRROR_SERVICE_NAME: npm # USER_MIRROR_CHOWN_LIST filter used by Docker User Mirror
network_mode: "host"
secrets:
- source: minisign
Expand Down
155 changes: 78 additions & 77 deletions images/node/entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# License: MIT
set -eC;

VERSION='0.1.1-0336f43';
VERSION='1.0.0-911dd14';
exec 3>/dev/null;
exec 4>/dev/null;

Expand All @@ -19,13 +19,28 @@ check_setpriv() {
setpriv --reuid="$(id -u nobody)" --regid="$(id -g nobody)" --clear-groups true;
}

# Split strings by a delimiter and extract an index. Uses space as a delimiter by default.
extract_index() (
str="$1";
count="${2:-0}";
delimiter="${3:- }";
while [ "$count" -gt 0 ]; do
: $((count -= 1));
case "$str" in
*${delimiter}*) str="${str#*${delimiter}}";;
*) str='';;
esac
done
echo "${str%%${delimiter}*}";
)

# Download a release file.
# Args: release filename, output filename, release tag (defaults to latest release it not provided)
download_file() {
if [ $# -eq 3 ]; then
curl --fail -Ls -o "$2" "https://github.com/AJGranowski/docker-user-mirror/releases/download/$3/$1";
curl --fail -Ls -o "$2" "https://github.com/AJGranowski/docker-user-mirror/releases/download/${3}/${1}";
elif [ $# -eq 2 ]; then
curl --fail -Ls -o "$2" "https://github.com/AJGranowski/docker-user-mirror/releases/latest/download/$1";
curl --fail -Ls -o "$2" "https://github.com/AJGranowski/docker-user-mirror/releases/latest/download/${1}";
else
echo 'Expected at least two arguments.' >&2;
return 1;
Expand All @@ -51,7 +66,7 @@ get_release_version() {
if [ $# -eq 0 ]; then
response="$(curl -Ls -w '\n%{http_code}' 'https://github.com/AJGranowski/docker-user-mirror/releases/latest/download/version')";
else
response="$(curl -Ls -w '\n%{http_code}' "https://github.com/AJGranowski/docker-user-mirror/releases/download/$1/version")";
response="$(curl -Ls -w '\n%{http_code}' "https://github.com/AJGranowski/docker-user-mirror/releases/download/${1}/version")";
fi

case "$(echo "$response" | tail -1)" in
Expand All @@ -63,12 +78,12 @@ get_release_version() {
}

print_user() {
grep -e "^$1:" /etc/passwd;
grep -e "^${1}:" /etc/passwd;
}

# Query GitHub to check if a release tag exists.
release_tag_exists() {
case "$(curl -Ls -o /dev/null -w '%{http_code}' "https://api.github.com/repos/AJGranowski/docker-user-mirror/releases/tags/$1")" in
case "$(curl -Ls -o /dev/null -w '%{http_code}' "https://api.github.com/repos/AJGranowski/docker-user-mirror/releases/tags/${1}")" in
2*) true;;
*) false;;
esac
Expand All @@ -92,8 +107,8 @@ update() {
fi
fi

echo " Current version: $VERSION";
echo " Latest version: $latest_version";
printf ' Current version: %s\n' "$VERSION";
printf ' Latest version: %s\n' "$latest_version";

if [ "$VERSION" = "$latest_version" ]; then
return 0;
Expand All @@ -102,15 +117,15 @@ update() {
if [ "$o_yes" != true ]; then
echo '\nInstall the update? (y/n)';
read yn;
case $yn in
case "$yn" in
y*) ;;
n*) return 0;;
esac
fi

echo 'Installing update...';
download_file 'entrypoint' "${0}.tmp";
mv "${0}.tmp" "$0" && chmod +x "$0" && echo "${latest_version} installed!"; exit 0;
mv "${0}.tmp" "$0" && chmod +x "$0" && printf '%s installed!\n' "$latest_version"; exit 0;
}

# Convert "$VERSION" to "release-**" format.
Expand All @@ -124,7 +139,7 @@ version_to_release_tag() {

# Parse options.
while [ $# -gt 0 ]; do
case $1 in
case "$1" in
--setup)
o_setup=true;
;;
Expand All @@ -136,7 +151,7 @@ while [ $# -gt 0 ]; do
exec 4>&2;
;;
--version)
echo "Version: ${VERSION}";
printf 'Version: %s\n' "$VERSION";
exit 0;
;;
-y|--yes)
Expand All @@ -146,9 +161,7 @@ while [ $# -gt 0 ]; do
shift;
break;
;;
*)
break;
;;
*) break;;
esac
shift;
done
Expand All @@ -169,14 +182,14 @@ if [ "$o_setup" = true ]; then

# Attempt to install setpriv with apk.
if (apk update && apk -s add setpriv) >/dev/null 2>&1; then
apk add --no-cache "setpriv>=$SETPRIV_VERSION";
apk add --no-cache "setpriv>=${SETPRIV_VERSION}";
hash -r;
check_setpriv;
echo 'setpriv installed!';

# Attempt to install setpriv from util-linux with apt-get.
elif (apt-get update && apt-get install --dry-run util-linux) >/dev/null 2>&1; then
apt-get satisfy --no-install-recommends -y "util-linux (>=$UTIL_LINUX_VERSION)";
apt-get satisfy --no-install-recommends -y "util-linux (>=${UTIL_LINUX_VERSION})";
hash -r;
check_setpriv;
echo 'setpriv installed!';
Expand All @@ -193,12 +206,12 @@ if [ "$o_setup" = true ]; then
ppc64le) dpkgArch='ppc64el' ;;
riscv64 | s390x) dpkgArch="$unameArch" ;;
x86_64) dpkgArch='amd64' ;;
*) echo >&2 "error: unknown/unsupported architecture '$unameArch'"; exit 1 ;;
*) printf 'error: unknown/unsupported architecture: %s\n' "$unameArch" >&2; exit 1;;
esac

mkdir -p /usr/local/bin/;
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch";
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc";
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${dpkgArch}";
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${dpkgArch}.asc";

GNUPGHOME="$(mktemp -d)";
if command -v gpg >/dev/null; then
Expand All @@ -214,51 +227,31 @@ if [ "$o_setup" = true ]; then
else
set -eC;

if [ -z "$HOST_MAPPED_GID" ]; then
printf 'HOST_MAPPED_GID is unset\n' >&2;
guard_failure=true;
fi

if [ -z "$HOST_MAPPED_GROUP" ]; then
printf 'HOST_MAPPED_GROUP is unset\n' >&2;
guard_failure=true;
fi

if [ -z "$HOST_MAPPED_UID" ]; then
printf 'HOST_MAPPED_UID is unset\n' >&2;
guard_failure=true;
fi

if [ -z "$HOST_MAPPED_USER" ]; then
printf 'HOST_MAPPED_USER is unset\n' >&2;
if [ -z "$USER_MIRROR_HOST_USER" ]; then
echo 'USER_MIRROR_HOST_USER is unset' >&2;
guard_failure=true;
fi

if [ "$guard_failure" = true ]; then
exit 1;
fi

echo "CAPABILITIES=$CAPABILITIES" >&3;
echo "HOST_MAPPED_GROUP=$HOST_MAPPED_GROUP" >&3;
echo "HOST_MAPPED_GID=$HOST_MAPPED_GID" >&3;
echo "HOST_MAPPED_USER=$HOST_MAPPED_USER" >&3;
echo "HOST_MAPPED_UID=$HOST_MAPPED_UID" >&3;
echo "SERVICE_NAME=$SERVICE_NAME" >&3;
printf 'USER_MIRROR_CAPABILITIES=%s\n' "$USER_MIRROR_CAPABILITIES" >&3;
printf 'USER_MIRROR_HOST_USER=%s\n' "$USER_MIRROR_HOST_USER" >&3;
printf 'USER_MIRROR_SERVICE_NAME=%s\n' "$USER_MIRROR_SERVICE_NAME" >&3;

host_user_name="$(extract_index "$USER_MIRROR_HOST_USER" 0 ':')";
host_user_id="$(extract_index "$USER_MIRROR_HOST_USER" 1 ':')";
host_user_group="$(extract_index "$USER_MIRROR_HOST_USER" 2 ':')";
host_user_gid="$(extract_index "$USER_MIRROR_HOST_USER" 3 ':')";

# Create/replace user if non-root.
if [ $HOST_MAPPED_UID -ne 0 ] && [ "$HOST_MAPPED_USER" != 'root' ]; then
if id -u "$HOST_MAPPED_USER" >/dev/null 2>&1; then
echo "Removing user matching $HOST_MAPPED_USER: $(print_user $HOST_MAPPED_USER)" >&3;
if command -v userdel >/dev/null; then
userdel "$HOST_MAPPED_USER";
else
deluser "$HOST_MAPPED_USER" 2>/dev/null;
fi
fi
if [ "$host_user_id" -ne 0 ] && [ "$host_user_name" != 'root' ]; then
export SHELL='/bin/sh';

uid_username="$(awk -F: "\$3 == $HOST_MAPPED_UID {print \$1}" /etc/passwd)";
uid_username="$(awk -F: "\$3 == ${host_user_id} {print \$1}" /etc/passwd)";
if id -u "$uid_username" >/dev/null 2>&1; then
echo "Removing user matching UID $HOST_MAPPED_UID: $(print_user $uid_username)" >&3;
printf 'Removing user matching UID %s: %s\n' "$host_user_id" "$(print_user "$uid_username")" >&3;
if command -v userdel >/dev/null; then
userdel "$uid_username";
else
Expand All @@ -267,65 +260,73 @@ else
fi

if command -v groupadd >/dev/null; then
groupadd -g $HOST_MAPPED_GID "$HOST_MAPPED_GROUP" || true;
groupadd -g "$host_user_gid" "$host_user_group" || true;
else
addgroup -g $HOST_MAPPED_GID "$HOST_MAPPED_GROUP" || true;
addgroup -g "$host_user_gid" "$host_user_group" || true;
fi
gid_groupname="$(awk -F: "\$3 == $HOST_MAPPED_GID {print \$1}" /etc/group)";
gid_groupname="$(awk -F: "\$3 == ${host_user_gid} {print \$1}" /etc/group)";

if command -v useradd >/dev/null; then
useradd -g $HOST_MAPPED_GID -m -s /bin/sh -u $HOST_MAPPED_UID $HOST_MAPPED_USER 2>&4;
useradd -c 'Mirrored Host User' -g "$host_user_gid" -m -s "$SHELL" -u "$host_user_id" "$host_user_name" 2>&4;
else
adduser -D -G "$gid_groupname" -s /bin/sh -u $HOST_MAPPED_UID $HOST_MAPPED_USER 2>&4;
adduser -D -g 'Mirrored Host User' -G "$gid_groupname" -s "$SHELL" -u "$host_user_id" "$host_user_name" 2>&4;
fi
echo "Created user: $(print_user "$HOST_MAPPED_USER")" >&3;
printf 'Created user: %s\n' "$(print_user "$host_user_name")" >&3;

HOME_DIR="/home/$HOST_MAPPED_USER";
export HOME="/home/${host_user_name}";
else
HOME_DIR="/root";
export HOME='/root';
fi

export LOGNAME="$host_user_name";
export USER="$host_user_name";

# Set the ownership of a set of items to the user.
while read service_name; do
if [ -z "$service_name" ]; then
continue;
fi

case $service_name in
case "$service_name" in
/*)
chown_path="$service_name";
unset service_name;
;;
*) read chown_path;;
esac

if [ -n "$service_name" ] && [ "$service_name" != "$SERVICE_NAME" ]; then
if [ -n "$service_name" ] && [ "$service_name" != "$USER_MIRROR_SERVICE_NAME" ]; then
continue;
fi

if ! [ -e "$chown_path" ]; then
if [ ! -e "$chown_path" ]; then
printf "cannot access '%s': No such file or directory\n" "$chown_path" >&4;
continue;
fi

chown -c "$HOST_MAPPED_UID:$HOST_MAPPED_GID" "$chown_path" >&3;
chown -c "${host_user_id}:${host_user_gid}" "$chown_path" >&3;
done <<EOF
$CHOWN_LIST
$USER_MIRROR_CHOWN_LIST
EOF

if [ -n "$USER_MIRROR_CAPABILITIES" ]; then
capabilities="-all,$USER_MIRROR_CAPABILITIES";
else
capabilities='-all';
fi

unset USER_MIRROR_CAPABILITIES;
unset USER_MIRROR_CHOWN_LIST;
unset USER_MIRROR_HOST_USER;
unset USER_MIRROR_SERVICE_NAME;

# Execute using $HOST_MAPPED_UID.
if check_setpriv >/dev/null 2>&1; then
if [ -n "$CAPABILITIES" ]; then
capabilities="-all,$CAPABILITIES";
else
capabilities='-all';
fi

echo "HOME=$HOME_DIR LOGNAME=$HOST_MAPPED_USER SHELL=/bin/sh USER=$HOST_MAPPED_USER exec setpriv --bounding-set \"$capabilities\" --init-groups --no-new-privs --reuid=$HOST_MAPPED_UID --regid=$HOST_MAPPED_GID ..." >&3;
HOME="$HOME_DIR" LOGNAME="$HOST_MAPPED_USER" SHELL='/bin/sh' USER="$HOST_MAPPED_USER" exec setpriv --bounding-set "$capabilities" --init-groups --no-new-privs --reuid=$HOST_MAPPED_UID --regid=$HOST_MAPPED_GID "$@";
echo "exec setpriv --bounding-set \"${capabilities}\" --init-groups --no-new-privs --reuid=${host_user_id} --regid=${host_user_gid} ..." >&3;
exec setpriv --bounding-set "$capabilities" --init-groups --no-new-privs --reuid=${host_user_id} --regid=${host_user_gid} "$@";
elif command -v gosu >/dev/null; then
echo "HOME=$HOME_DIR LOGNAME=$HOST_MAPPED_USER SHELL=/bin/sh USER=$HOST_MAPPED_USER exec gosu "$HOST_MAPPED_UID:$HOST_MAPPED_GID" ..." >&3;
HOME="$HOME_DIR" LOGNAME="$HOST_MAPPED_USER" SHELL='/bin/sh' USER="$HOST_MAPPED_USER" exec gosu "$HOST_MAPPED_UID:$HOST_MAPPED_GID" "$@";
echo "exec gosu \"${host_user_id}:${host_user_gid}\" ..." >&3;
exec gosu "${host_user_id}:${host_user_gid}" "$@";
else
printf 'Unable to set user\n' >&2;
exit 1;
Expand Down
Loading

0 comments on commit 56a4bc8

Please sign in to comment.