caddyserver / caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
https://caddyserver.com
Apache License 2.0
57.01k stars 3.99k forks source link

Options to disable admin listener #2833

Closed aksdb closed 4 years ago

aksdb commented 4 years ago

1. What would you like to have changed?

Under some circumstances, the admin listener might cause a problem or is simply not necessary. If the dynamic configuration at runtime via HTTP is not needed, it would be good to be able to disable the admin port altogether.

I would even propose that if caddy runs on Caddyfile, the admin port is disabled by default (to be closer to caddy v1) and introduce a Caddyfile directive to easily enable and configure the admin port if needed.

2. Why is this feature a useful, necessary, and/or important addition to this project?

I think there are two use cases for caddy 2: professional hosting, reverse proxying, etc. where you greatly benefit from dynamic configuration you can read/write during runtime using a HTTP listener.

Then there is also the "simple user" that only needs a quick or easy webserver. This is also where the Caddyfile shines and what (for me) always is/was the great benefit of caddy in contrast to its competitors. In this case, an additional port is only another potential attack vector (since machines can be shared) and is another port that could conflict with other running services. Also in these cases there is simply no gain from the admin feature, since all configuration is done through the Caddyfile anyway.

3. What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?

So far I stay with caddy 1 to keep it simple for myself.

Remarks

This ticket is mostly intended for discussion. If you / someone jumps on that train right away and implements it I would not be sad, of course. But if the proposal is considered valid, I would also be willing to implement it and create a PR.

francislavoie commented 4 years ago

Seeing as how by default it only accepts requests from 127.0.0.1, I would think that would be as good as disabled for most apps/environments. That said, it is a reasonable request regardless, to limit the potential attack surface if it's not needed at all.

mholt commented 4 years ago

I think this is a good idea, and I've also had it on my mind for a while. The admin endpoint is probably the next thing to get some love after I'm done with logging in #2831. Yes, we definitely need a way to turn off the admin endpoint, with the understanding that it will be impossible to turn it back on without restarting the process.

We should have a global option in the Caddyfile that makes it easy to turn off the admin endpoint (or configure it in other ways, like setting its listener address, etc).

This would probably translate to a property in the "admin" property of Caddy's top-level JSON config structure, like "disable": true or something. Applying that config would prevent all other future access to the configuration.

Oh, and by the way:

I think there are two use cases for caddy 2

There are WAY more than two :wink:. (But I know what you meant.)

What do you think, will that be satisfactory? If we're on the same page, please, go ahead and submit a PR!

aksdb commented 4 years ago

Since directives in the Caddyfile are opt-in, I would also like to treat the admin interface as "opt in" if the config is loaded via the Caddyfile (not in the other cases). That way if you want it plain simple but still prefer to have the admin HTTP endpoint, simply put "admin" or "admin_listener" (I will have to come up with a name :D) into the Caddyfile to enable it on the default port, or add some more manual options to customize it (custom port/unix socket).

@mholt Would you be fine with that?

mholt commented 4 years ago

Since directives in the Caddyfile are opt-in, I would also like to treat the admin interface as "opt in" if the config is loaded via the Caddyfile (not in the other cases).

Ahh... that's a possibility. Should the admin endpoint be off by default for all cases where Caddy is started with an initial config (i.e. when the -config flag is used to start or run caddy), even if it's not a Caddyfile, then?

aksdb commented 4 years ago

Since directives in the Caddyfile are opt-in, I would also like to treat the admin interface as "opt in" if the config is loaded via the Caddyfile (not in the other cases).

Ahh... that's a possibility. Should the admin endpoint be off by default for all cases where Caddy is started with an initial config (i.e. when the -config flag is used to start or run caddy), even if it's not a Caddyfile, then?

That would certainly work as well. Though I guess all other config formats are more or less already the "advanced" ones. So it can probably expected of the user to manually tune it.

However, a good argument towards always starting with a disabled admin endpoint (when a config is used) would be that Caddy then does not assume any port number by itself. You have to configure the admin listener like any other listener.

aksdb commented 4 years ago

Hmm now that I wrote that ... wouldn't it even be nice to have a real "handler" that deals with all admin stuff you can manually mount where ever you like? That way you could even already use basic auth etc. If someone fancies, he could add the admin endpoint at some sub-path on his public facing server.

(Out of scope for this ticket, but since we are already talking about admin endpoints ... :D)

mholt commented 4 years ago

However, a good argument towards always starting with a disabled admin endpoint (when a config is used) would be that Caddy then does not assume any port number by itself. You have to configure the admin listener like any other listener.

That's a good point. I do think it's OK to have a default listener address, but I agree that opt-in to the API listener should be explicit when using config files.

I figure if you use the -config flag at all, that means you're using config files rather than the API, so turning off the API is a safe, conservative default in that case. But if you intend to use the admin API after the initial load, you can still enable it with a single line in your config file before you start Caddy.

mholt commented 4 years ago

Hmm now that I wrote that ... wouldn't it even be nice to have a real "handler" that deals with all admin stuff you can manually mount where ever you like?

I've thought about this, and couldn't make it work in the early days of Caddy 2, but I probably could take another look at it soon. It's a possibility. (Either way, the admin API will have access controls before the end.)

mholt commented 4 years ago

Thanks for the great PR!

I will be giving the admin endpoint some more love in the coming days/weeks. Would be happy to continue collaborating with you, if you're interested.

I need to think hard about how/where to put the actual admin endpoint. It was the very first thing written in Caddy 2, actually (literally "func main()" and then "ListenAndServe()", that is how it all started!) -- so Caddy 2 literally didn't exist when the admin endpoint was made. Now that we have a working program, I think we can find a better way to establish the API. Like you said, maybe it should be able to benefit from the rest of Caddy's HTTP features...

mholt commented 4 years ago

@aksdb So, one thing I realized as I've been working on the API some more in preparation to finish #2850 is that the API pretty much always has to be enabled -- unless explicitly disabled.

The reason the API needs to be enabled is because commands like caddy reload and caddy stop use it to reload the config and stop the process. (Actually I think caddy stop just sends a signal right now, but that'll be changing very soon to use the API so that it works on Windows too.)

Currently, there is no way to change the configuration without the API enabled. (That could change in the future, but I'm trying to avoid leaning on signals as they are POSIX-only.)

So, in order to allow any config changes -- even those by way of the CLI -- we need to keep the admin endpoint enabled.

After giving it more thought, I do not think this is a bad thing or a security risk in the general case.

1) By default, Caddy's admin endpoint binds to the loopback interface (localhost). Thus, no external connections are accepted. In order to even connect, it would have to be proxied locally. Sure, a rogue application could proxy an external listener to that port, but then... you've already been pwned (see next point).

2) Caddy's threat model requires that you don't let untrusted code run on your machine (with the exception of web page JavaScript in web browsers, which I'll get to next). This is true of most user-space application threat models: if your computer is running an untrusted program, you're already compromised. (There are other ways that the OS can protect you in this case, but virtually nothing other user-space applications can do to protect you.)

3) Web pages often run untrusted code (JavaScript) in a web browser. They even have the ability to attempt connections to local or remote origins on any port. However, browsers enforce CORS, which forbid these connections by default, unless the server authorizes it for that specific origin (or *). It's worth noting that production servers usually don't also have web browsers running on them.

4) Even if CORS is enabled, the server can still protect against DNS rebinding and cross-site attacks by checking the Origin and Host headers on the request against a whitelist. Browsers don't let JavaScript modify those headers on outbound requests. As part of a big commit I'm pushing this week, the admin API has this protection which can be optionally enabled.

The reason that the protection described in (4) will optional in Caddy is because I don't believe it will effectively serve the majority of the user base, and working with Caddy's API becomes more tedious. For example, with those protections enabled, all your curl commands need to add -H "Origin: localhost:2019" (or whatever you set the listener address to be).

For local development, a successful attack on this endpoint seems both unlikely and insignificant.

For a production server, a successful attack on this endpoint seems even less likely (despite being more significant).

In either setting, additional protections can be enabled: access controls (WIP), and Origin/Host checking (coming this week). Both require more work to make legitimate requests (setting proper headers and possibly credentials). In extreme cases, the admin endpoint can be disabled entirely (the server can still run, it just can't be reconfigured without stopping it and restarting it with the updated config).

So, I hope you don't mind, my commits this week will cause the admin API to always be enabled by default, whether or not an initial config was provided. Its security controls should be sufficient to mitigate any practical attacks anyway.

Let me know if you have any further thoughts on this!

aksdb commented 4 years ago

@mholt Wow, thanks for that very thorough explanation :-) No that's totally fine. I was actually already contemplating how to best implement signal based config reloading ... good to know, that is is not done by design. I guess for my personal "threat model" I can live with the admin endpoint being a unix socket. (for "reasons" my Caddy runs on a server where other users have shell access ... and I would rather not have them manipulate Caddy via curl. The server in question is in general still very old-school, with functional user accounts and stuff like that. Only a few newer services are dockerized already. Even Caddy needs to run with host-networking, since I need to reverse-proxy a few of those services that run with functional user / system accounts.)

mholt commented 4 years ago

@aksdb Good to know, thanks! Yeah, in a multi-user machine I would recommend:

1) Unix socket listener (as you said), properly permissioned, and... 2) Coming soon there will be access controls so the API may require authentication at the application-layer if configured.

So that should be sufficient. Thanks!

mholt commented 4 years ago

This refactor introduces the option to completely disable the admin endpoint: https://github.com/caddyserver/caddy/commit/35f70c98fa1ea13882ee4f0406cd17f5545d0100

There's another issue (#2850) specifically about access controls. So, I will close this now! :)