bpatrik / pigallery2

A fast directory-first photo gallery website, with rich UI, optimized for running on low resource servers (especially on raspberry pi)
http://bpatrik.github.io/pigallery2/
MIT License
1.76k stars 202 forks source link

Don't run node as root user in docker image #728

Open Maxwellfire opened 1 year ago

Maxwellfire commented 1 year ago

I believe currently the dockerfile is written so that node is run with the root user. This goes against best security practices. I don't believe there is anything that pigallery does that needs a root user in the container. I believe the node image you are using provides user node which is recommended here

Maxwellfire commented 1 year ago

Reading more about this, it seems like this will need to be configured per system, since docker container uids must match a suitable service user on the host. In light of this, maybe the documentation could be updated to advise how to run as a non-root host.

On my system I created a user and group pigallery2:pigallery2 for the service, gave that user access to the required bind mount locations, and then also had to chown the files under the docker pigallery2_db-data volume to the correct user.

I then added the uid:guid pair for pigallery2:pigallery2 to the docker compose file as:

version: '3'
services:
  pigallery2:
    image: bpatrik/pigallery2:latest
    container_name: pigallery2
    environment:
      - NODE_ENV=production # set to 'debug' for full debug logging
    user: <uid>:<guid>
    volumes:
      - "<config>:/app/data/config"
      - "db-data:/app/data/db"
      - "<images>:/app/data/images:ro"
      - "<temp>:/app/data/tmp"
    ports:
      - 44771:80
    restart: always

volumes:
  db-data:
bpatrik commented 1 year ago

It's more than that to it. I think you need root bind.to.port 80 and 443. If you use nginx, then I guess it only applies to the reverse proxy.

Anyway as you mentioned it depends on the host. The docker-compose is only a simple way to run the app. I think the simplest is to do some write up here, that you did above and add a comment to the docker compose files.

Clould you send a PR for that?

Maxwellfire commented 1 year ago

I don't believe you need root for that, since all ports are allowed to be bound inside the docker container without root, and the docker daemon running as root handles the binding outside the container.

Yes I can submit a pull request. I believe that in order to make this easy, the docker image should be changed so that the files in db-data are owned by node (and node changed to have a uid less likely to collide). It does seem like this is one major deficiency of docker. See here.

bpatrik commented 1 year ago

I mean that the one container that exposes the final port to the host. That should bind to 80 and or 443. And binding to those port on the host needs special rights if I understand correctly.

I don't think that the user within the container matters much. To my understanding if the container runs as a non-root user, within the container the user can be root, it won't be able to do more on the host machine than the non-root user, that is used for running the container. But correct me if im wrong, I'm not a docker expert. I only provide the app with some minimal docker setup.

Rickkwa commented 12 months ago

I recall a few months ago, I had to make an nginx container run as non-root because our kubernetes cluster had a security check in place to not allow containers running as root.

In that nginx container, I had to change the port it was listening on to achieve that. Revisiting that now, I see that ports < 1024 have to run as root. So inside an nginx container, if you have listen 80 in the nginx.conf, it's going to complain if you try to set up a non-privileged user. For the purpose of these security checks, the container itself doesn't need port 80/443, it can listen on 8080. That way, we can have the container run as non-root user and be security compliant. Then the final port mapping from host->container will need to be ran by a privileged user.

In my personal setup of pigallery, I have a wrapping Dockerfile to build the container using a non-root user. You can see my setup in this repo, with the pigallery Dockerfile here, and nginx (technically openresty) Dockerfile here.

Maxwellfire commented 12 months ago

I don't think this is true. Inside docker containers there are no privileged ports (assuming you're using docker networking and not host networking). See https://github.com/moby/moby/pull/41030/commits. Essentially net.ipv4.ip_unprivileged_port_start is set to 0 so all ports are considered unprivileged.

The container itself needs to bind port 80 on the host machine, but that is done by the docker daemon, not the software inside the container. If you need the docker daemon to run rootless, that's a whole other thing.

Rickkwa commented 12 months ago

You're right, maybe I'm misremembering something from a few months ago, because I can run a non-root nginx listening on port 80.

Editing since I don't want to derail this thread too much: You're right that my kubernetes cluster is running on containerd instead of docker; that explains it. Thanks.

Maxwellfire commented 12 months ago

If you run Kubernetes with the containerd runtime instead of the docker runtime, it looks like you'd still need the privileges as that runtime doesn't change net.ipv4.ip_unprivileged_port_start by default. This should still be pretty easy to manage though, since like you said you just change the port that nginx is binding inside and then map that port to 80 through the docker daemon's networking.

dsm1212 commented 11 months ago

I had no issue on debian setting the user uid/gid in docker compose. You need to choose an external port in the user range, but the internal port of 80 is fine. If you already ran as root you have chown all the files, include the ones on the db data volume.

evrial commented 6 months ago

There is no sane explanation here why inside 80 port is by default and not 8000. I mean the gallery should be rootless, that's the problem of reverse proxy to host on 80 port and have minimal attack surface.

bpatrik commented 3 months ago

I dont mind chaning the def port to 8080 or similar. but a very easy workaround for now is:

  pigallery2:
    image: bpatrik/pigallery2:latest
    container_name: pigallery2
    environment:
      - NODE_ENV=production # set to 'debug' for full debug logging
      - PORT=8080 # <------------
      # - NODE_OPTIONS=--enable-source-maps # enable source map support on the backend for development
    volumes:
      - "./pigallery2/config:/app/data/config" # CHANGE ME
      - "db-data:/app/data/db"
      - "./pigallery2/images:/app/data/images:ro" # CHANGE ME, ':ro' means read-only
      - "./pigallery2/tmp:/app/data/tmp" # CHANGE ME
    expose:
      - "8080:80" # <------------
    restart: always

I did not double check the code about but should be similar. My point is that the used port can be set through an env variable.