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

Change images to run as non-root user by default #45

Closed
wants to merge 9 commits into from
Closed

Change images to run as non-root user by default #45

wants to merge 9 commits into from

Conversation

yoshioterada
Copy link
Member

Currently, the container images are running as root user as follows.
And I hope that it will run as a non-root user by default for security purposes.

The following log will be seen at the startup time of Spring Boot.
"/app/app.jar started by root" was wrote.

2022-10-20 17:32:02.045  INFO 1 --- [main] com.yoshio3.HelloSampleApplication : 
Starting HelloSampleApplication v0.0.1-SNAPSHOT using Java 17.0.4.1 on 7446eed214e7 
with PID 1 (/app/app.jar started by root in /app)

If we use this fix, the application will run as a non-root user ("javauser") by default as follows.
"/app/app.jar started by javauser" was wrote.

2022-10-20 17:30:28.103  INFO 1 --- [main] com.yoshio3.HelloSampleApplication : 
Starting HelloSampleApplication v0.0.1-SNAPSHOT using Java 17.0.4.1 on 9a2adf159e03 
with PID 1 (/app/app.jar started by javauser in /app)

To run the container more securely, this pull request will be useful. And if this pull request is not included, every user needs to write a look like the following Dockerfile to run as a non-root user

##################################################################
# Stage 1: Create User and Group
##################################################################
FROM mcr.microsoft.com/cbl-mariner/base/core:2.0 AS CREATE-DEPENDS-FILES

RUN mkdir /staging \
    && mkdir /staging/etc \
    && tdnf install -y  --releasever=2.0 shadow-utils \
    && groupadd --system -g 101 java-app \
    && useradd -u 101 -g java-app --shell /bin/false --home-dir /dev/null --system javausers \
    && tdnf clean all \
   # Copy user/group info to staging
   && cp /etc/passwd /staging/etc/passwd \
   && cp /etc/group /staging/etc/group

##################################################################
# Stage 2: Create User and Group
##################################################################
FROM mcr.microsoft.com/openjdk/jdk:17-distroless

COPY --from=CREATE-DEPENDS-FILES /staging/ /
USER javauser
WORKDIR /app
ENV LANG='ja_JP.UTF-8' LANGUAGE='ja_JP:ja' LC_ALL='ja_JP.UTF-8'
ENV TZ='Asia/Tokyo'
ENV JAVA_HOME=/app

COPY ./target/hello-sample-0.0.1-SNAPSHOT.jar app.jar

ENTRYPOINT ["java","-Xmx1g","-XX:+UseParallelGC","-XX:MaxRAMPercentage=75","-jar","/app/app.jar"]
EXPOSE 8080

Currently the container images is running as root user as follows.
And it is recommended to run as non-root user for the security purpose.

In fact, following log will be seen at the startup time of Spring Boot.
```text
2022-10-20 17:32:02.045  INFO 1 --- [           main] com.yoshio3.HelloSampleApplication       : Starting HelloSampleApplication v0.0.1-SNAPSHOT using Java 17.0.4.1 on 7446eed214e7 with PID 1 (/app/app.jar started by root in /app)
```

If we use this fix, the application will run as non-root user ("javauser") by default like follows.

```text
2022-10-20 17:30:28.103  INFO 1 --- [           main] com.yoshio3.HelloSampleApplication       : Starting HelloSampleApplication v0.0.1-SNAPSHOT using Java 17.0.4.1 on 9a2adf159e03 with PID 1 (/app/app.jar started by javauser in /app)
```

In order to run the container more secure, this pull request will be useful.
And if this pull request is not included, every user need to write look like following Dockerfile to run as non-root user

```Dockerfile
##################################################################
# Stage 1: Create User and Group
##################################################################
FROM mcr.microsoft.com/cbl-mariner/base/core:2.0 AS CREATE-DEPENDS-FILES

RUN mkdir /staging \
    && mkdir /staging/etc \
    && tdnf install -y  --releasever=2.0 shadow-utils \
    && groupadd --system -g 101 java-app \
    && useradd -u 101 -g java-app --shell /bin/false --home-dir /dev/null --system javausers \
    && tdnf clean all \
   # Copy user/group info to staging
   && cp /etc/passwd /staging/etc/passwd \
   && cp /etc/group /staging/etc/group

##################################################################
# Stage 2: Create User and Group
##################################################################
FROM mcr.microsoft.com/openjdk/jdk:17-distroless

COPY --from=CREATE-DEPENDS-FILES /staging/ /
USER javauser
WORKDIR /app
ENV LANG='ja_JP.UTF-8' LANGUAGE='ja_JP:ja' LC_ALL='ja_JP.UTF-8'
ENV TZ='Asia/Tokyo'
ENV JAVA_HOME=/app

COPY ./target/hello-sample-0.0.1-SNAPSHOT.jar app.jar

ENTRYPOINT ["java","-Xmx1g","-XX:+UseParallelGC","-XX:MaxRAMPercentage=75","-jar","/app/app.jar"]
EXPOSE 8080
```
@karianna
Copy link
Member

Thank you for the PR and the suggestion. We'll discuss it internally but I suspect that this particular implementation (of adding a MSFT provided javauser will be seen as something that the customer should choose to do themselves (and not have it supplied for them)

@brunoborges - thoughts?

@brunoborges
Copy link
Member

Some people may want to run a server on port 80 inside the container. And for that to work, the JVM must be running as root. So, to cover all scenarios, we've decided to go with root.

But as Martijn pointed out, customers are more than welcome to customize their layer as they write their own Dockerfiles, and configure a non-root user.

Indeed, our documentation does not cover this well, and we should at the very least, explain which user it runs by default, and how to customize that on a downstream Docker image layer built by customers.

@jdubois
Copy link

jdubois commented Oct 20, 2022

Running as root is usually seen as a bad practice ( there is tons of documentation on this, https://sysdig.com/blog/dockerfile-best-practices/ for example), so I believe it should be the other way around : non-root by default, then users can reconfigure this as root if they have specific needs.

@yoshioterada
Copy link
Member Author

@karianna san, @brunoborges san
First, Thank you so much for your messages and reply.

the customer should choose to do themselves (and not have it supplied for them

Yes, if the customer can choose the USER that they would like to configure, it is better.
However, if we use the base image as "CBL-Mariner," it is difficult now and it will fail by default as follows.

At first, I created the following Dockerfile and build the images.

FROM mcr.microsoft.com/openjdk/jdk:17-distroless

USER javauser
WORKDIR /app
ENV LANG='ja_JP.UTF-8' LANGUAGE='ja_JP:ja' LC_ALL='ja_JP.UTF-8'
ENV TZ='Asia/Tokyo'
ENV JAVA_HOME=/app

COPY ./target/hello-sample-0.0.1-SNAPSHOT.jar app.jar

ENTRYPOINT ["java","-Xmx1g","-XX:+UseParallelGC","-XX:MaxRAMPercentage=75","-jar","/app/app.jar"]
EXPOSE 8080 

Then, If I run the above images, the following error happens.

> docker run -p 8080:8080 -it tyoshio2002/sample1:1.1                          
docker: Error response from daemon: unable to find user javauser: no matching entries in passwd file.

Even though the container builds success, the above "javauser" was not added to the container. Because the "CBL-Mariner" image doesn't have "useradd,groupadd" command by default and failed.
And there was not enough info to add the user. So I asked the products team of "CBL-Mariner," and I got it. So it means that it is difficult for customers to customize the USER now.

Perhaps, many users would like to run the container as "non-root" users because it is the best practice that @jdubois san mentioned above.
And EVERY user will face the above problem since now. And the user will ask the same things every time without the official documents.

RUN mkdir /staging \
    && mkdir /staging/etc \
    && tdnf install -y  --releasever=2.0 shadow-utils \
    && groupadd --system -g 101 java-app \
    && useradd -u 101 -g java-app --shell /bin/false --home-dir /dev/null --system javausers \
    && tdnf clean all \
   # Copy user/group info to staging
   && cp /etc/passwd /staging/etc/passwd \
   && cp /etc/group /staging/etc/group

To avoid confusion and every user must do the above, I created the pull request.

If you decide to run the container as root, at least we should explain how to customize the USER in the official document. And I would like to ask you to do it please.

BTW, I already wrote it by Japanese as follows.
https://qiita.com/yoshioterada/items/93260d9eec501f2ffeba

@karianna
Copy link
Member

@yoshioterada - As a side note - the GH Actions are failing. I'm not sure if this is due to the #comment you've added in the middle of the user setup logic in the PR.

@karianna
Copy link
Member

if we make the change then I think moving to non-root as a default will be a breaking change for existing customers who are using these images, something we need to keep in mind.

Java Version updated
During the build images in the GitHub Actions, Following error had showed and failed to build the images.

```text
Step 1/18 : ARG INSTALLER_IMAGE="mcr.microsoft.com/cbl-mariner/base/core"
Step 2/18 : ARG INSTALLER_TAG="2.0"
Step 3/18 : ARG BASE_IMAGE="mcr.microsoft.com/cbl-mariner/distroless/base"
Step 4/18 : ARG BASE_TAG="2.0"
invalid reference format
Step 5/18 : FROM ${INSTALLER_IMAGE}:${INSTALLER_TAG} AS installer
```
create test work flow
@yoshioterada
Copy link
Member Author

yoshioterada commented Oct 21, 2022

@karianna san,

For the GitHub Action failed issue.

It may be due to the workflow as a build-images.yml file, not from my modification.
On the above YAML file, the following was written.

docker build 
-t mcr.microsoft.com/openjdk/jdk:${{ matrix.jdkversion }}-${{ matrix.baseimage }} 
-f ./docker/${{ matrix.baseimage }}/Dockerfile.msopenjdk-${{ matrix.jdkversion }}-jdk 
./docker/${{ matrix.baseimage }}/ 
--build-arg INSTALLER_IMAGE=${{ env.INSTALLER_IMAGE }} 
--build-arg INSTALLER_TAG=${{ matrix.installer_tag }} 
--build-arg BASE_IMAGE=${{ env.BASE_IMAGE }} 
--build-arg BASE_TAG=${{ matrix.base_tag }}

And from the result of the GitHub Action, the following was invoked.

Run docker build 
-t mcr.microsoft.com/openjdk/jdk:11-mariner 
-f ./docker/mariner/Dockerfile.msopenjdk-11-jdk 
./docker/mariner/ 
--build-arg IMAGE= 
--build-arg TAG='2.0'

At that time, the following error happened.

Sending build context to Docker daemon  5.[6](https://github.com/yoshioterada/openjdk-docker/actions/runs/3298349428/jobs/5440350467#step:7:7)32kB

Step 1/10 : ARG IMAGE="mcr.microsoft.com/cbl-mariner/base/core"
Step 2/10 : ARG TAG="2.0"
Step 3/[10](https://github.com/yoshioterada/openjdk-docker/actions/runs/3298349428/jobs/5440350467#step:7:11) : FROM ${IMAGE}:${TAG}
invalid reference format
Error: Process completed with exit code 1.

In my local environment, I tried to execute the above command, then the same error happened.

> docker build -t mcr.microsoft.com/openjdk/jdk:11-mariner -f ./docker/mariner/Dockerfile.msopenjdk-11-jdk \
./docker/mariner/ --build-arg IMAGE= --build-arg TAG='2.0'
[+] Building 0.1s (2/2) FINISHED                                                                                                                                                  
 => [internal] load build definition from Dockerfile.msopenjdk-11-jdk                                                                                                        0.0s
 => => transferring dockerfile: 819B                                                                                                                                         0.0s
 => [internal] load .dockerignore                                                                                                                                            0.0s
 => => transferring context: 2B                                                                                                                                              0.0s
failed to solve with frontend dockerfile.v0: failed to create LLB definition: failed to parse stage name ":2.0": invalid reference format

However, if I invoke the following command that includes --build-arg IMAGE="mcr.microsoft.com/cbl-mariner/base/core" by hand, it succeeded the build.
It means that the reference of the INSTALLER_IMAGE definition as ${{ env.INSTALLER_IMAGE }}, it may not be able to get during the GitHub Actions. (Now I'm investigating the reason....)

docker build 
-t mcr.microsoft.com/openjdk/jdk:11-mariner 
-f ./docker/mariner/Dockerfile.msopenjdk-11-jdk 
./docker/mariner/ 
--build-arg IMAGE="mcr.microsoft.com/cbl-mariner/base/core" 
--build-arg TAG='2.0'

And also, if I updated the "Java Version" to the latest version in the build-all-images.sh file, I could succeed in building all of the images in the local environment as follows.

> docker images | grep openjdk
REPOSITORY                      TAG              IMAGE ID       CREATED          SIZE
mcr.microsoft.com/openjdk/jdk   17-ubuntu        36257b9063c9   3 minutes ago    420MB
mcr.microsoft.com/openjdk/jdk   11-ubuntu        e3e8059f2b98   4 minutes ago    405MB
mcr.microsoft.com/openjdk/jdk   17-mariner-cm1   08097df5a446   5 minutes ago    587MB
mcr.microsoft.com/openjdk/jdk   11-mariner-cm1   eda8f1738478   6 minutes ago    585MB
mcr.microsoft.com/openjdk/jdk   8-mariner        43cfb9675c4a   8 minutes ago    301MB
mcr.microsoft.com/openjdk/jdk   17-mariner       e21f25855e93   9 minutes ago    380MB
mcr.microsoft.com/openjdk/jdk   11-mariner       66a903eb3d45   9 minutes ago    378MB
mcr.microsoft.com/openjdk/jdk   8-distroless     d7b053ea4be5   10 minutes ago   232MB
mcr.microsoft.com/openjdk/jdk   17-distroless    a6360cfe9fcd   17 minutes ago   342MB
mcr.microsoft.com/openjdk/jdk   11-distroless    d9de552e9e4a   17 minutes ago   327MB

It seems that it looks like a similar issue as follows.

https://stackoverflow.com/questions/69261684/failed-to-parse-stage-name-postgres-invalid-reference-format

Deleted test-yaml file
@karianna
Copy link
Member

@d3r3kk - Can we get one of the infra folks to look at this?

@brunoborges brunoborges changed the title Please support running as a non-root user by default Change images to run as non-root user by default Oct 24, 2022
@brunoborges brunoborges added enhancement New feature or request do-not-merge labels Oct 24, 2022
@brunoborges
Copy link
Member

@yoshioterada do you have some customer feedback to share, where they have concrete security concerns about this image being root by default?

@jdubois
Copy link

jdubois commented Oct 25, 2022

There is plenty of documentation telling not to run containers as root, but probably the best argument is that you cannot run containers as root on OpenShift ( see https://dev.to/ksingh7/allow-containers-to-run-as-root-on-openshift-4-hack-3gp7 for example), so our image wouldn't work on ARO.

In some situation, UID 101 may be used. In order to prevent conflicts. I changed the UID.
After applied this pull request, the application will run as "javauser". However there is no directory which has "write permission".

If one Java Application try to write some file in the deployment directory, it will fail. Because it is owned by root user.

So in order write some file from the application, I added a
 default directory which owned by the "javauser"

So if user deployment their application under the "/app" directory. The application will run without problem.

If the user uses the Mariner core image, then there is "chown" command in the container images.
So user can create any directory and can change the owner.

However "the distress image" doesn't have the "chown" command on the image nor shell.

So we should mentioned the restriction for "distress image" users.

For example, following explanation will be needed.

```
COPY --chown=2000:2000 artifact.jar /app/
```
@yoshioterada
Copy link
Member Author

yoshioterada commented Oct 25, 2022

Dear Bruno-san.

I'm sorry that I don't have any customers now.
Because I knew that the Container Application should run as "non-root" user as a best practice.
And before I informed the customer, I noticed and created the pull request by myself.
So currently, there is no actual customer feedback.
As Julien-san mentioned use case of deploying to OpenShift will be one of issues.

And today, I noticed one more thing.
Thus I submitted two very important modifications.


After applying this pull request, the application will run as "javauser". However, no directory has "write permission".

If one Java Application tries to write some file in the deployment directory, it will fail. Because it is owned by the root user.

So to write some files from the application, I added a
default directory, which is owned by the "javauser"

So if the user deployment their application under the "/app" directory. The application will run without a problem.

If the user uses the Mariner core image, then there is "chown" command in the container images.
So the user can create any directory and can change the owner.

However, the "distress image" doesn't have the "chown" command on the image or shell.

So we should mention the restriction for "distress image" users.

For example, the following explanation will be needed.

If you use the "distress image", please copy the artifact file under the "/app" directory with the following option.

COPY --chown=javauser:java-app artifact.jar /app/

@d3r3kk
Copy link
Contributor

d3r3kk commented Nov 23, 2022

@d3r3kk - Can we get one of the infra folks to look at this?

Created a Bug for the infra team to follow up on.

@brunoborges
Copy link
Member

Thanks @yoshioterada.

I've submitted PR #57 as a solution to this problem. The intent is that images will continue to run as root by default, but with the option that the user/customer may run as a non-root user, with the app account.

Let me know what you think of that.

Copy link
Member

@brunoborges brunoborges left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is likely we will close this one in favour of #57. By adding a non-root user to the images, customers will have the option to run their applications as non-root.

@yoshioterada
Copy link
Member Author

Daer @brunoborges san,

I saw the new pull request as follows.

GitHub : Add non-root user 'app' to all images #57

If you provide a default Application "app" user and the directory owned by the user, it may be helpful for the developers.

There is no info now on learn.microsoft.com. So please write an explanation of how to use the "app" user in the customer's application.

Then please close this pull request.

@brunoborges brunoborges closed this Dec 3, 2022
@yoshioterada yoshioterada deleted the support-non-root-user branch December 7, 2022 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do-not-merge enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants