Does Caddy Support Internal Links like Nginx?

1. Caddy version (caddy version):

v2.1.1 h1:X9k1+ehZPYYrSqBvf/ocUgdLSRIuiNiMo7CvyGUQKeA=

2. How I run Caddy:

a. System environment:

CentOS Linux release 7.7.1908 (Core)

b. Command:

systemctl start caddy

c. Service/unit/compose file:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target

[Service]
User=nginx
Group=nginx
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/caddy.conf
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/caddy.conf
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

d. My complete Caddyfile or JSON config:

{
    "apps": {
        "http": {
            "servers": {
                "my_server": {
                    "listen": [":60000"],
                    "automatic_https": {
                        "disable": true
                    },  
                    "routes": [
                        {   
                            "handle": [{
                                "handler": "reverse_proxy",
                                "upstreams": [
                                    {   
                                        "dial": "192.168.154.15:80"
                                    }   
                                ]   
                            }]  
                        }   
                    ],  
                    "tls_connection_policies": [{
                        "match": {
                            "sni": ["subdomain.domain.com"]
                        }   
                    }]  
                }   
            }   
        },  
        "tls": {
            "certificates": {
                "load_files": [{
                    "certificate": "/etc/ssl/subdomain.domain.com/fullchain.pem",
                    "key": "/etc/ssl/subdomain.domain.com/privkey.pem"
                }]  
            }   
        }   
    }   
}

3. The problem I’m having:

I run a Django server on the same system that Caddy is running on.
When users connect to my server, and login, they have a list of remote systems they have access to. When they select a remote system and want to connect to its web interface, my Django:

  • Uses the Caddy API to setup a reverse proxy to that system’s web interface on an unused port (ex port 4000)
  • Tells the user’s browser to go to subdomain.domain.com:4000.

This works, and lets the user interact with the remote system’s web interface through my Django website. It does have a few disadvantages though:

  1. I need a unique port for every remote system actively in use
    • This is not ideal, but my Django website does not currently, nor will it ever have enough simultaneous users for me to run out of ports
  2. Security
    • If a user sees that they are connecting to subdomain.domain.com:PORT, and simply guesses a different PORT number, they’ll connect to a system they don’t own

Is there a better way to do this?

I’m thinking that:

  1. If Caddy has something similar to Nginx’s concept of Internal paths, where it won’t load a resource for a user unless Django specifically allows it, this would solve my security issue
  2. I could use domain names instead of ports to match on, and a wildcard SSL cert, which would solve my first problem of using one port per remote system
    • Unfortunately our DNS provier (Network Solutions) does not appear to be supported by DNS challenge, which is the only way to get a wildcard cert (as far as I can tell). I can’t write Go to make a module to support them, and even if I could, they don’t have an API to update TXT records (I asked them)

Does Caddy have a concept of internal resources like Nginx does? Anything else I can do? Any suggestions would be appreciated.

Caddy has a solution for this called On-Demand TLS:

Caddy will fetch a cert per subdomain on the fly, when required, instead of a wildcard cert. This feature was designed with SaaS in mind, so you can let your customers point their own domains to your server and Caddy will fetch a cert for it as long as it’s allowed (strongly recommended that you configure an ask endpoint for Caddy to check if the domain is allowed).

That said, regarding internal resources, can’t you just use port numbers that aren’t publicly exposed?

If you’re worried about “security” here, you should have an authentication gate on these services (or make sure auth is forwarded with SSO). You’re basically doing “security by obscurity” otherwise.

I’m not sure that the SaaS-oriented design of Caddy helps me here. I control all the upstream servers (they aren’t customers - they are accessed by customers). None of these upstream servers have port 80 or 443 accessible from the internet, and our DNS provider isn’t supported by Caddy for DNS challenge, so from what I understand, I can’t give them their own domain names - at least not easily.

As for internal ports, I’m not quite sure how that would work.

The point of using port numbers to connect to upstream servers is that when I tell a client to load:

subdomain.domain.com:60000/main.html

and a resource on main.html (like some javascript I didn’t write, or bootstrap or something) points to another resource using an absolute path (/some_font.ttf), that will resolve to subdomain.domain.com:60000/some_font.ttf. The browser will make a request for that resource, and since Caddy will know to forward all requests on port 60000 to one specific upstream server, everything will work.

To access a different upstream server, the client would just be told to connect to a different port instead:

subdomain.domain.com:60001/main.html

It would be nice if I could do something like setup the system to say:

If the user requests subdomain.domain.com/upstream_system/1, connect them to the web server of upstream system 1
If the user requests subdomain.domain.com/upstream_system/2, connect them to the web server of upstream system 2
Etc

The problem with that is that when the user connects to subdomain.domain.com/upstream_system/2, and that same main.html requests /some_font.ttf, it will resolve to subdomain.domain.com/some_font.ttf, not subdomain.domain.com/upstream_system/2/some_font.ttf. This means that the web server will have no idea which upstream server to route the subdomain.domain.com/some_font.ttf request to.

The only way I can think of to make it work using ports is if the ports are publicly accessible.

Using ports works, but is a pain, and a security nightmare.

When you say forward the credentials with SSO, what exactly does that mean? They would be forwarded in an HTTP header? From where, and to where? To Caddy? In my setup, the request comes directly to caddy without hitting Django, so I’m not sure what would be adding the header.

Yeah, that’s a problem then. Who’s your DNS provider? Why can’t you open 80/443 to Caddy?

Yeah - that’s what sub-subdomains can help you solve, e.g.

https://upstream1.subdomain.domain.com/some_font.ttf

I was making assumptions about your system cause you’re being kinda vague about the setup. Basically the idea would be that if the user is authenticated on one system, then you’d forward the auth token along with the request to Caddy, and maybe using some plugin (say, maybe something like GitHub - abiosoft/caddy-hmac: Caddy v2 hmac signature validation middleware) you could verify that the user is trusted. There’s all kinds of ways to do this, but it totally depends on your system.

Just re-reading this; it appears to be Network Solutions. There doesn’t appear to be an implementation for them even in go-acme/lego: DNS Providers :: Let’s Encrypt client and ACME library written in Go.

Oh my bad, I read right past that cause that doesn’t even sound like a company name :joy:

1 Like

Haha ya. Network Solutions. Our company has been using them for years, and while we’ve had no issues with them, they told me yesterday that they have no public API of any kind, so I’m pretty sure it’s no possible to add it to the LEGO project even if one had the skills, and the inclination.

Apparently they have some sort of undocumented private XML-based API at least one project has attempted to use: GitHub - rbrotherton/NetSolApi: Class to interact with the Network Solutions API. Not sure if it supports creating/editing records though. Even if it does, the lack of public documentation would make it difficult to use, and the fact that they can change, remove, or secure it at any point without regard for how any public projects using it might be effected makes it a bad idea to use.

As for opening port 80/443, there are a few challenges. Without going into too much detail about our entire system, which would require extensive diagrams and explanations, the Caddy server is behind a firewall appliance which also acts as an SSL termination point. When users hit port 80, they hit this firewall appliance, which returns a 301 redirect to https (port 443), and when they hit port 443, they also hit the firewall appliance, which terminates the SSL connection, and forwards the request, using HTTP, to the server Caddy is running on.

I’m hoping I can get something working with subdomains somehow. I guess I’m just wondering if I’m missing any obvious solutions. Doesn’t sound like it.

Thanks for the help guys.

Let’s Encrypt requires the DNS challenge for wildcard certs, but some other ACME CAs might not, for example, ZeroSSL doesn’t require the DNS challenge for wildcards as far as I know. Caddy v2.2 which is releasing today has native support for ZeroSSL. (But you can use ZeroSSL already pre-2.2, it just involves manually setting EAB credentials, whereas 2.2 will do that for you.)

Interesting.

I have no problem updating my Caddy binary to the latest version. I’ll give that a try.

Thanks!

This topic was automatically closed after 30 days. New replies are no longer allowed.