Skip to content
This repository has been archived by the owner on Aug 26, 2021. It is now read-only.

You should not run as root #38

Open
EugenMayer opened this issue Sep 30, 2018 · 13 comments
Open

You should not run as root #38

EugenMayer opened this issue Sep 30, 2018 · 13 comments

Comments

@EugenMayer
Copy link

you should not run traefik as root in the docker image.

@emilevauge
Copy link
Member

emilevauge commented Jan 30, 2019

@dduportal
Copy link
Contributor

Some take aways on this topic:

  • The "machinery" required to create the user/group and set the data volume is easy technically, but might have an impact (USER directive, eventually /etc/passwd etc.)

  • Solving the authorization problem when you mount a data volume which has not been initialized with the same user/group as traefik's image must be documented.

  • The example from Kubic requires a Kubernetes 1.13.x cluster, to benefit from the "PSP" (Pod Security Policy)

  • The main issue with a non root user is the ability to open ports < 1024. Even though Linux provides the CPA_BIND_NET capability, it does not work on Kubernetes, because of the ambient capabilities not supported: Kubernetes should configure the ambient capability set kubernetes/kubernetes#56374 . From this there are 2 solutions:

    • Support of non-root only on the Alpine image, by installing the libcap package and using the setcap net_bind_service=+ep /traefik command to fix the capabilities mask issue. This won't work on the scratch image AFAIK.
    • Decide to switch the default exposed port from 80 to 8080 as proposed in -BREAKING- Update base image to not run as root #43 . This is a non easy change, because it would require to adapt Traefik's default configuration (which creates an entrypoint on 80/TCP). Maybe it could be done with a default CMD ["--entrypoints=''Name:http Address::8080"] instruction to NOT modify Traefik itself.

Thoughts?

@EugenMayer
Copy link
Author

EugenMayer commented Feb 8, 2019

  • The "machinery" required to create the user/group and set the data volume is easy technically, but might have an impact (USER directive, eventually /etc/passwd etc.)

That is only an issue for host-mount based volumes and is a known aspect everybody runs docker in production is aware of, when using host mounts. I would not focus on that "volume" aspect too much here - it's the same for all projects using gosu or whatever.

Not sure how traefik needs /etc/passwd at all / ever, but rather work with right group access if needed at all. AFAIK Traefik does not need anything that is system-specific / distro specific, everything can be created just for Traefik with the right permissions for it. Did i miss something here?

  • Solving the authorization problem when you mount a data volume which has not been initialized with the same user/group as traefik's image must be documented.
    Same as above, this is a very known issue, some containers work around this issue by using UID/GID mapping, check all the images from LinuxServer. I would though, not overcomplicate things in the image here, stick to the A-plan. If a user decides to use host mounts, they need to be properly writeable by the correct user. Maybe just error out a proper way if acme/certs are not writeable

A good way could be here, start with root, create the folders for traefik on the volume and then degrade to the Traefik user. AFAIR that is still secure, but i am not entirely remembering it right now, we would need to double check if dropping privileges this way is to be considered secure for the initial attack vector

  • The example from Kubic requires a Kubernetes 1.13.x cluster, to benefit from the "PSP" (Pod Security Policy)
    Great that it actually exists.
  • The main issue with a non root user is the ability to open ports < 1024. Even though Linux provides the CPA_BIND_NET capability, it does not work on Kubernetes, because of the ambient capabilities not supported: kubernetes/kubernetes#56374 . From this there are 2 solutions:

Well i would say that's a very know thing also, using 8080/8443 for those LB kind of images. Surely when the default is changed, this needs a fat-warning in the release notes of the docker-image or even a Traefik release notice, which introduces the new image. In the end, the person just remaps the ports to 80/443 when mapping on the host, i rarely matters what the dest port.

  • Support of non-root only on the Alpine image, by installing the libcap package and using the setcap net_bind_service=+ep /traefik command to fix the capabilities mask issue. This won't work on the scratch image AFAIK.
    Never worked with it, will get an idea
  • Decide to switch the default exposed port from 80 to 8080 as proposed in -BREAKING- Update base image to not run as root #43 . This is a non easy change, because it would require to adapt Traefik's default configuration (which creates an entrypoint on 80/TCP). Maybe it could be done with a default CMD ["--entrypoints=''Name:http Address::8080"] instruction to NOT modify Traefik itself.

Totally agree that we should just do that for the docker-image, not for the binary using parameters.
Remapping the ports you just mentioned using CMD is what i do fairly regular either by using ENV with my env based image https://github.com/EugenMayer/docker-image-traefik/blob/master/tiller/common.yaml#L34 or as you mentioned https://github.com/EugenMayer/docker-image-rundeck/blob/master/docker-compose.yml#L71
and other projects. It works very well. I guess we would of course put this into the CMD of the docker image as a default

Thoughts?

@dduportal
Copy link
Contributor

  • That is only an issue for host-mount based volumes and is a known aspect everybody runs docker in production is aware of, when using host mounts. I would not focus on that "volume" aspect too much here - it's the same for all projects using gosu or whatever.

@EugenMayer Points taken. The goal of my comment was to underline some technical facts to have them written and shared, because not everyone is having them as principal concerns.

Let's hear what the others members of the community would want to say about this topic.

@westurner
Copy link

westurner commented Mar 14, 2019

From awesome-traefik, I found this article: "How to run Træfik as a non-root user ? Part 1" https://medium.com/@zepouet/how-to-run-tr%C3%A6fik-as-non-privileged-user-4a824bc5cc0

Traefik could also instead drop privileges at startup? (Does it already? Nginx and Apache drop privileges after binding 80 and 443. edit: traefik/traefik#1434 ... https://github.com/tmiller/setuidgid/blob/master/main.go)
From https://linux-audit.com/how-and-why-linux-daemons-drop-privileges/ :

if (getuid() == 0) {
 /* process is running as root, drop privileges */
 if (setgid(groupid) != 0)
 fatal("setgid: Unable to drop group privileges: %s", strerror(errno));
 if (setuid(userid) != 0)
 fatal("setuid: Unable to drop user privileges: %S", strerror(errno));
}

Though, "syscall: Setuid/Setgid doesn't apply to all threads on Linux" golang/go#1435 (comment)

... FWIW, "Docker integration: Exposing Docker socket to Traefik container is a serious security risk" traefik/traefik#4174
(Here's a different approach that does not mount the docker socket into a container for a process running as root: https://github.com/liquidat/ansible-role-traefik/blob/master/tasks/main.yml)

@westurner
Copy link

Compared to just running as non-root and updating the docs - and maybe the seclist -- to say 'You need to map the port now', setcap is less than optimal, because it doesn't limit access to other ports < 1024 (which shouldn't be mapped anyway, and nobody should be using source ports for any firewall rules):

setcap 'cap_net_bind_service=+ep' $(which traefik)

But in terms of least privileges, setcap should be unnecessary if the app is running as non-root. Filesystem privileges can be set with an entrypoint script or in a traefik init routine (prior to dropping privileges).

@dealboy
Copy link

dealboy commented Mar 24, 2019

Having similar requirements for security reasons, we also investigate the non-root options.

@westurner : Can you please clarify a bit more what breaks in your approach at #43 ?
(the redirection break you mention is not clear, is it because of ACME usage, or because of an
[entryPoints.http.redirect] in your toml)

did you try the user: "${UID}:${GID}" option in compose (instead of creating your own image)

@westurner
Copy link

I haven't yet tried specifying the UID/GID. That might be less to maintain; but it won't solve for the port remapping issue. (Nonroot cannot bind ports less than <1024 without CAP_NET_RAW, which is really unnecessary particularly for a frontend service). This solved; though I'm not sure if I've forgone any features (?) by creating the redirect with the port number specified instead of just referencing the other entrypoint by name:

[entryPoints]
  [entryPoints.http]
  address = ":8080"
    [entryPoints.http.redirect]
    #entryPoint = "https"
    regex = "^http://(.*):8080/(.*)"
    replacement = "https://$1/$2"

  [entryPoints.https]
  address = ":8443"
    [entryPoints.https.tls]
      #[[entryPoints.https.tls.certificates]]
      #certFile = "/certs/website.crt"
      #keyFile  = "/certs/website.key"

@neingeist
Copy link

FWIW, Traefik runs perfectly fine as a user with these options in a systemd service file:

User=traefik
AmbientCapabilities=CAP_NET_BIND_SERVICE

CAP_NET_BIND_SERVICE allows it to bind to 80 and 443.

I basically run it with almost all options enabled in here: https://github.com/containous/traefik/blob/master/contrib/systemd/traefik.service, except that it does not run with LimitNPROC=1. Not tested with the Docker backend.

@alexanderadam
Copy link

alexanderadam commented Jul 30, 2019

The main issue with a non root user is the ability to open ports < 1024

In case traefik runs within a container (which is the whole point of the issue), this shouldn't be necessary anyway, right?
Because you can map the port to the "outside world" anyway (i.e. 3000 to 80 & 3001 to 443) and the parent process (i.e. Docker or Podman) will handle this.

@EugenMayer
Copy link
Author

That is known, but you have to use a port greater then 1024 in the container / image, which would change the current default 80/443 and that is the whole point of the discussion

@JoyceBabu
Copy link

Now would be a good time to revisit this, since v2 that was just released is has backward incompatible changes requiring manual upgrade.

@gnat
Copy link

gnat commented Oct 31, 2019

Just FYI got this working with Caddy with Let's Encrypt. You can de-escalate your privileges from root to a traefik user using setpriv (or gosu). setpriv is a standard utility in most distros now making it super easy in ubuntu or even alpine.

Set an entrypoint.sh script, do any needed chown with your host bind mounts, and then go ahead and run traefik with setpriv --reuid=traefik --regid=nogroup --init-groups /usr/bin/traefik.

You can see an example of this in action in the mysql and jenkins containers currently but I'll be submitting a patch to Caddy soon, if you'd like a reference.

For this to work you also need to remember to set sysctls: net.ipv4.ip_unprivileged_port_start=0 in your docker-compose.yml or set the sysctl when using docker run so you can access port 80 and port 443 as a non-root user.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants