* httpcaddyfile: Revise automation policy generation
This should fix a frustrating edge case where wildcard subjects are
used, which potentially get shadowed by more specific versions of
themselves; see the new tests for an example. This change is motivated
by an actual customer requirement.
Although all the tests pass, this logic is incredibly complex and
nuanced, and I'm worried it is not correct. But it took me about 4 days
to get this far on a solution. I did my best.
* Fix typo
returnnil,warnings,fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v",ap.Issuer,issuer)
returnnil,warnings,fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v",ap.Issuer,issuer)
// associated automation policy to have an empty Subjects list, i.e. no
// host filter, which is indistinguishable between the two server blocks
// because automation is not done in the context of a particular server...
// this is an example of a poor mapping from Caddyfile to JSON but that's
// the least-leaky abstraction I could figure out
iflen(sblockHosts)==0{
ifserverBlocksWithTLSHostlessKey>1{
// this server block and at least one other has a key with no host,
// making the two indistinguishable; it is misleading to define such
// a policy within one server block since it actually will apply to
// others as well
returnnil,warnings,fmt.Errorf("cannot make a TLS automation policy from a server block that has a host-less address when there are other TLS-enabled server block addresses lacking a host")
}
}
ifcatchAllAP==nil{
// first make sure this block is allowed to create an automation policy;
// this server block has a key with no hosts, but there is not yet
// doing so is forbidden if it has a key with no host (i.e. ":443")
// a catch-all automation policy (probably because no global options
// and if there is a different server block that also has a key with no
// were set), so this one becomes it
// host -- since a key with no host matches any host, we need its
catchAllAP=ap
// associated automation policy to have an empty Subjects list, i.e. no
// host filter, which is indistinguishable between the two server blocks
// because automation is not done in the context of a particular server...
// this is an example of a poor mapping from Caddyfile to JSON but that's
// the least-leaky abstraction I could figure out
iflen(sblockHosts)==0{
ifserverBlocksWithTLSHostlessKey>1{
// this server block and at least one other has a key with no host,
// making the two indistinguishable; it is misleading to define such
// a policy within one server block since it actually will apply to
// others as well
returnnil,warnings,fmt.Errorf("cannot make a TLS automation policy from a server block that has a host-less address when there are other TLS-enabled server block addresses lacking a host")
}
ifcatchAllAP==nil{
// this server block has a key with no hosts, but there is not yet
// a catch-all automation policy (probably because no global options
// were set), so this one becomes it
catchAllAP=ap
}
}
}
}
// associate our new automation policy with this server block's hosts,
// associate our new automation policy with this server block's hosts
// unless, of course, the server block has a key with no hosts, in which
ap.Subjects=sblockHosts
// case its automation policy becomes or blends with the default/global
sort.Strings(ap.Subjects)// solely for deterministic test results
// automation policy because, of necessity, it applies to all hostnames
// (i.e. it has no Subjects filter) -- in that case, we'll append it last
// if a combination of public and internal names were given
ifap!=catchAllAP{
// for this same server block and no issuer was specified, we
ap.Subjects=sblockHosts
// need to separate them out in the automation policies so
// that the internal names can use the internal issuer and
// if a combination of public and internal names were given
// the other names can use the default/public/ACME issuer
// for this same server block and no issuer was specified, we
varap2*caddytls.AutomationPolicy
// need to separate them out in the automation policies so
ifap.Issuer==nil{
// that the internal names can use the internal issuer and
varinternal,external[]string
// the other names can use the default/public/ACME issuer
for_,s:=rangeap.Subjects{
varap2*caddytls.AutomationPolicy
if!certmagic.SubjectQualifiesForCert(s){
ifap.Issuer==nil{
returnnil,warnings,fmt.Errorf("subject does not qualify for certificate: '%s'",s)
varinternal,external[]string
for_,s:=rangeap.Subjects{
if!certmagic.SubjectQualifiesForCert(s){
returnnil,warnings,fmt.Errorf("subject does not qualify for certificate: '%s'",s)
}
// we don't use certmagic.SubjectQualifiesForPublicCert() because of one nuance:
// names like *.*.tld that may not qualify for a public certificate are actually
// fine when used with OnDemand, since OnDemand (currently) does not obtain
// wildcards (if it ever does, there will be a separate config option to enable
// it that we would need to check here) since the hostname is known at handshake;
// and it is unexpected to switch to internal issuer when the user wants to get
// regular certificates on-demand for a class of certs like *.*.tld.