-
-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Implement privilege de-escalation #528
Comments
At start, Caddy would start the listeners then run a new unprivileged process. At this point two things could happen:
Remember, this only happens if run as root, and the new process only happens once. I'm leaning toward the first option, where the original process stays alive and blocks, in the interest of preserving the current experience. Thoughts/preferences? |
I don't know if this is possible with go/caddy, but a program I used (fossil-scm) puts itself in a chroot when it starts:
The context above is referencing inetd/xinetd, but doesn't always mean that. If I launch Is this something caddy can do? Here's the C code that handles what's described above: |
I doubt chroot would be useful, since: golang/go#1435 (comment) |
@mholt I'm not really into Go but from a operating systems' point of view I think option 1 is the way to go e.g. regarding PID tracking, especially when it comes to init scripts for the various platforms. That would also include having all child processes killed once the main process is stopped. Also, if I'm not misinterpreting that, Apache does it that way, too:
As far as I'm concerned, privilege dropping is the one most important topic that keeps me from using Caddy for larger production usage though I'm using it for smaller projects and really love it. As Caddy is so portable and self-contained i'd be sad to see About the Windows thing... I'd suggest to keep the current behavior on Windows as I think, Caddy on Windows might primarily be used for web development rather than production deployment. EDIT: Just comprehended the priv-dropping PoC you linked. Seems to be exactly what'd be neccessary but I'm not sure if this will work when combining it with |
@dprandzioch Thanks for the feedback. This is good to know. So if Caddy had two processes going, which one would process the signals? Both? Or just one? Which one? How do we make it clear which process to signal? |
In distrustful decomposition with the pattern privilege separation the parent is in charge of handling all OS-level signals (and hence reaping childs, too). Sometimes the parent attaches to the child and monitors its activity with the intent of killing it on trespassing. (We have now Seccomp to do this.) With Caddy, the parent would load and (ideally) hold all certificates, or delegate handling them to another set of child processes. |
@dprandzioch |
Any updates on this? |
Nope. |
Caddy does not need to run as
1–4 already work without any additional work. fig 1: capsh --keep=1 --uid=1000 --gid=1000 \
--caps="cap_net_bind_service,cap_lease+eip" \
-- -c "exec /usr/bin/caddy …" |
In fact, we need privilege de-escalation to protect TLS certificates. Caddy cannot read TLS certificate owned by root, and the rights on certificate must be changed to be readable by another user than root. It's a security issue. |
Why are your certificates only readable by root? You can still use users/groups to wall off access to the certificates from others. Add your web server to some group that's allowed to read the certificates maybe? |
It's a common security practice, if you have any security issue in your web server or in you web app, if the cert is readable by the same user that run the web server, it will be readable by the attacker. |
@cmehay I assume when you say "cert" here you mean private key. Caddy loads these into memory, which, even if the keys are read-protected, are still available to an attacker who gains privileges. Also, keys are not necessarily loaded at server startup. They may be loaded later on. |
Yes, I'm talking about the private key. Reading memory of another process is not the same deal that reading file on file system afaik.
This is the behavior of Apache and nginx, they read cert and private key as root and fork to another process with another uid and gid. |
But they read the key before forking; Caddy might not know it needs the key until after the fork. |
But the keys are specified into Caddyfile right ? For sure this won't apply for generated cert through let's encrypt, but for the certificates in Caddyfile, caddy could read them before forking. |
Not usually anymore, no.
This is not the majority use case. Besides, what security problem are we solving if the Let's Encrypt certs remain unprotected? |
An attacker can read any private key from the process' memory. The mitigation is using a different architecture (which I suggested, and which has been rejected understandably due to the scope of caddy), one where the same process cannot r/w to any device or disk, and the other which cannot contain or work with any secrets. (A consequence of applying here the design pattern → distrustful decomposition.) It's in fact easier for an attacker to read process memory than an arbitrary file! The latter needs interaction with the kernel (syscalls), which could be detected by a monitor {3.42 there}. Furthermore, Linux' DAC is more versatile than you might think: chown root:root secret.key
setfacl -m u:caddy:r secret.key But, as said above, the contents will be loaded into memory anyway and (even in nginx) made available to any childs. (Compare: keyless SSL) If you're concerned with this and don't want to switch to a daemon with a different architecture, then sponsoring work to interface caddy or Golang with HSM modules might be the way to go here. (→ e.g. miekg/pkcs11) |
Thanks for your reply, it's very instructive. |
FYI: See https://superuser.com/a/726922/73857 There are ways to get around it, but they aren't things that should be installed alongside caddy nor changes that should be made without the user doing it themselves (like fudging the ipfw port forwarding) Seems like using the setuid and setgid support that's available and A) not letting insecure modules into caddy and B) filing bugs as they pop up might be a good forward movement? |
Related question: is there any way for a non-root user to send the (I'm specifically interested in a solution for macOS) EDIT: or something similar to |
Picking this approach also lets admins deploy a software HSM, which gives some improvement in information security without needing any new hardware. Eg SoftHSM |
Very interesting server with the Let's Encrypt automation! Thank you for making it. May I ask about the status of this issue? If the goal is to bind sockets as root and then drop privileges, would you accept a trivial C program to do it? It's like 50-100 lines of code. Very similar to the http://play.golang.org/p/dXBizm4xl3 example linked in the first post. The So the trivial launcher program in C would bind a port-80 server on fd 3 and a port-443 server on fd 4 and then drop privileges to another UID/GID and execute Are there problems with this approach? It's Unix only but seems simple to implement and would be a big help there. Also there would be no Go code running as root, so no worries about goroutines and threads. Trivial to audit all the code that runs as root. |
Another thing that would tie into this is the daemontools family of process supervisors. I got the impression that Caddy needs to restart every now and then due to renewal of certificates, and is already using exit codes to say what caused the exit. The daemontools family operates on the principle that a service (such as Caddy) is constantly running. A "run script" starts it and leaves it running in the foreground, using I don't know if you're planning to have boutique "remote control" facilities for Caddy but with 15 years of Unix experience I can't speak highly enough of the simplicity and robustness of the daemontools approach. Even if you don't want to adopt it as Caddy's main approach, it would be nice to have command line flags to support it. I'll gladly discuss details and answer any questions about it. |
@lassik: one thing that then becomes difficult is protecting private key material (so that it's usable for TLS but not readable by the main Caddy worker process) |
I will be implementing such a webserver shortly. Wouldn't be my first iteration, as I've done so in the past for clients for certification. Follow me on Github or Twitter for updates. :-) |
lassik, you have just reinvented
|
Given the dynamic nature of Caddy 2's configuration, and the growing proliferation of kernel features like setcap, this doesn't seem to make as much sense as it did 4-5 years ago. (For example, using the API you could POST a config that requires a new binding of a low port, but if Caddy has already de-escalated, that is impossible.) Currently, the best way to drop privileges in a Go program seems to be to not run it with those privileges in the first place, which is very doable on the most common production environments (semi-modern Linux) using setcap or by just using higher ports. (And on MacOS, low ports no longer require root privileges.) While you can run Caddy as root -- and probably even more safely than any other C-based web server -- you don't have to 99% of the time. This is the oldest open issue and no progress has been made on it, nor is there any known or pressing exploit that necessitates my time right now in lieu of other development priorities, so I'll close it. Feel free to continue discussion, but I doubt anything will be done here unless the following is presented:
|
Go can't really perform de-escalation safely within the same process because of the runtime, which has always led us to recommending using
setcap
.But this comment gave me an idea -- that simply starting a new, unprivileged process and passing it the listeners that were created from the privileged process -- could work as an effective way to drop privileges. Caddy already does this for graceful reloads, except for the part about dropping privileges.
Even though Caddy can kind of already do this, plenty of refactoring will need to happen so that this could work smoothly. (Of course, this won't work on Windows.)
I have not done this before so I need to learn more how this works. For example, it seems like I will need to set the user ID and/or group ID for the new process (for the "Credential" field)... and I am not sure how to get those without asking the user, if that's even possible.
EDIT: A Google employee made this PoC for it: http://play.golang.org/p/dXBizm4xl3 -- user 65534 is "nobody" I think, and I suppose the user and group IDs could be configured via command line flag.
Feedback is welcome.
The text was updated successfully, but these errors were encountered: