Is there a way to selectively bypass mTLS client auth?

1. Caddy version (caddy version):

v2.1.1 h1:X9k1+ehZPYYrSqBvf/ocUgdLSRIuiNiMo7CvyGUQKeA=

2. How I run Caddy:

Caddy runs as a reverse proxy in a rootless podman container. It listens on ports 8080 and 8443 which I forward traffic from ports 80 and 443 to. Everything mostly works great, I just want to know about some mTLS fallback configuration things.

a. System environment:

Fedora 32, podman version 2.0.2.

b. Command:

N/a

c. Service/unit/compose file:

podman create \
--name=caddy \
--pull=always \
--pod=web-services \
--detach \
--memory 256m \
-v ~/services/caddy/data:/data:Z \
-v ~/services/caddy/config:/config:Z \
-v ~/services/caddy/caddyfile:/etc/caddy:Z \
caddy

d. My complete Caddyfile or JSON config:

{
    http_port   8080
    https_port  8443
}

(mTLS) {
    tls {
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file /config/custom-certs/ak.codes-ca.cer
        }
    }
}

ak.codes {
    encode gzip
    reverse_proxy localhost:3434
    import mTLS
}

plex.ak.codes {
    reverse_proxy localhost:32400
}

tautulli.ak.codes {
    reverse_proxy localhost:8181
    import mTLS
    tls {
        on_demand
    }
}

cockpit.ak.codes {
    reverse_proxy 10.1.1.2:9090
    import mTLS
}

transmission.ak.codes {
    reverse_proxy localhost:9091
    import mTLS
    tls {
        on_demand
    }
}

radarr.ak.codes {
    reverse_proxy localhost:7878
    import mTLS
    tls {
        on_demand
    }
}

sonarr.ak.codes {
    reverse_proxy localhost:8989
    import mTLS
    tls {
        on_demand
    }
}

jackett.ak.codes {
    reverse_proxy localhost:9117
    import mTLS
    tls {
        on_demand
    }
}

3. The problem I’m having:

The scenario I want is that for certain subdomains caddy acts as a reverse proxy for, I want to fallback to basic auth should an mTLS client cert not be presented, as opposed to outright rejecting the traffic. Is there a way to do this? Ideally, I could have my own personal devices with correct client certs not need to use basic auth, and my friends who I don’t have mTLS set up for could use a username/password to get in to certain domains. I figured there must be some way to use basic auth matchers to do this.

4. Error messages and/or full log output:

5. What I already tried:

I got basic auth working just fine, and mTLS working just fine. I just want to be able to use both. There must be a way to gracefully fall back to basic auth?

6. Links to relevant resources:

Hmm, my gut would be to set client_auth in verify_if_given mode and put some kind of matcher on basicauth to check for (lack of) a verified client certificate.

I’m not sure if there’s a matcher you could use or a placeholder you could run an expression against, though…

Alright, looks like we’ve got something you can test out.

There’s a series of http.request.tls.client. placeholders (see JSON Config Structure - Caddy Documentation).

Apparently http.request.tls.client.subject is nil if there’s no cert, so we can test against it.

So you should be able to match an expression like this: expression {tls_client_subject} == null

Edit: per cel-spec/langdef.md at master · google/cel-spec · GitHub, the null value is null, not nil.

(The shorthand placeholder comes from here: Caddyfile Concepts — Caddy Documentation)

And run basicauth off that with mTLS in verify_if_given mode.

Essentially, you connect, you can either offer a cert or not. If you have a cert, and it’s valid, you get in. If you have a cert and it’s invalid, it gets rejected. If you have no cert, you get in, but stop at basic auth. Should be the behaviour you’re after, right? Give it a test and let us know if that works for you.

(Thanks to @francislavoie for finding the placeholder!)

1 Like

Wow, thank you so much @Whitestrake! Followed your suggestions and this has worked absolutely flawlessly. I now have graceful fallback to basic auth which saves me entering creds when I don’t want to and lets me share whatever domains with others I want to without having to issue them a cert. This should be added to the docs as an example!

3 Likes

It definitely belongs in our examples gallery for sure!

Do you have a working final config you could share? Make it a wiki post! “Example: mTLS with Basic Auth fallback” or something like that.

3 Likes

I do have a working config, I’ll add it to the wiki :slight_smile:

1 Like

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