lucaslorentz / caddy-docker-proxy

Caddy as a reverse proxy for Docker
MIT License
2.86k stars 168 forks source link

Issue redirecting from non-www to www or vice versa #555

Open antiftw opened 9 months ago

antiftw commented 9 months ago

Hi,

I'm having some trouble getting the redirect working as explained in this issue.

My docker-compose.yml looks like this:

version: '3.7'

services:
  app:
    build:
      context: .
      target: runtime
    labels:
      caddy_0: domain.tld
      caddy_0.reverse_proxy: "{{upstreams 80}}"
      caddy_1: www.domain.tld
      caddy_1.redir: "https://domain.tld{uri} permanent"

    networks:
      - caddy

networks:
  caddy:
    external: true

I've been looking into it myself what exactly is going on and to see if I was not understanding it or doing it right, by having the Caddyfile.autosave file inside the CaddyDockerProxy container open in an editor too see the actual effects of the config, and I think its pretty weird what's happening. The above config results in the following Caddyfile:

domain.tld {
    reverse_proxy 172.20.0.5:80
}

I've tried all kinds of different configs, and it seems like it just ignores anything I do when using the caddy: www.domain.tld rule.

For example: if I switch them around, like:

    labels:
      caddy_0: www.domain.tld
      caddy_0.reverse_proxy: "{{upstreams 80}}"
      caddy_1: domain.tld
      caddy_1.redir: "https://domain.tld{uri} permanent"

It results in:

domain.tld {
    redir https://domain.tld{uri} permanent
}

Then I tried an arbitrary subdomain, since I am using several other subdomains on this server using CDP, and have never encountered similar issues :

 labels:
      caddy_0: foo.domain.tld
      caddy_0.reverse_proxy: "{{upstreams 80}}"
      caddy_1: domain.tld
      caddy_1.redir: "https://foo.domain.tld{uri} permanent"

or

labels:
      caddy_0: domain.tld
      caddy_0.reverse_proxy: "{{upstreams 80}}"
      caddy_1: foo.domain.tld
      caddy_1.redir: "https://domain.tld{uri} permanent"

It works like expected:

domain.tld {
    redir https://foo.domain.tld{uri} permanent
}
foo.domain.tld {
    reverse_proxy 172.20.0.5:80
}

respectively

domain.tld {
    reverse_proxy 172.20.0.5:80
}
foo.domain.tld {
    redir https://domain.tld{uri} permanent
}

So for some reason it does not seem to like the www subdomain. Any ideas what this could be? It feels like I'm missing something obvious but after quite some time of trying to troubleshoot this myself I'm kinda lost for what it could be. Although I do now understand the inner workings of CDP a lot better than I did before this session :)

As a last note: when trying to add the required redir config block myself manually to the Caddyfile.autosave it does work as it's supposed to - for a small while of course - until CDP refreshes the file and stuff breaks again.

Thanks in advance.

lucaslorentz commented 9 months ago

Just tried and worked fine for me:

$ docker run --name caddy -d -p 443:443 -v /var/run/docker.sock:/var/run/docker.sock lucaslorentz/caddy-docker-proxy:ci-alpine

$ docker run --name whoami0 -d \
  -l caddy_0=domain.tld \
  -l "caddy_0.reverse_proxy={{upstreams 80}}" \
  -l caddy_0.tls=internal \
  -l caddy_1=www.domain.tld \
  -l "caddy_1.redir=https://domain.tld" \
  -l caddy_1.tls=internal \
  containous/whoami

Generated caddyfile:

  domain.tld {
    reverse_proxy 172.17.0.3:80
    tls internal
}
www.domain.tld {
    redir https://domain.tld
    tls internal
}

Doesn't seem related to www. Can you please try again? Check CDP container logs for errors, CDP probably removed a section due to Caddyfile parsing errors and it logged that action.

antiftw commented 9 months ago

Okay, thanks for verifying that it is not related to the www, Always nice to be able to definitively rule something out.

After a bit more trial and error, I think I've come a step closer to a solution. However I'm not sure how to fix it, but am guessing you probably do, since I have now found a clear error message:

{"level":"info","ts":1703414277.6724002,"logger":"docker-proxy","msg":"Process Caddyfile","logs":"[ERROR] Removing invalid block: Caddyfile:16: unrecognized directive: www.domain.tld\nDid you mean to define a second site? If so, you must use curly braces around each site to separate their configurations.\nwww.domain.tld {\n\treverse_proxy 172.20.0.6:80\n}\n\n"}

Weird thing is that it only gives this error with the www subdomain. I've registered another random domain in my DNS and that works without issues, with the exact same configuration as www with just the 3 letters changed in the docker-compose file.

The docker compose file is exactly the one posted in the beginning of my post - with of course my own (sub)domain filled in instead of the generic one. Not sure what other extra information could be relevant to help solve this?

antiftw commented 9 months ago

I might have some extra, useful information and also a way to get it working (not sure if its a 'good' way though) :

The Caddyfile.autosave generated by CDP is the following:

domain.tld {
    redir https://www.domain.tld
}
api.domain.tld {
    reverse_proxy 172.20.0.5:80 {
        header_down +Access-Control-Allow-Credentials true
        header_down +Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization"
        header_down +Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
        header_down +Access-Control-Allow-Origin https://domain.tld
    }
}
dav.domain.tld {
    reverse_proxy 172.20.0.3:80
}
mail.domain.tld

So now I was thinking, might it be because the www rule will be placed after the mail.domain.tld ruile - which does not come with curly brackets, and as such results in a syntax error, because there are two domain directives right after another without the usual comma to separate them?

That would explain why the other tests I conducted (using foo and email as subdomains) - which have there directives placed behind dav.domain.tld, and as such not being placed directly after another domain directive - worked without issues.

To verfiy this theory, I tried the setup using the subdomains lll and nnn respectively, to get the rules placed before and after the mail.domain.tld and this indeed seems to indicate that it is the issue, because the first one creates a valid config and the latter gives the same error, and removes the block that is placed behind mail.domain.tld.

For context: The mail.domain.tld is located there so that Caddy automatically refreshes the certificates of my mail server, done by only adding the following to its docker-compose.yml:

    labels:
      caddy: mail.domain.tld

I have now managed to get it working by adding a directive to this config, as following:

    labels:
      caddy: mail.domain.tld
      caddy.respond: "Unauthorized 403"

I think this should work without issues, because the mail.domain.tld is not used as a web-domain.

However I am not sure if this wont bite with the way CDP generates/refreshes its certificates? Because I am aware of refreshing methods from LetsEncrypt that use HTTP requests to validate domain ownership, and can imagine this might interfere with that.

So I was wondering what the recommended way to configure CDP for automated certificate renewal would be for a mail-domain in this case?

If my solution is sufficient feel free to close this issue, and thanks again for pointing me in the right direction.

lucaslorentz commented 9 months ago

Got it. Yeah we didn't anticipate that users would want to create websites without any directives.

Nice that you found exactly what was the problem.

I think whatever you configure inside a site doesn't affect the ACME http challenge responses. So, your workaround should be fine. @francislavoie might be able to confirm that from top of mind.

antiftw commented 9 months ago

Ok, yea glad I found it indeed. Would be nice to hear that confirmed but I'm going to trust that it's fine unless I hear otherwise :)