Exposing docker socket to a container
Exposing docker socket to a container
Do you guys expose the docker socket to any of your containers or is that a strict no-no? What are your thoughts behind it if you don't? How do you justify this decision from a security standpoint if you do?
I am still fairly new to docker but I like the idea of something like Watchtower. Even though I am not a fan of auto-updates and I probably wouldn't use that feature I still find it interesting to get a notification if some container needs an update. However, it needs to have access to the docker socket to do its work and I read a lot about that and that this is a bad idea which can result in root access on your host filesystem from within a container.
There are probably other containers as well especially in this whole monitoring and maintenance category, that need that privilege, so I wanted to ask how other people handle this situation.
Cheers!
Per this guide https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html I do not. I have a cron/service script that updates containers automatically (‘docker compose pull’ I think) that I don’t care if they fail for a bit (pdf converter, RSS reader, etc.) or they’re exposed to the internet directly (Authentik, caddy).
Note that smart peeps say that the docker socket is not safe as read-only. Watchtower is inherently untenable sadly, so is Traefik (trusting a docker-socket-proxy container with giga root permissions only made sense to me if you could audit the whole thing and keep auditing with updates and I cannot). https://stackoverflow.com/a/52333163 https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html
I then just have scripts to do the ‘docker compose pull’ for things with oodles of breaking changes (Immich) or things I’d care if they did break suddenly (paperless).
Overall, I’ve only had a few break over a few years - and that’s because I also run all services (per link above) as a user, read-only, and with no capabilities (that aren’t required, afaik none need any). And while some containers are well coded, many are not, and if an update makes changes that want to write to ‘/npm/staging’ suddenly, the read-only torches that until I can figure it out and put in a tmpfs fix. The few failures are worth the peace of mind that it’s locked the fuck down.
I hope to move to podman sometime to eliminate the last security risk - the docker daemon running the containers, which runs as root. Rootless docker seems to be a significant hassle to do at any scale, so I haven’t bothered with that.
Edit: this effort is to prevent the attack vector of “someone hacks or buys access to a well-used project (e.g., Watchtower last updated 2 years ago, commonly used docker socket proxy, etc.) which is known to have docker socket access and then pushes a malicious update that to encrypt and ransom your server with root access escalations from the docker socket”. As long as no container has root, (and the container doesn’t breach the docker daemon…) the fallout from a good container turned bad is limited to the newly bad container.
All true, wanted to add on to this:
That's true, and it's not just something mildly imperfect, read-only straight up does nothing. For connecting to a socket, Linux ignores read-only mount state and only checks write permission on the socket itself. Read-only would only make it impossible to make a new socket there. Once you do have a connection, that connection can write anything it wants to it. Traefik and other "read-only" uses still have to send GET queries for the data they need, so that's happening for legitimate use cases too.
If you really need a "GET-only" Docker socket, it has to be done with some other kind of mechanism, and frankly the options aren't very good. Docker has authorization plugins that seem like too much of a headache to set up, and proxies don't seem very good to me either.
Or TLDR:
:ro
or stripping off permission bits doesn't do anything aside from potentially break all uses for the socket. If it can connect at all, it's root-equivalent or has all privileges of your rootless user, unless you took other steps. That might or might not be a massive problem for your setup, but it is something you should know when doing it.Thanks for explaining the underworkings, never dug to see what happened and how it works - I see it bad
Thank you for your comment and the resources you provided. I definitely look into these. I like your approach of minimizing the attack surface. As I said, I am still new to all of this and I came across the user option of docker compose just recently when I installed Jellyfin. However, I thought the actual container image has to be configured in a way so that this is even possible. Otherwise you can run into permission errors and such. Do you just specify a non-root user and see if it still works?
And while we're at it, how would you setup something like Jellyfin with regards to read-write permissions? I currently haven't restricted it to read-only and in my current setup I most certainly need write permissions as well because I store the artwork in the respective directories inside my media folder. Would you just save these files to the non-persisted storage inside the container because you can re-download them anyway and keep the media volume as read-only?
So I've found that if you use the
user:
option with auser: UserName
it requires the container to have that UserName alsoo inside. If you do it with a UID/GID, it maps the container's default user (likely root 0) to the UID/GID you provideuser: 1500:1500
. For many containers it just works, for linuxserver (a group that produces containers for stuff) containers I think it biffs it - those are way jacked up. I put the containers that won't play ball in a LXC container (via Incus GUI), or for simple permission fixes I just make a permissions-fixing version of the container (runs as root, but only executes commands I provide) to fill a volume with the data that has the right permissions then load that volume into the container. Luckily jellyfin doesn't need that.I give jellyfin read-only access (via
:ro
in thevolumes:
) to my media stuff because it doesn't need to write to it. I think it's fine if your use-case needs:rw
, keep a backup (even if you:ro
!).Here's my docker-compose.yml, I gave jellyfin its own IP with macvlan. It's pretty janky and I'm still working it, but you can have jellyfin use your server's IP by deleting everything after
jellyfin-nw:
(but keepjellyfin-nw:
!) in both thenetworks:
section andservices:
section. Delete themac:
in theservices:
section too. In theports:
part that10.0.1.69
would be the IP of your server (or in this case, what I declare the jellyfin container's IP to be) - it makes it so the container can only bind to the IP you provide, otherwise it can bind to anything the server has access to (as far as I understand).And of course, I have GPU acceleration working here with some embeded Intel iGPU. Hope this helps!
Lastly thought I should add the external stuff needed for the hardware acceleration to work/get the user going:
The Jellyfin user isn't added to the render group, rather the group is added to the container in the docker-compose.yml file.