httpcaddyfile: new tls_automate_names global option - #8015
IslamElsayed wants to merge 3 commits into
Conversation
Provisioning a certificate for a name that is not served requires giving
it a site block of its own:
*.example.com {
}
foo.example.com {
respond "Real site"
}
That asks the tls app for the wildcard, but it also adds a route to the
http app, so every name pointed at the server that has no site block of
its own -- bar.example.com here -- gets an empty but valid response
rather than no match at all. Wanting a certificate and wanting to serve
a name are separate things, and the Caddyfile had no way to say only the
first.
Name them in the new global option instead:
{
tls_automate_names *.example.com
}
foo.example.com {
respond "Real site"
}
The names are added to the automate certificate loader and to an
automation policy built from the global options, so a name listed here
is managed exactly as it would be from a site block, with the same
issuers; the only difference in the adapted config is that no route is
added for it. Repeating the option appends rather than replaces, so a
long list can be split over several lines.
Names that cannot get a public certificate are given the internal
issuer, the same treatment a site block gives them. Names that already
appear in the automate list are skipped, but a name that also has a site
block is left listed here as well: a site block for http:// only is not
managed by auto-HTTPS, so dropping the name because a block exists could
silently leave it without a certificate.
The option needs no http app at all, so a config consisting only of
global options now adapts to a tls app on its own -- enough to keep
certificates renewed for a mail or XMPP server, or for names served by a
layer 4 app.
Closes caddyserver#7122
A name given to tls_automate_names may already have an automation policy
from its own site block. Adding a second policy for the same subject is
not just redundant: the adapter rejects overlapping subjects, so
{
email nobody@example.com
tls_automate_names foo.example.com
}
foo.example.com {
tls {
ca https://acme.example.test/directory
}
}
failed to adapt at all, with "hostname appears in more than one
automation policy, making certificate management ambiguous".
Keep such a name in the automate loader but leave its policy alone. The
site block's policy is the more specific of the two, and the loader entry
is still wanted, since a site block served only over HTTP is not managed
by auto-HTTPS.
|
Good catch, and it was worse than redundant — that combination didn't adapt at all. Reproducing it on the previous commit: The adapter rejects overlapping subjects and my second policy collided with the site block's. Fixed in cef0148: a name that already appears as a subject of some automation policy keeps that policy, and is still added to the automate loader. Same comparison the overlap check itself uses, so the two can't disagree. The config above now adapts, with the site block's issuer intact: "certificates": { "automate": ["foo.example.com"] },
"automation": { "policies": [
{ "subjects": ["foo.example.com"], "issuers": [{"ca": "https://acme.example.test/directory", "email": "nobody@example.com", "module": "acme"}] }
]}Regression test added as
|
steadytao
left a comment
There was a problem hiding this comment.
LGTM. Just need to deliberate on the option name since ya jumped the gun a bit.
|
@mholt, are you happy with tls_automate_names, or do you still prefer Once the name is settled, please add the global-option documentation in caddyserver/website @IslamElsayed - preferably human written :/ but I suppose if you can at least get a shape we can always refine it. |
|
What about |
|
That sounds pretty good to me. Happy for it to be changed and docs PR to be submitted. |
|
I don't like |
|
Hmmmm that is also true, ill let you battle |
|
Thanks for the review. On the name — I'll implement whichever of the three you land on; it's a one-line change plus renaming four test files, so it costs nothing to decide late. One piece of data rather than an opinion, since @francislavoie's point is checkable: of the 41 currently registered global options, none contains a preposition.
On docs: yes, I'll open the PR to caddyserver/website. I'll write it myself rather than generate it, and I'd rather do that once the name is settled so the examples don't have to be rewritten — but say the word if you'd prefer a rough shape sooner to refine, and I'll put one up against whichever name is currently winning. @steadytao one thing from your review I want to make sure I read right: "jumped the gun" — if you mean I should have waited for the name before implementing, that's fair, and I'll hold the rename until you two have settled rather than guessing again. |
|
That is precisely what I meant. It allows us to prevent this kind of noise in PRs and keep it to issues where it generally belongs. Appreciate it. |
|
I still prefer |
|
@steadytao docs PR is up: caddyserver/website#570. It uses One thing in it I flagged there and will repeat: I wrote "(Requires Caddy 2.12 or newer.)" purely because v2.11.4 is the latest release. If this lands in some other version, that line needs correcting and I have no way to know which. |
|
The implementation still looks correct. However; please add one adaptation fixture covering |
The names given to the option are managed whether or not auto-HTTPS is disabled: unlike the hostless-key block above it, that code is not gated on auto_https, because naming a subject explicitly is a stronger signal than the general switch. Nothing enforced it, so add the adaptation fixture @steadytao asked for.
|
Added in 6c55051 — You were right that it needed pinning rather than changing: the option already overrides the disable. The hostless-key block above it is gated on "automatic_https": { "disable": true },
...
"tls": { "certificates": { "automate": ["mail.example.com"] } }I checked it actually guards that: adding the |
|
Website PR needs its updates but other then that; it may be worth including this in 2.12 alongside #7976 - @francislavoie, thoughts? |
|
Since it's a low impact new feature and not an API change I'm fine with adding it into 2.11.5 maybe. |
|
Works for me. |
The version note was a guess from v2.11.4 being the latest release. @francislavoie and @steadytao settled on 2.11.5 rather than 2.12 on caddyserver/caddy#8015, since the option is a low-impact addition and not an API change.
|
Thanks — 2.11.5 answers the one thing I had flagged as a guess in the docs PR. Updated it there in caddyserver/website@30a0dc0: the entry now reads "(Requires Caddy 2.11.5 or newer.)" instead of 2.12. @steadytao if "needs its updates" meant something beyond the version note, say what and I will fix it — that was the only open item I knew of on caddyserver/website#570. |
@steadytao's review: describing the option only as the management Automatic HTTPS provides hid the precedence. Listing a name is an explicit request, so it is still managed when auto_https is off or disable_certs, the same way the tls directive's force_automate forces automation for a site. Verified by adapting both: `auto_https off` and `auto_https disable_certs` each leave the name in the automate loader. caddyserver/caddy#8015 pins the first in tls_automate_names_auto_https_off.caddyfiletest.
Assistance Disclosure
I used AI assistance throughout this PR — for exploring the existing
tlsapp.gocode, drafting the implementation and tests, and writing this description. I verified all of it myself: I read the surrounding code to work out why an automation policy was needed (see below), generated every expected adapt output from a build and checked each one by hand against what the config should say, and confirmed the tests actually fail when the implementation is broken in four different ways. The behaviour claims below are measured, not assumed.Closes #7122.
The problem
Provisioning a certificate for a name you don't serve currently requires giving it a site block:
That gets the wildcard, but it also adds a route to the
httpapp, so any name pointed at the server without its own site block —bar.example.com— gets an empty-but-valid response instead of no match. Wanting a certificate and wanting to serve a name are separate things, and the Caddyfile had no way to say only the first.The change
Per @francislavoie's suggestion on the issue. Adapting the two configs above, the
tlsapp comes out identical — same policy, same subjects, same issuers — and the only difference is the route that is no longer there:"routes": [ { "match": [{"host": ["foo.example.com"]}], "handle": [...], "terminal": true }, - { "match": [{"host": ["*.example.com"]}], "terminal": true } ] ... "tls": { + "certificates": { "automate": ["*.example.com"] }, "automation": { "policies": [ { "subjects": ["foo.example.com", "*.example.com"], "issuers": [...] } ]} }It also works with no site blocks at all, which #7122 and @felixauringer's comment both ask for — enough to keep certs renewed for a mail or XMPP server, or for names served by a layer 4 app:
{"apps": {"tls": {"certificates": {"automate": ["mail.example.com", "xmpp.example.com"]}}}}Notes on the implementation
The names need an automation policy, not just the
automateloader. My first attempt only appended to the loader, and withemail nobody@example.comset the wildcard silently came out with no issuers — it would not have used the configured ACME account. The catch-all policy that would normally carry global options gets dropped once every other policy names its subjects, so these names need a policy of their own; consolidation then folds it back into an identical one. This is what thetls_automate_names.caddyfiletestcase pins, and it's the part most worth a careful look.A policy is only created when it would carry something. Otherwise an inert
{"subjects": [...]}with no issuers ends up in the output. I pulled the existinghasGlobalACMEDefaultsexpression out into a small helper to decide this, sincenewBaseAutomationPolicy's own "are there global options" check doesn't countemail.Names that can't get a public cert get the internal issuer, the same treatment a site block gives them.
A name that also has a site block stays listed here. It's redundant, and I'd initially planned to skip it (I said as much on the issue), but a site block for
http://only isn't managed by auto-HTTPS — dropping the name because some block exists could silently leave it without a certificate. Listing it twice is harmless; not listing it isn't. Happy to change this if you'd rather.Repeating the option appends rather than replaces, so a long list can be split over lines.
On the name
I went with
tls_automate_names(@francislavoie's suggestion) since @mholt'stls_certs/tls_certificatesquestion was still open. Renaming is a one-line change plus the test filenames — say the word.Testing
Three adapt tests: the issue's exact scenario, the no-site-block case, and internal/public name splitting with the option repeated. I checked they aren't vacuous by breaking the implementation four ways — dropping the internal split, never creating the policy, not adding to the loader, and making the parser overwrite instead of append — and confirming each mutation fails the tests that should catch it.
go test ./caddyconfig/... ./caddytest/...passes.TestACMEServerWithDefaultsis flaky on my machine, but equally so onmaster(2 of 4 runs failed there, 1 of 3 on this branch), so it looks unrelated.Docs for the new global option would need a PR to caddyserver/website — glad to write it once the name is settled.