-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
auto managed domain mistakenly using self-signed #6694
Comments
Seems to be a duplicate of #5933. |
the problem is when I visit |
@mholt Please re-open the issue, Please understand the fully before closing it |
@arpitjindal97 Not sure what I am missing here, please enlighten me. This seems to be a duplicate, as I said. The other issue will track this. |
Because you used
You haven't presented evidence of this.
We haven't seen evidence of this. |
Ignore @mholt @mohammed90 Here is the evidence of this:
|
The certificate CN is literally |
What's the difference between this issue and #5933? |
I have configured the self-signed certificate under for It shouldn't matter what the CN of certificates are. |
@mholt My apologies, you are correct it is related to that issue. I didn't have a closer look earlier. When can we expect a fix? |
|
Even after using 2.9, it doesn't seem to work. wildcard cert is still being picked
|
Caddy 2.9 works fine. You can try reproduce with the following example below to troubleshoot what is going wrong for your actual setup :)
networks:
default:
name: example-net
volumes:
custom-certs:
name: example-tls
services:
reverse-proxy:
image: caddy:2.9
depends_on:
- get-certs
volumes:
- custom-certs:/srv/tls
# Config is embedded for copy/paste of single `compose.yaml` to run example:
configs:
- source: caddy-config
target: /etc/caddy/Caddyfile
# For this example, DNS lookups from containers on this network
# will resolve these FQDN to this Caddy container:
networks:
default:
aliases:
- caddy.example.test
- wild.example.test
- example.test
- sub.example.test
- also-wild.example.test
# Since Caddy depends on this service before it starts,
# compose will first run this service to provision the external certificates:
get-certs:
image: smallstep/step-ca
volumes:
- custom-certs:/tmp/certs/
# This service runs as non-root (1000:1000) by default,
# change to desired UID/GID ownership of certs generated:
user: root
# Support for running the custom script below:
working_dir: /tmp/certs
entrypoint: /tmp/generate-certs.sh
configs:
- source: generate-certs
target: /tmp/generate-certs.sh
# Make script executable:
mode: 500
configs:
caddy-config:
content: |
# Global Settings
{
# Optional: For testing purposes (otherwise defaults to a public CA)
# Have Caddy provision certs locally (self-signed):
local_certs
}
caddy.example.test {
tls force_automate
respond "I am using a certificate provisioned by Caddy"
}
wild.example.test {
respond <<HEREDOC
I am using the external wildcard cert loaded
from the tls directive of another site block
HEREDOC
}
example.test, sub.example.test, also-wild.example.test {
tls /srv/tls/example.test/cert.pem /srv/tls/example.test/key.pem
respond "I am using an externally loaded certificate"
}
# NOTE: For `smallstep/step-ca` container to run it's `step` CLI to create a cert:
generate-certs:
content: |
#!/usr/bin/env bash
mkdir -p ca example.test
step certificate create 'Smallstep Root CA' ca/cert.pem ca/key.pem \
--profile root-ca --no-password --insecure --force
step certificate create 'Smallstep Leaf' example.test/cert.pem example.test/key.pem \
--san 'sub.example.test' --san '*.example.test' \
--ca ca/cert.pem --ca-key ca/key.pem \
--profile leaf --no-password --insecure --force Verify: $ docker compose up -d --force-recreate
$ docker run --rm -it --volume example-tls:/srv/tls --network example-net alpine
$ apk add curl step-cli jq
#
## All sites responding:
#
# NOTE: Insecure flag is used due to Caddy's `local_certs` self-signed CA cert missing:
$ curl --insecure https://caddy.example.test
I am using a certificate provisioned by Caddy
$ curl --insecure https://wild.example.test
I am using the external wildcard cert loaded
from the tls directive of another site block
$ curl --cacert /srv/tls/ca/cert.pem https://sub.example.test
I am using an externally loaded certificate
#
## Certificates used are the ones expected:
#
$ step certificate inspect --insecure --format json https://caddy.example.test \
| jq '{ issuer_dn, subject_dn, names }'
{
"issuer_dn": "CN=Caddy Local Authority - ECC Intermediate",
"subject_dn": null,
"names": [
"caddy.example.test"
]
}
$ step certificate inspect --roots /srv/tls/ca --format json https://wild.example.test \
| jq '{ issuer_dn, subject_dn, names }'
{
"issuer_dn": "CN=Smallstep Root CA",
"subject_dn": "CN=Smallstep Leaf",
"names": [
"sub.example.test",
"*.example.test"
]
}
$ step certificate inspect --roots /srv/tls/ca --format json https://sub.example.test \
| jq '{ issuer_dn, subject_dn, names }'
{
"issuer_dn": "CN=Smallstep Root CA",
"subject_dn": "CN=Smallstep Leaf",
"names": [
"sub.example.test",
"*.example.test"
]
}
# Due to the site-block forcing external via `tls` directive, this will return an invalid cert:
$ step certificate inspect --insecure --format json https://example.test \
| jq '{ issuer_dn, subject_dn, names }'
{
"issuer_dn": "CN=Smallstep Root CA",
"subject_dn": "CN=Smallstep Leaf",
"names": [
"sub.example.test",
"*.example.test"
]
} NOTE: If the site-block with I do not know for |
The wild card certificate I use has |
@arpitjindal97 yes, sorry I missed that 😅 Prior to using SANs for provisioning, the FQDN could be in the leaf certificate That is, yes Caddy would select that externally loaded certificate instead of provisioning a separate one, even if there was no wildcard with that certificate, I also verified that if the FQDN is an explicit SAN as well, this would use the external certificate (or any other one provisioned by Caddy), even with So for your ReferenceThis is a variant of the earlier reference above, specifically tailored to replicate your FQDN + wildcard scenario. I self-contained the networks:
default:
name: example-net
volumes:
custom-certs:
name: example-tls
services:
reverse-proxy:
image: caddy:2.9
depends_on:
- certs
volumes:
- custom-certs:/srv/tls
configs:
- source: caddy-config
target: /etc/caddy/Caddyfile
networks:
default:
aliases:
- arpit.msmartpay.in
- arpit-test.msmartpay.in
# Provision the externally loaded wildcard cert + provide cert inspection:
certs:
image: localhost/certs
volumes:
- custom-certs:/srv/tls
pull_policy: build
build:
dockerfile_inline: |
FROM alpine:3.21
RUN <<HEREDOC
apk add curl jq step-cli
mkdir /srv/tls
HEREDOC
# This is a small shell script that you can with: docker compose run --rm certs cert-info arpit.msmartpay.in
# NOTE: The `$$` is required to escape `$` from the Docker Compose variable interpolation feature.
COPY --chmod=755 <<"HEREDOC" /usr/local/bin/cert-info
#! /usr/bin/env sh
FQDN="$${1}"
curl -w '\n' --insecure "https://$${FQDN}"
step certificate inspect --format json --insecure "https://$${FQDN}" | jq '{ issuer_dn, subject_dn, names }'
HEREDOC
# Provision a locally signed certificate with a private CA root:
# This command is provided via the `exec` format instead of `shell`,
# Thus `ash -c '<string>'` is the equivalent form:
command:
- ash
- -c
- |
cd /srv/tls
mkdir -p ca msmartpay.in
step certificate create 'Smallstep Root CA' ca/cert.pem ca/key.pem \
--profile root-ca --no-password --insecure --force
step certificate create 'Smallstep Leaf' msmartpay.in/cert.pem msmartpay.in/key.pem \
--san 'arpit.msmartpay.in' --san '*.msmartpay.in' \
--ca ca/cert.pem --ca-key ca/key.pem \
--profile leaf --no-password --insecure --force
configs:
caddy-config:
content: |
{
local_certs
auto_https prefer_wildcard
}
# If the wildcard certificate has this site-address as an explicit CN or SAN,
# Caddy will use that certificate instead of provisioning a separate certificate:
arpit.msmartpay.in {
tls force_automate
respond "I should be using a certificate provisioned by Caddy"
}
arpit-test.msmartpay.in {
tls /srv/tls/msmartpay.in/cert.pem /srv/tls/msmartpay.in/key.pem
respond "I am using an externally loaded certificate"
} The CN $ docker compose up -d --force-recreate
$ docker compose run --rm certs cert-info arpit.msmartpay.in
I am using a certificate provisioned by Caddy
{
"issuer_dn": "CN=Smallstep Root CA",
"subject_dn": "CN=Smallstep Leaf",
"names": [
"arpit.msmartpay.in",
"*.msmartpay.in"
]
}
# If you swap the SAN for the leaf cert CN instead:
$ docker compose up -d --force-recreate
$ docker compose run --rm certs cert-info arpit.msmartpay.in
I am using a certificate provisioned by Caddy
{
"issuer_dn": "CN=Smallstep Root CA",
"subject_dn": "CN=arpit.msmartpay.in",
"names": [
"arpit.msmartpay.in",
"*.msmartpay.in"
]
} |
I can't really change wildcard certificate in my production because the impact is bigger. When can we expect a fix for this? |
I'm not a developer for Caddy, I just helped you to properly identify and reproduce the problem. If you do not get a response from a maintainer by like Monday, maybe it's because the issue was close as "Not Planned" and you'd need to open a new issue. If so, just link to my comment above which details what appears to be a bug. Your external certificate has a 10 year expiry and given the CN is clearly self-signed? So I'm not sure why the impact of correcting your certificate is a blocker sorry? It'd be faster to resolve that then wait on a fix to be formally released.
Provisioning a certificate locally without LetsEncrypt is quite simple, as shown above. |
Yes. Caddy sees it as a certificate that already serves that domain. |
@mholt while that makes sense it does conflict with what Shouldn't it be similar to I know the intention for |
I am honestly disappointed on codebase being so wrong.
There should be an option to load TLS globally which will be scoped to every site. Precedence must be followed in scenario when global TLS and dedicated site TLS are provided. How hard is it to implement such a basic flow? |
TL;DR: You provisioned your external certificate incorrectly, that's your core problem.
My prior comment explained that to you. Below is a further breakdown if you need to understand the logic better. Let's start from your original config shared at the top of this issue. You requested in global settings that Caddy ignore loaded certs, which means any site address will be provisioned a certificate even if one was already loaded from an external source like the one you did via the That is why both sites are provisioned. If you don't want that then remove that global setting. You then ask why are they both still using the externally loaded certificate, despite the provisioning.
This is where Traefik would differ here with its router tls settings which is similar to the tls directive in Caddy's site blocks. Traefik allows you to specify an ACME resolver and configure multiple beyond the default one. Caddy only has the one I think (but supports multiple vendors defaulting to LetsEncrypt + ZeroSSL). Despite that I think Caddy is lacking a tls directive option to favor the acme provisioner (which could be used in combination with the global So I'm not sure what you're trying to request should be done differently here beyond confirmation from the maintainers that Caddy needs to identify valid certificates for a given site-address, and if there is more than one know how to select the correct one. We've already established that your certificate was provisioned incorrectly and it's why you have this problem in the first place, to workaround that mistakeyyou can use |
Let me explain myself in better way, this is a simple config:
We have loaded two certificates, when caddy looks for the certificate for xxx then it's able to find one. When caddy looks for yyy then:
Coming to There could be one scenario where we might need this feature, i.e.
|
TL;DR: Ok thanks for that.
The global certificate you propose I assume is meant to be like Traefik's default one, which is a fallback IIRC (and you can provide your own external certificate to use as the default for global fallback). As such without an external one, it absolutely can't guarantee the site-address to exist in the cert, only if you provide one for it to use instead that does. You then propose the existing certificate storage effectively become the global storage, with site-addresses for a site-block using their own isolated storage with
While I agree with you that as a user, global settings What you propose would be breaking, so like the experiment with I agree as a user that it is easier to reason about as a layered configuration with a base/global config which a site block can override with the |
Thanks for the discussion, and especially thank you to @polarathene for the thoughtful consideration of this complex topic! I concur with Brennan's analysis. (Sorry, I've been following the issue for a few days, but have been very busy with some personal things.) I also understand where you're coming from, @arpitjindal97. The design of the global cert cache is such that there is no notion of sites or config blocks, because of the general-purpose nature of the code of the underlying library. It can be used in many things and in many ways, so we didn't want to impose limitations on that. The way Caddy's certificate cache is designed works well for 99.99~% of users, without much/any tuning. It is very efficient this way. Now, it's true, we could use multiple cert caches in Caddy, one per "site", though even the Caddy JSON config doesn't have any notion of "sites". That's strictly a Caddyfile concept that results in a pattern in the JSON config as it pertains to HTTP handlers. When CertMagic loads a certificate into the in-memory cache, whether automated or manually-managed, it can associate the certificate with arbitrary user tags: https://pkg.go.dev/github.com/caddyserver/certmagic#Certificate.Tags These tags are also exposed in Caddy's JSON configuration, but not the Caddyfile: https://caddyserver.com/docs/json/apps/tls/certificates/load_files/tags/ (and you can find the Anyway, over in your Notably, there's Tagging a loaded certificate does not mean it will only be used by connection policies that designate them, but it does mean that those connection policies will only use those certificates. So to ensure the "per-site" behavior you are wanting, @arpitjindal97, you can create connection policies for all of your "sites" to make sure they only load the specific certificate you require. That essentially maps domain names to a specific eligible certificate, rather than any eligible certificate. At the end of the day, a certificate is a certificate. If it satisfies the TLS connection parameters, it can be used without errors, so we do not go to the extra complexity of doing this all for you, since most users do not need this. (I am still not sure why you need it, tbh, though I can understand your way of thinking.) As for |
Thanks for taking the time to break all that down @mholt ! ❤ I haven't used the JSON config myself, but I assume it'd click more if I adapted a Caddyfile to see how the two JSON doc links are laid out. From what you've shared the gist of what I understood with this alternative solution via JSON is:
For example, the rough equivalent functionality (in the sense of selection, not provisioning) of
Without looking at real JSON config for that I'm probably off the mark if |
If possible, could you expand on that for context of this whole thread since that's the main issue you're trying to workaround? The certificate looks like it's been provisioned by you locally. Assuming there was a root CA certificate paired with that and distributed to your client devices that need to trust the certificate, wouldn't you only need to provision a new leaf certificate for the wildcard? I am curious what the impact concern is that places too much friction on what should be a simple change? 🤔 |
@polarathene Basically; but I would create a connection policy for each domain name and just map 1:1 domains to certificate tags, to keep it simple. |
@polarathene I wouldn't explain the reason behind creating the certificate the way I did. I can absolutely fix my product and make it compatible with caddy. The purpose of raising issue here was to let developers know that there is a bug in their tool. I also thought of contributing the fix to caddy initially but after looking at the codebase how common storage is being used. I realised its a much bigger change and devs are the right people to do fixes. In my opinion, caddy is still not mature enough. Not de-faming it but the way it handles TLS is just wrong. Metrics is also another aspect which caddy lacks. Request per second is clustered together as Caddy got my eyes only because they could provision certs from let's encrypt automatically. I have been using nginx for many years with multiple sites and certs. No issues so far. I will look at traefik and see if it can handle such use-cases and provide meaningfull metrics. @mholt Can you provide a sample JSON which I can use to achieve the behaviour I want? |
Probably not worth my time since it sounds like you won't be using it:
Caddy can do it, as described above, but I don't have the time to do the work for you for free at the moment. |
I may sound rude in my comments because of the frustration and disappointment. Because automatic provisioning certs from let's encrypt in a reverse proxy sounds a really good selling point and upon testing I found it mishandling certs in a way I didn't expect any reverse proxy would do.
The requirement I am coming up with aren't any edge case. As a hobbyist when user has a couple of sites and just wants basic https on them, then caddy is really good. But, I am willing to use it on an Enterprise level. I can also explain a use-case which you will find bizarre.
Solution:
Basically, Requirement is fulfilled traffic is encrypted but cert validation check fails which is totally okay. I can't go into details on the rational behind it because it's internal to the company. But you got the idea, Caddy should be able to use a cert on the site even if the cert doesn't belong to this domain. It's a strange use-case but fulfils the requirement and nobody cares if the cert matches the hostname or not because they are all on internal network. If you find these use-cases worth spending time then you can put them on roadmap and prioritise accordingly. No hard feelings. Think of it this way, "Why should someone use caddy? Why not nginx, envoy, haproxy or traefik. These tools have been in industry for some time. Tried and battle tested. What is something caddy has to offer apart from existing functionalities" Observability on reverse proxy is must must. |
TL;DR:
That said I do sympathise with the UX not being intuitive here, but your scenario that triggers it seems niche and probably always an anti-patten / mistake on the user end. Full response (verbose / rant)A good example of a "fix" for enterprise bumped the FD limit to It was misinformed and caused various problems that were difficult to troubleshoot, software that worked correctly outside of a container would now break with excessive CPU and memory usage regressions because this change increased workloads affected by over 1 million times the iterations or memory allocations. I helped tackle resolving that, now the enterprise software breaks without additional admin config, but that software still has not implemented correct logic to raise soft limits at runtime, thus the modified config would bring back the failures for software that regressed significantly. This issue doesn't have a dire outcome like that mentioned fix enterprise introduced, but is intended to highlight that sometimes we focus on fixing the wrong concern. You're not making a convincing argument sorry. Nginx doesn't provision certs, so the comparison is not fair. You could explicitly have both site blocks use their own I have not confirmed if traefik avoids the same concern you hit with caddy out of the box, I would give it a quick go myself but my system decided to break yesterday 🙄 The bolded statement (I'd quote but it's awkward to do on this phone), doesn't make sense with this discussion... Your problem is you polluted the cert storage with a cert that would be a valid match for the site-address, it got preferred instead of the other provisioned one for whatever reason. Caddy will happily serve up a cert unrelated to the domain when you tell it to via the I doubt there's many valid scenarios where this should really matter that isn't an XY problem.
You need to look at how pragmatic this scenario you have is. If you're familiar with maintaining projects with a large list of of tasks to tackle and prioritise, then you understand why this is so niche to justify the time to prioritise over tasks which have a wider value impact to the project and users. Despite that, you still received a excellent support and suggestions on how you could resolve the issue. I have to deal with this problem on both sides frequently enough. In this case there just isn't many users configuring Caddy to load external certificates with DNS names that they don't actually want to use. You're working at an enterprise scale with this issue, you can either sponsor or contribute a fix if it's that valuable to the business or leverage caddy JSON config instead which is very reasonable if correcting the loaded certificate properly is out of the question. Caddy might not be able to serve you well in this case, and while that's unfortunate it can't be expected to cover everyone's niche requirements (just like nginx is lacking compared to caddy with some features). If another software works just as well for solving your problem that probably works out best for everyone when neither party can justify the cost to tackle it. |
For the record, your statement here about Caddy isn't accurate. |
Well, these requirements and the first three points of your solution are pretty common (depending on how you define "mesh network"). I'm not saying yours is an "edge case," but I will also say that having observed hundreds, or thousands, of deployments and configs and requirements by this point, I haven't seen a good/practical implementation like yours, because:
This is objectively not okay. I think too many people forget that encryption, without authentication, is just a game that, given enough time, only has one winner (and it's not you). That last point reads more like a problem, not a solution. So yes, I find it bizarre, and I'm baffled by it, because there is no good way to serve invalid certificates and create security.
Well, I would love to, but the key to solving the problem is being withheld:
and that's unfortunate, because I think there's a way forward otherwise.
Attackers love this mentality. Oh they LOVE this. I'm not saying that all connections on a private network need to be secured with TLS, but I am saying that if you're encrypting (but not authenticating) on said private network, you have an illusion of security/privacy, which is not healthy security posture. It's the kind of infrastructure I hope my data never flows throughout (but I know it does... alas, I got a notice of security breach affecting our data, from our health insurance company, just the other week).
Well, we have definitely considered this carefully over the last decade, and... decided that Caddy does nothing well that is new. Just kidding. Apples and oranges, but:
... I mean, there are many more. I don't know if you've seen our features page but you'd be hard pressed to find this set of capabilities in other web servers. I know nothing I write here will probably change your mind, but for posterity, it's good for me to put this out there.
I mean, sure, that's why we have metrics and opentelemetry integration. We have open issues for improving them (like 100 other things), so feel free to submit a PR! Anyway, if your company decides to become a Business-tier sponsor, we can work with your team in private, which seems to be necessary since the crucial parts are "internal to the company." We're standing by to help if you decide to! |
Expected Behaviour:
I want to use self-signed certificate for
arpit-test.msmartpay.in
domain only. I want caddy to automatically manage other domains.When i visit
arpit.msmartpay.in
, I should be presented with let's encrypt certificatewhen I visit
arpit-test.msmartpay.in
, I should be presented with self-signed certificateActual Behaviour:
Caddyfile:
domain.crt:
The text was updated successfully, but these errors were encountered: