docker-library / golang

Docker Official Image packaging for golang
https://golang.org
BSD 3-Clause "New" or "Revised" License
1.45k stars 502 forks source link

Golang without root - ecosystem security #463

Open tspearconquest opened 1 year ago

tspearconquest commented 1 year ago

Hello!

I want to ask about the possibility of getting an additional tag added to dockerhub which offers a golang image built to run by default as a non-root user.

My goal with this is to make the ecosystem more secure. By publishing the golang image without dropping from the root user, we're breaking the principle of least privilege. Since golang is built on top of other images like alpine and debian, it's generally not a great practice to publish the images so that they start up as root by default because it allows for the package manager to be accessed.

tspearconquest commented 1 year ago

I have a working example published internally which required only a couple of small changes to my team's build processes for building go based images; generally it was permissions issues that I encountered when setting this up and I suspect that I could tweak my process to fix the permissions without needing to make any changes to my downstream projects. It'd be great if this would be considered for acceptance. I'm happy to submit a pull request if anyone is interested.

tianon commented 6 months ago

I think some (minimal) tweaks to our documentation to show users how to choose/use a non-root user would be appropriate here. :+1:

yosifkit commented 6 months ago

Is that not covered by the note on the Docker Hub documentation?

Note: /go is world-writable to allow flexibility in the user which runs the container (for example, in a container started with --user 1000:1000, running go get github.com/example/... into the default $GOPATH will succeed). While the 777 directory would be insecure on a regular host setup, there are not typically other processes or users inside the container, so this is equivalent to 700 for Docker usage, but allowing for --user flexibility.

Maybe it just needs an example? Something like this:

FROM golang
ENV HOME=/tmp
USER 1000:1000
RUN go install ...
tspearconquest commented 6 months ago

Hi all, thanks for the feedback. I'd like to start out by saying thanks for the suggestion for how to make my own image secure by default, and I'd like to clarify that what I'm suggesting is that it would be great if I as a developer on other OSS projects can do:

$ docker pull golang:nonroot
$ cat > Dockerfile <<EOF
FROM golang:nonroot
COPY . .
RUN go build ./...
EOF

And know that the go build ./... is not running as root.

I realize I can build and publish a docker build container image specific to each project I ever touch, which drops root where I want it to, and contribute those containers to the projects I work on.

But wouldn't it make more sense to provide a(n (optional)) more secure default for development to start from? When I build a go project on my local machine without Docker, I am not doing so as root. So I've wondered for a while why doesn't the golang docker image do the same?

Regarding building a layer on top of the golang:latest image to drop root, and then publishing that for projects I work on to use, sure I can. I could even simply add a layer that only includes ONBUILD commands to drop root when the image is being used for a build.

However, as an OSS contributor, I'm hoping to contribute a new tag that anyone can simply use; and without having to make any other changes to their code, dockerfiles, permissions, etc, the images will at least build if not run without root.

As I mentioned, I've actually built this out internally already and the internal images at my org are non-root; only the images that hook into more sensitive parts of the system (monitoring, logging, etc) have root during runtime; and aside from our internal golang base image which starts from the dockerhub image, patches OS package CVEs and then drops to a user, none of our container images have root during build time.

But I'm also contributing to OSS projects outside of my work hours, and I feel like I'm spinning my wheels making this type of contribution in individual projects. So I really hope that we can come to an agreement that will make it easier for me to contribute this change elsewhere, and make it easier for the projects to adopt as a result.

polarathene commented 6 months ago

And know that the go build ./... is not running as root.

When I build a go project on my local machine without Docker, I am not doing so as root. So I've wondered for a while why doesn't the golang docker image do the same?

You're building with Docker daemon running as root though regardless? Why not configure your usage of Docker to run rootless, or leverage other configuration options available to you to do similar?

Root in a container is not exactly root on the host. Likewise a non-root user in a container has been capable of escalating privilege to become root (since the daemon itself is running as root).

For image builds, the default is to run commands within a sandbox already.

I have experienced an issue in the past using the image to build a project via container (instead of extending the image via Dockerfile), but that's more to do with a change with git.

tspearconquest commented 5 months ago

You're building with Docker daemon running as root though regardless? Why not configure your usage of Docker to run rootless, or leverage other configuration options available to you to do similar?

Not in CI; we're running buildkitd rootless in CI and using the buildctl CLI tool to build instead of docker. However, I want to mention again that this is not about my needs; and more about providing a better default configuration for those who don't want to have to think about it.

For image builds, the default is to run commands within a sandbox already.

Is there any more documentation about this sandbox? It doesn't really go into details about how that sandbox works.

What concerns do you have that require USER to be non-root?

When I run go build ./... in CI using golang:alpine, go is running as root. I don't care that it's in a container; root is root. It might have the ability to escape the container or it might not; depending on too many other configuration factors. I want to simply use the image and know that the build itself isn't running as root unless I've explicitly defined USER root or USER 0 in my dockerfiles. It follows the same conventional logic that one would follow when running make while building a project in another language. You build as non-root and then install as root: make build && sudo make install for example, and you don't login directly as root to your desktop to build, so we shouldn't do it here either.

As I contribute to a number of OSS projects (in addition to private projects), it would benefit them all if I could help them to adopt this configuration by simply changing the image tag in their Dockerfiles, rather than having to make a custom golang docker image for every project.

For my private projects, I've essentially built this out already and I'm happy to contribute it with any modifications the team would request. I modified the golang:alpine dockerfile to drop root almost immediately and leverage a multi-stage build to build golang itself, then install it with the correct permissions to a fresh alpine image and set the default non-root user for anything that makes use of the image later on.

polarathene commented 5 months ago

However, I want to mention again that this is not about my needs; and more about providing a better default configuration for those who don't want to have to think about it.

Python and Node images have many tags, but none offer :non-root or similar. Do you have examples of official images for other languages that do offer a non-root variant?

I generally don't have to think about it. It's assumed root by default, if I don't want that I'm either:

If anything, I've often had more friction with images that switch to non-root. I'd be interested in knowing a good reason to prefer it beyond dropping capabilities implicitly for the user when they run a container, instead of having them do so.


Is there any more documentation about this sandbox? It doesn't really go into details about how that sandbox works.

Do you have an example where building an image that hasn't changed from root with USER is causing a security issue during build?


When I run go build ./... in CI using golang:alpine, go is running as root. I don't care that it's in a container; root is root

root is not equivalent to root on the host. It's limited. What you're saying is "I'm security paranoid for the sake of it, non-root makes me feel safe even though it can be exploited all the same".

I often see the advice parroted without properly understanding the security benefit. Then you'll see things like using setcap with =ep for capabilities that are not safe to grant for an optional feature of the containerized service... When you know you don't need that capability, if you ensure it's removed from the bounding set, the program fails to start because of the enforced kernel check. So the user has to allow the reduced security to run the container, as opposed to a locked down root which wouldn't have had that problem.

That's not to say that you can't have the ideal outcome of keeping that service non-root, the capability can be permitted and not set as effective. Instead requiring the program at run-time to explicitly check if the capability is available when it's actually needed to be used, and raise from permitted set to the effective set or log an error about the capability not being available for the feature requiring it.

In practice, I've seen the bad way used often. I've also seen software that doesn't know how to handle soft limits properly, such as for file descriptors per process. This leads to other poor practices that I've been able to address with Docker and containerd, and reaching out to software that expects a high number of file descriptors be supported but refuses to communicate that need at run-time (some don't even document it). That encourages bad habits again from a lack of understanding, especially from end-users and causes various bugs that are fun to troubleshoot.

root user as a security risk depends on context. Using non-root user is a good practice but doesn't mean you're necessarily avoiding the problems you intended to. Your stance seems to blindly trust the practice without the understanding I've tried to demonstrate above.


I want to simply use the image and know that the build itself isn't running as root unless I've explicitly defined USER root or USER 0 in my dockerfiles

It's always building with the root user initially 🤷‍♂️

Use the other mentioned configuration for making root in the container namespace even less capable, or perhaps request upstream to Docker to support invoking a build with an option like --user? (and equivalent config, so you can implicitly have that experience unless you explicitly request docker build --user root)

Seems like that would meet your needs/expectations more than getting a non-root variant maintained by each image publisher, and expecting them to deal with the burden of supporting that (see other images that offer non-root and the support issues those generate).


you don't login directly as root to your desktop to build, so we shouldn't do it here either

Again... root on the host is completely different from root in the container. They are not equivalent. A non-root user can escape the container and escalate to root too.


it would benefit them all if I could help them to adopt this configuration by simply changing the image tag in their Dockerfiles, rather than having to make a custom golang docker image for every project.

Sorry, I don't follow the logic here?


I'm not a maintainer here, and I'm not against non-root either. However, I don't personally see value in supporting a :non-root tag for a base image.

I understand wanting the convenience, and comfort that brings you. So long as root remains the default, and those who use a :non-root variant actually understand security when it matters, instead of making it worse (see the capability example above).