Can't add a user with a high UID in docker Alpine - linux

I'm trying to create a new user with UID 1340816314 inside an Alpine Linux Docker container in order to have a user with an UID matching a specific user on the host.
The problem is that I'm facing adduser: number 1340816314 is not in 0..256000 range even if I redefine the value of UID_MAX inside /etc/login.defs by following adduser man page. I don't think by the way that it has any impact as the adduser command in Alpine is from BusyBox.
Here is the log of what I try to do:
$ docker run -it --rm alpine:3.4 sh
/ # adduser -D -g '' -u 1340816314 user
adduser: number 1340816314 is not in 0..256000 range
/ # echo "UID_MAX 1340816314" > /etc/login.defs
/ # adduser -D -g '' -u 1340816314 user
adduser: number 1340816314 is not in 0..256000 range
/ # echo "UID_MAX 1340816315" > /etc/login.defs
/ # adduser -D -g '' -u 1340816314 user
adduser: number 1340816314 is not in 0..256000 range
Do you know how to add a user with a large UID in Alpine Linux inside a Docker container?

There is a more elegant solution to the high UID/GID in Alpine.
The package shadow contains the useradd and groupadd utilities which in turn supports higher values. Not sure which is the upper bound of those utils and if the whole 2^32 space is supported, but I've tested with values over 600 million and it works.
For example the commands to achieve this would look something like this:
UID=666000666
GID=999000999
apk add shadow
/usr/sbin/groupadd -g ${GID} my_group
/usr/sbin/useradd -s /bin/sh -g ${GID} -u ${UID} my_user
Note that I'm passing the shell variable to useradd as by default it tries to use /bin/bash which is not installed.

Here is a working but dirty workaround, by manually creating the user, using $UID_TO_SET as the bash variable containing the high UID to set:
# Create user
echo "user:x:$UID_TO_SET:$UID_TO_SET::/home/user:" >> /etc/passwd
## thanks for http://stackoverflow.com/a/1094354/535203 to compute the creation date
echo "user:!:$(($(date +%s) / 60 / 60 / 24)):0:99999:7:::" >> /etc/shadow
echo "user:x:$UID_TO_SET:" >> /etc/group
mkdir /home/user && chown user: /home/user

This problem actually inspired me to solve it in a way, which is convenient and easy to use for this specific use case of getting long IDs to work on Alpine and BusyBox based Docker images.
https://github.com/theAkito/userdef
Example Usage:
## Get the binary.
## The default Docker Tag provides the Alpine (musl) based binary.
FROM akito13/userdef AS base
## Pull the image you want to modify the executing user of.
FROM gitea/gitea:1.16.5-linux-amd64-rootless
## We temporarily need to use the root user,
## as we are doing administrative tasks, like e.g. modifying an OS user.
USER root:root
COPY --from=base /userdef /userdef
## 1. Change the existing user.
## 2. Use that user to `chown` relevant folders.
## 3. Remove the binary, because the user has been changed,
## i.e. our job is done here.
RUN /userdef -h=/var/lib/gitea/git -n=git -u=9234 -g=9234 && \
chown git:git -R /var/lib/gitea /etc/gitea && \
rm -f /userdef
## Switch to the now relevant executing user.
USER 9234:9234
## Taken from https://github.com/go-gitea/gitea/blob/66f2210feca0b50d305a46a203c2b3d2f4d3790b/Dockerfile.rootless#L71-L72
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD []

Elaboration on the only working answer from #anthony-o
# FOR UID higher than 60000, users need to be added like this!
RUN export GUID_TO_SET=100 && \
export UID_TO_SET=66666 && \
export USER=youruser && \
echo "${USER}:x:${UID_TO_SET}:${GUID_TO_SET}:Linux User,,,:/home/${USER}:/bin/sh" >> /etc/passwd && \
echo "${USER}:!:$(($(date +%s) / 60 / 60 / 24)):0:99999:7:::" >> /etc/shadow && \
mkdir /home/${USER} && \
chown ${USER}:${GUID_TO_SET} /home/${USER}

While the "dirty" methods above do work, they are ugly and not easy for some people to use. It would be easy to make a mistake with copy and paste.
So given that this is related to Docker, I've got a better way using multi-stage builds.
## Busybox adduser doesn't allow UIDs over 60000
## So we'll use a debian image to make the user
## And then copy the files and grep out the new user into the busybox container
FROM debian as base
RUN useradd -D -g '' -u 1340816314 user
FROM alpine as image
COPY --from=base /etc/passwd /root/passwd
RUN grep user /root/passwd >> /etc/passwd && rm -f /root/passwd
USER user
While this example uses debian, you can use any container image you want in the first stage instead of Debian. As long as the container doesn't error out because of an arbitrarily low UID limit in the command you use, it should be fine, so I encourage you to play around with different containers as the base image and also check if both useradd and adduser commands are available and which one might support the UID number you need.

Related

Creating user with uid 1000 on alpine Docker image

When I'm trying to create user with UID=1000 in Dockerfile
FROM alpine:3.16
RUN addgroup -g 1000 sail
RUN adduser -g 1000 -u 1000 sail
I'm getting error:
Step 3/3 : RUN adduser -g 1000 -u 1000 sail
---> Running in 0c4ce7e0bddf
adduser: uid '1000' in use
The command '/bin/sh -c adduser -g 1000 -u 1000 sail' returned a non-zero code: 1
But if I'm build a container without this stage and look to /etc/passwd there is no user with UID=1000. So it's exists only while building.
How to create user with UID=1000 properly in alpine?
Your syntax is incorrect, the adduser command of Alpine is the BusyBox one, so, unlike the "regular" adduser, here are its help page:
BusyBox v1.35.0 (2022-08-01 15:14:44 UTC) multi-call binary.
Usage: adduser [OPTIONS] USER [GROUP]
Create new user, or add USER to GROUP
-h DIR Home directory
-g GECOS GECOS field
-s SHELL Login shell
-G GRP Group
-S Create a system user
-D Don't assign a password
-H Don't create home directory
-u UID User id
-k SKEL Skeleton directory (/etc/skel)
You can easily go through it, running the command:
docker run -ti --rm alpine:3.16 adduser
The important part of information here are:
-g GECOS GECOS field
-G GRP Group
Where you can see that, a group, in BusyBox adduser requires your to be added with the option G, in capital letter, and that the option g in lowercase if for something else.
The option allows you to add a group, and not a GID, so you'll need the command:
RUN adduser -G sail -u 1000 sail
Furthermore, if you run that command, the shell will prompt you to fill in a password. You will need to skip this with the D option:
-D Don't assign a password
And so, your Dockerfile ends up being:
FROM alpine:3.16
RUN addgroup -g 1000 sail \
&& adduser -G sail -u 1000 sail -D
Note that it is always a good idea, in Docker, to reduce as much as possible the number of layers you are creating by running subsequent RUN instruction, for further reading on this, see here.

Is it possible to map a user inside the docker container to an outside user?

I know that one can use the --user option with Docker to run a container as a certain user, but in my case, my Docker image has a user inside it, let us call that user manager. Now is it possible to map that user to a user on host? For example, if there is a user john on the host, can we map john to manager?
Yes, you can set the user from the host, but you should modify your Dockerfile a bit to deal with run time user.
FROM alpine:latest
# Override user name at build. If build-arg is not passed, will create user named `default_user`
ARG DOCKER_USER=default_user
# Create a group and user
RUN addgroup -S $DOCKER_USER && adduser -S $DOCKER_USER -G $DOCKER_USER
# Tell docker that all future commands should run as this user
USER $DOCKER_USER
Now, build the Docker image:
docker build --build-arg DOCKER_USER=$(whoami) -t docker_user .
The new user in Docker will be the Host user.
docker run --rm docker_user ash -c "whoami"
Another way is to pass host user ID and group ID without creating the user in Dockerfile.
export UID=$(id -u)
export GID=$(id -g)
docker run -it \
--user $UID:$GID \
--workdir="/home/$USER" \
--volume="/etc/group:/etc/group:ro" \
--volume="/etc/passwd:/etc/passwd:ro" \
--volume="/etc/shadow:/etc/shadow:ro" \
alpine ash -c "whoami"
You can further read more about the user in docker here and here.
Another way is through an entrypoint.
Example
This example relies on gosu which is present in recent Debian derivatives, not yet in Alpine 3.13 (but is in edge).
You could run this image as follow:
docker run --rm -it \
--env UID=$(id -u) \
--env GID=$(id -g) \
-v "$(pwd):$(pwd)" -w "$(pwd)" \
imagename
tree
.
├── Dockerfile
└── files/
└── entrypoint
Dockerfile
FROM ...
# [...]
ARG DOCKER_USER=default_user
RUN addgroup "$DOCKER_USER" \
&& adduser "$DOCKER_USER" -G "$DOCKER_USER"
RUN wget -O- https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64 |\
install /dev/stdin /usr/local/bin/gosu
COPY files /
RUN chmod 0755 /entrypoint \
&& sed "s/\$DOCKER_USER/$DOCKER_USER/g" -i /entrypoint
ENTRYPOINT ["/entrypoint"]
files/entrypoint
#!/bin/sh
set -e
set -u
: "${UID:=0}"
: "${GID:=${UID}}"
if [ "$#" = 0 ]
then set -- "$(command -v bash 2>/dev/null || command -v sh)" -l
fi
if [ "$UID" != 0 ]
then
usermod -u "$UID" "$DOCKER_USER" 2>/dev/null && {
groupmod -g "$GID" "$DOCKER_USER" 2>/dev/null ||
usermod -a -G "$GID" "$DOCKER_USER"
}
set -- gosu "${UID}:${GID}" "${#}"
fi
exec "$#"
Notes
UID is normally a read-only variable in bash, but it will work as expected if set by the docker --env flag
I choose gosu for it's simplicity, but you could make it work with su or sudo; it will need more configuration however
if you don't want to specify two --env switch, you could do something like: --env user="$(id -u):$(id -g)" and in the entrypoint: uid=${user%:*} gid=${user#*:}; note at this point the UID variable will be read-only in bash that's why I switched to lower-case... rest of the adaptation is left to the reader
There is no simple solution that handles all use cases. Solving these problems is continuous work, a part of life in the containerized world.
There is no magical parameter that you could add to a docker exec or docker run invocation and reliably cause the containerized software to no longer run into permissions issues during operations on host-mapped volumes. Unless your mapped directories are chmod-0777-and-come-what-may (DON'T), you will be running into permissions issues and you will be solving them as you go, and this is the task you should try becoming efficient at, instead of trying to find a miracle once-and-forever solution that will never exist.

Set docker image username at container creation time?

I have an OpenSuse 42.3 docker image that I've configured to run a code. The image has a single user(other than root) called "myuser" that I create during the initial Image generation via the Dockerfile. I have three script files that generate a container from the image based on what operating system a user is on.
Question: Can the username "myuser" in the container be set to the username of the user that executes the container generation script?
My goal is to let a user pop into the container interactively and be able to run the code from within the container. The code is just a single binary that executes and has some IO, so I want the user's directory to be accessible from within the container so that they can navigate to a folder on their machine and run the code to generate output in their filesystem.
Below is what I have constructed so far. I tried setting the USER environment variable during the linux script's call to docker run, but that didn't change the user from "myuser" to say "bob" (the username on the host machine that started the container). The mounting of the directories seems to work fine. I'm not sure if it is even possible to achieve my goal.
Linux Container script:
username="$USER"
userID="$(id -u)"
groupID="$(id -g)"
home="${1:-$HOME}"
imageName="myImage:ImageTag"
containerName="version1Image"
docker run -it -d --name ${containerName} -u $userID:$groupID \
-e USER=${username} --workdir="/home/myuser" \
--volume="${home}:/home/myuser" ${imageName} /bin/bash \
Mac Container script:
username="$USER"
userID="$(id -u)"
groupID="$(id -g)"
home="${1:-$HOME}"
imageName="myImage:ImageTag"
containerName="version1Image"
docker run -it -d --name ${containerName} \
--workdir="/home/myuser" \
--v="${home}:/home/myuser" ${imageName} /bin/bash \
Windows Container script:
ECHO OFF
SET imageName="myImage:ImageTag"
SET containerName="version1Image"
docker run -it -d --name %containerName% --workdir="/home/myuser" -v="%USERPROFILE%:/home/myuser" %imageName% /bin/bash
echo "Container %containerName% was created."
echo "Run the ./startWindowsLociStream script to launch container"
The below code has been checked into https://github.com/bmitch3020/run-as-user.
I would handle this in an entrypoint.sh that checks the ownership of /home/myuser and updates the uid/gid of the user inside your container. It can look something like:
#!/bin/sh
set -x
# get uid/gid
USER_UID=`ls -nd /home/myuser | cut -f3 -d' '`
USER_GID=`ls -nd /home/myuser | cut -f4 -d' '`
# get the current uid/gid of myuser
CUR_UID=`getent passwd myuser | cut -f3 -d: || true`
CUR_GID=`getent group myuser | cut -f3 -d: || true`
# if they don't match, adjust
if [ ! -z "$USER_GID" -a "$USER_GID" != "$CUR_GID" ]; then
groupmod -g ${USER_GID} myuser
fi
if [ ! -z "$USER_UID" -a "$USER_UID" != "$CUR_UID" ]; then
usermod -u ${USER_UID} myuser
# fix other permissions
find / -uid ${CUR_UID} -mount -exec chown ${USER_UID}.${USER_GID} {} \;
fi
# drop access to myuser and run cmd
exec gosu myuser "$#"
And here's some lines from a relevant Dockerfile:
FROM debian:9
ARG GOSU_VERSION=1.10
# run as root, let the entrypoint drop back to myuser
USER root
# install prereq debian packages
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
apt-transport-https \
ca-certificates \
curl \
vim \
wget \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install gosu
RUN dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
&& chmod 755 /usr/local/bin/gosu \
&& gosu nobody true
RUN useradd -d /home/myuser -m myuser
WORKDIR /home/myuser
# entrypoint is used to update uid/gid and then run the users command
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD /bin/sh
Then to run it, you just need to mount /home/myuser as a volume and it will adjust permissions in the entrypoint. e.g.:
$ docker build -t run-as-user .
$ docker run -it --rm -v $(pwd):/home/myuser run-as-user /bin/bash
Inside that container you can run id and ls -l to see that you have access to /home/myuser files.
Usernames are not important. What is important are the uid and gid values.
User myuser inside your container will have a uid of 1000 (first non-root user id). Thus when you start your container and look at the container process from the host machine, you will see that the container is owned by whatever user having a uid of 1000 on the host machine.
You can override this by specifying the user once you run your container using:
docker run --user 1001 ...
Therefore if you want the user inside the container, to be able to access files on the host machine owned by a user having a uid of 1005 say, just run the container using --user 1005.
To better understand how users map between the container and host take a look at this wonderful article. https://medium.com/#mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf
First of all (https://docs.docker.com/engine/reference/builder/#arg):
Warning: It is not recommended to use build-time variables for passing
secrets like github keys, user credentials etc. Build-time variable
values are visible to any user of the image with the docker history
command.
But if you still need to do this, read https://docs.docker.com/engine/reference/builder/#arg:
A Dockerfile may include one or more ARG instructions. For example,
the following is a valid Dockerfile:
FROM busybox
ARG user1
ARG buildno
...
and https://docs.docker.com/engine/reference/builder/#user:
The USER instruction sets the user name (or UID) and optionally the
user group (or GID) to use when running the image and for any RUN, CMD
and ENTRYPOINT instructions that follow it in the Dockerfile.
USER <user>[:<group>] or
USER <UID>[:<GID>]

How do I add a container user to a user group in the Docker host?

I'm currently experimenting on Docker specifically on the aspect where a user inside given container affects the Docker host itself. Ultimately, I want the user to be able to run containers on its very own host from inside its container. It kind of sounds a little absurd, but I'm hoping it works.
At the moment, I'm looking for a way for that given user to be part of the Docker host's docker group. Is there a way to do that?
From the perspective of the Docker host, any users inside the container are treated exactly the same as a user outside the container with the same UID (not the same name!), regardless of whether the UID is actually in use on the host. Unfortunately, it appears that only users with a username can belong to groups, so you can't just add the UID to the group. Instead, you need to add the host user with the same UID to the group (or create a user with that UID if one doesn't exist).
I wrote a bash script to generate dockerfiles on the fly with the current user added on the container and the docker group. Here is the meat of it:
# get the current user's info
user=$USER
uid=`id -u $user`
gid=`id -g $user`
# get gid for group docker
RESULT=$(id -g docker 2>&1)
if [ 1 -ne $? ] ; then
docker_gid=$RESULT
fi
echo FROM ubuntu:14.04
echo MAINTAINER Nobody "no-reply#nothere.com"
echo USER root
echo "RUN apt-get update && apt-get -y install docker-engine"
echo "RUN /bin/bash -c 'getent passwd ${user} || \
adduser --system --gid ${gid} ${docker_group} --uid ${uid} --shell /bin/bash ${user} || \
usermod -l ${user} \$(getent passwd ${uid} | cut -d: -f1)'"
if [ -n "$docker_gid" ] ; then
echo "RUN /bin/bash -c 'getent group docker && groupmod --gid ${docker_gid} docker \
|| groupadd --gid ${docker_gid} docker'"
echo "RUN /bin/bash -c 'groups ${user} | grep docker || usermod --groups ${docker_gid} ${user}'"
fi
HTH!

Using mkdir -m -p and chown together correctly

I would like to create a directory using a bash script and then set the mode to 00755 at the same time
mkdir -p -m=00755 "/dir/dir2"
Is this the correct way of using them together and can I also add chown command to the same line while creating them?
It goes a little like this:
install -d -m 0755 -o someuser -g somegroup /dir/dir2
If you want to set the owner during creation, you can simply impersonate as this user, using sudo for example:
sudo -uTHE_USER mkdir -p -m=00755 "/dir/dir2"
This has the advantage that there will be no time difference between creation and changing the ownership, which could otherwise being harmful if exploited.
Yes that should work. As for the chown, simply follow the command ' && chown... '. && is similar to ; except the next command ONLY executes if the previous command exits success (0).

Resources