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

Enable running as non root #274

Merged

Conversation

abjugard
Copy link
Contributor

@abjugard abjugard commented Jan 17, 2023

Addresses part of #104

Fixes #273

@francislavoie francislavoie added the enhancement New feature or request label Jan 17, 2023
@francislavoie
Copy link
Member

I don't understand what CI is doing 🤔 it's erroring out on the nsswitch.conf thing, but we removed that.

This will need @hairyhenderson's eyes on it I think.

I opened my own PR #275 and I guess this one precludes that one since it makes the same fix.

@abjugard
Copy link
Contributor Author

abjugard commented Jan 17, 2023

I don't understand what CI is doing 🤔 it's erroring out on the nsswitch.conf thing, but we removed that.

Yeah, very strange, that stage seems to be building using the latest commit on master, with the test before it building and succeeding using my branch?

What's the purpose of this step that builds the old code?

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from e8b8943 to 2449369 Compare January 17, 2023 12:06
@abjugard
Copy link
Contributor Author

(No change, just added the #273 issue tag to the relevant commit)

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from 2449369 to f0794c8 Compare January 17, 2023 12:30
@abjugard
Copy link
Contributor Author

abjugard commented Jan 17, 2023

@francislavoie another thing to mention as well (didn't look like it was part of this repo otherwise I would have updated it myself), the documentation (https://hub.docker.com/_/caddy) for building your own caddy using the caddy-builder image, and copying the resulting binary onto this image, should also include the libcap stuff in my opinion.

I've pushed another commit adding libcap to the builder image so that the documentation can be updated to suggest the following Dockerfile:

FROM caddy:<version>-builder AS builder

RUN xcaddy build \
    --with github.com/caddyserver/nginx-adapter \
    --with github.com/hairyhenderson/caddy-teapot-module@v0.0.3-0

RUN setcap cap_net_bind_service=+eip /usr/bin/caddy

FROM caddy:<version>

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

@francislavoie
Copy link
Member

Oh, really? It needs to be set on the binary before copying it over? Wow, that's really annoying. I thought setcap would write a rule to the existing container (i.e. the final stage) that would persist when the binary changes at the same path. I guess that's not how it works? I've never dug into how setcap really works.

I'd really hope not to have to add that to the recommended instructions. Is there anything we could do to make it smoother without needing to add that line?

An idea, we could add an option to xcaddy to try to run setcap on the binary after it's done building it. Would that work at all? Then we could use an env var to instruct xcaddy to do so, so the user doesn't see any difference in their own Dockerfile.

@abjugard
Copy link
Contributor Author

abjugard commented Jan 17, 2023

Having xcaddy run setcap on the binary is enough yeah, and sounds like an elegant solution in my opinion! However... how do we instruct users that it's possible to do that, since that would still require changes to the Dockerfile?

And no, the capability is set on the binary itself so it doesn't survive if you copy another binary over it.

Another possible solution is to put a simple "loader" binary in the caddy image, then in the image we would RUN setcap cap_net_bind_service=+eip /usr/bin/caddy_loader, this loader would be set as the entrypoint, and it would load /usr/bin/caddy, transferring the capability to the loaded caddy binary.

It's less elegant but it requires zero changes to both the builder Dockerfile and xcaddy.

@francislavoie
Copy link
Member

francislavoie commented Jan 17, 2023

how do we instruct users that it's possible to do that, since that would still require changes to the Dockerfile?

We'd add it to here, maybe XCADDY_SETCAP_NET=1 or something:

ENV XCADDY_SKIP_CLEANUP 1

Then users don't need to do anything, cause the builder image would just do it.

Edit: See caddyserver/xcaddy#128

@jjlin
Copy link

jjlin commented Jan 17, 2023

I tried doing something similar for another project a couple of years ago, and there are some big caveats. I haven't checked whether they still apply, but I wouldn't be surprised at all if they do.

  1. Capabilities are associated with an executable via extended file attributes. The COPY instruction doesn't copy extended attributes, so you can't do a multi-stage build where you set the capability in the build stage and then copy that out to a runtime stage.

  2. If you set a capability via a separate RUN instruction, Docker seems to make another copy of the executable in a new layer (with an image where the bulk of the content is the executable, the image basically doubled in size). So it's probably best to build the executable and set the capability via a single RUN.

  3. I've read that Docker support for extended attributes may not be the same across all Linux platforms, though I don't have personal experience beyond the common ones. Since the Caddy image is built for some of the less common platforms, that might be something to verify.

This blog post is a couple of years old, but seems to cover most of what I observed, and more.

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from 69d9e73 to f5fb008 Compare January 18, 2023 22:43
@francislavoie
Copy link
Member

Thanks @jjlin, that's a useful article.

  1. Capabilities are associated with an executable via extended file attributes. The COPY instruction doesn't copy extended attributes, so you can't do a multi-stage build where you set the capability in the build stage and then copy that out to a runtime stage.

That seems to have been true for the original Docker builder, but with Buildx/BuildKit, it is preserved. The article you linked mentions that. BuildKit is on by default now in latest versions of Docker, so it should work fine if users are keeping Docker up to date.

  1. If you set a capability via a separate RUN instruction, Docker seems to make another copy of the executable in a new layer (with an image where the bulk of the content is the executable, the image basically doubled in size). So it's probably best to build the executable and set the capability via a single RUN.

Yep, that's a good point, and that's why I'd like for xcaddy to do it for us.

This PR adds setcap to the main RUN already for the main image, so that's already good 👍

  1. I've read that Docker support for extended attributes may not be the same across all Linux platforms, though I don't have personal experience beyond the common ones. Since the Caddy image is built for some of the less common platforms, that might be something to verify.

🤔 yeah, I don't have experience with this. We'll raise it with the Docker Library team when we push it there, they can tell us what's up.

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from f5fb008 to 205cf64 Compare January 18, 2023 22:57
@abjugard
Copy link
Contributor Author

abjugard commented Jan 18, 2023

🤔 yeah, I don't have experience with this. We'll raise it with the Docker Library team when we push it there, they can tell us what's up.

For what it's worth it works as we expect it to on my machine (Intel Mac running colima in vz), documented my experience in caddyserver/xcaddy#128

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch 2 times, most recently from 48f2a15 to a781133 Compare January 18, 2023 23:03
@jjlin
Copy link

jjlin commented Jan 19, 2023

I tried implementing this again in my other project, and with DOCKER_BUILDKIT=1 (BuildKit doesn't seem to be enabled by default), the COPY instruction does carry over the capabilities, so that's nice.

While testing this, I noticed that executables running as non-root can (unexpectedly) bind to "privileged" ports even without setting cap_net_bind_service. Apparently this behavior was introduced in Docker 20.10.0 via moby/moby#41030. To disable this feature for testing, you need to do something like docker run --sysctl net.ipv4.ip_unprivileged_port_start=1024 ....

Nevertheless, setting cap_net_bind_service should still be useful for people running earlier versions of Docker, or using another container runtime that doesn't have this feature.

@francislavoie
Copy link
Member

Oooh that's awesome, thanks for trying that and finding that new feature in Moby!

I think buildkit might only be enabled by default in new installs, not in upgraded ones. That's what I read in the docs I think.

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from a781133 to 4eefc92 Compare January 20, 2023 11:21
@hairyhenderson
Copy link
Contributor

@abjugard can you revert the changes to library/caddy for now? I usually only update that separately after PRs have merged, to ensure the commits are correct for the main branch.

2.6/alpine/Dockerfile Outdated Show resolved Hide resolved
@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from 4eefc92 to 9c04c2b Compare January 21, 2023 11:04
@abjugard
Copy link
Contributor Author

abjugard commented Jan 21, 2023

@abjugard can you revert the changes to library/caddy for now? I usually only update that separately after PRs have merged, to ensure the commits are correct for the main branch.

@hairyhenderson Done!

@francislavoie
Copy link
Member

Opened a PR in xcaddy: caddyserver/xcaddy#129

@hairyhenderson
Copy link
Contributor

Do we need caddyserver/xcaddy#129 to merge first? otherwise this LGTM (except that this needs to be rebased, and the Dockerfiles regenerated now that the errant .editorconfig is gone)

@francislavoie
Copy link
Member

Yeah, I think we should wait for xcaddy. This is only a half solution until we can make the builder also work correctly. Shouldn't take long, Matt was on vacation but will get back into thing soon.

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from 9c04c2b to 65f4102 Compare January 27, 2023 17:36
@francislavoie
Copy link
Member

Alright @abjugard, https://github.com/caddyserver/xcaddy/releases/tag/v0.3.2 is released! Got time to finish this up? 🎉

Caddy v2.6.3 is gonna release sometime this week, would be great to get this in for that version.

@abjugard
Copy link
Contributor Author

abjugard commented Feb 7, 2023

Absolutely @francislavoie. Want me to bump xcaddy in this PR then?

@abjugard
Copy link
Contributor Author

abjugard commented Feb 7, 2023

How's that @francislavoie?

@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from 1655c41 to 30c363c Compare February 7, 2023 08:10
@abjugard abjugard force-pushed the feature/enable-running-as-non-root branch from 30c363c to a5a1dac Compare February 7, 2023 08:12
@abjugard
Copy link
Contributor Author

abjugard commented Feb 7, 2023

Sorry, copy-pasting hashes is hard 😅. Now it's all correct!

Copy link
Member

@francislavoie francislavoie left a comment

Choose a reason for hiding this comment

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

LGTM! I'll let Dave do the final review and merge

Copy link
Contributor

@hairyhenderson hairyhenderson left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks for doing this @abjugard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Building custom image from caddy:2.6.2 failed
4 participants