opnsense / plugins

OPNsense plugin collection
https://opnsense.org/
BSD 2-Clause "Simplified" License
833 stars 620 forks source link

www/nginx: Add ACME proxy support #975

Closed jpawlowski closed 4 years ago

jpawlowski commented 5 years ago

It is kind of bothersome to configure ACME challenge forwarding to internal servers as to the NGINX configuration becomes quite crowded but such simple rules, especially as you cannot recycle a single location definition for multiple upstream servers.

I suggest to extend the existing LetsEncrypt support with an additional local PHP routing script:

  1. If no local file for ACME challange was found in /var/etc/acme-client/challenges, forward HTTP request to a local PHP script.

  2. The PHP script should resolve the requested hostname from the HTTP header using the local resolver settings.

  3. It is assumed that the firewall would be able to resolve it to a network internal IP address (IPv4 or IPv6, both may also be public IPs).

  4. The firewall shall forward the ACME request to that internal server on port 80 and enforcing the correct URI path. Don't think the DNS lookup needs to be done explicitly, it may just be performed by the firewall. The only thing to consider is avoiding a loop, e.g. the script shall ensure it is not connecting back to the firewall on port 80.

I imagine this would simplify the ADME challenge handling for internal servers a lot as it is quite normal that DNS validation cannot be used or is even not supported by some blackboxes (e.g. Proxmox VMM).

fabianfrz commented 5 years ago

you should be able to disable intercepting ACME challenges. Then the request is forwarded normally to the upstream as specified. It is a checkbox in the HTTP block.

jpawlowski commented 5 years ago

You didn't get my point right.

What I want is an easy proxy method to forward ACME challenges to hosts in my internal network w/o a need to setup a dedicated HTTP proxy for each of those. I think this could be achieved way easier as the firewall is able to lookup my internal hosts dynamically from the DNS and could then decide to forward that request. It won't be possible using native NGINX method but using a PHP script that in turn would forward the request and send back the response via NGINX to the original LetsEncrypt host.

jpawlowski commented 5 years ago

It's untested with any reverse proxy yet, but this is the first version of a corresponding PHP script to be used for such scenario: https://github.com/jpawlowski/acme_proxy.php

jpawlowski commented 5 years ago

Happy to confirm the script is now working as expected and could already be used by this custom location in HTTP server "default_server:80":

image

Would love to see that functionality fully integrated to OPNsense.

fabianfrz commented 5 years ago

@jpawlowski the router script works slightly different than internal code because the internal ones do not get the standard parameters. Instead they get some special config properties they need to access the entries in the configuration. For example see: https://github.com/opnsense/plugins/blob/master/www/nginx/src/opnsense/scripts/nginx/ngx_auth.php

Your script by the way has a security impact because it allows using the host as a proxy to access content from the internet (not limited). Some headers are not passed because they are rewritten by nginx anyway.

I don't think including this is a good idea anyway, The router script may be the ideal place for it (nothing that needs to be supported by the default plugin).

jpawlowski commented 5 years ago

@fabianfrz what do you mean by "the router script", can you be more specific and post a reference to it? The acme_proxy.php script does not require any special properties (and doesn't get those mentioned in the ngx_auth.php script anyway, so I don't get your point here).

Your script by the way has a security impact because it allows using the host as a proxy to access content from the internet (not limited).

This is a PoC so for sure it can be improved. Currently www/nginx does not allow custom parameters to be added, otherwise a restricted domain list could easily be handed over to acme_proxy.php already today using fastcgi_param. This is already possible for other settings like changing destination port (_acme_dstport), enable TLS (_acmetls) or disable host validation (_acme_tlsverify).

The router script may be the ideal place for it (nothing that needs to be supported by the default plugin).

I don't understand the meaning of this sentence. Besides "what is the router script?" I also want to ask what the "default plugin" is...

fabianfrz commented 5 years ago

The option router script is exactly what you want. It means that all requests are routed to this script (advanced option, enter the full path relative to the document root): https://github.com/opnsense/plugins/blob/master/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf#L78

It looks almost like URL rewriting and just pretty standard. You get all requests matching the passed to the script where you can handle them.

jpawlowski commented 5 years ago

Thank you. As you can see from the screenshot I posted above, I am already using that option to run my acme_proxy.php script.

The point is that I think it could be an improvement to directly integrate that script into the www/nginx package as configuration options would be more user friendly (see your comment above about domain restriction) and I am probably not the only person requiring to forward ACME requests to the internal network.

Also, the script is supposed to be used in the default_server on port 80 which is something completely unsupported by www/nginx today. This is what you had confirmed yesterday.

fabianfrz commented 5 years ago

@jpawlowski the listen thing is a bug in the validation and I'll look for fixing that. By the default the field should only allow numeric ports and some special ones (ssh, http) which will also cause some trouble in most plugins because they rely on a numeric input.

AdSchellevis commented 4 years ago

This issue has been automatically timed-out (after 180 days of inactivity).

For more information about the policies for this repository, please read https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md for further details.

If someone wants to step up and work on this issue, just let us know, so we can reopen the issue and assign an owner to it.

jpawlowski commented 4 years ago

I still like my idea to somehow help with automatic backwards routing of ACME challenge responses. Apparently others do like to configure this for each and every internal server just by themselves... 🙄