@ -76,30 +76,35 @@ func (st ServerType) buildTLSApp(
}
}
}
}
// a catch-all automation policy is used as a "default" for all subjects that
// don't have custom configuration explicitly associated with them; this
// is only to add if the global settings or defaults are non-empty
catchAllAP , err := newBaseAutomationPolicy ( options , warnings , false )
catchAllAP , err := newBaseAutomationPolicy ( options , warnings , false )
if err != nil {
if err != nil {
return nil , warnings , err
return nil , warnings , err
}
}
if catchAllAP != nil {
if tlsApp . Automation == nil {
tlsApp . Automation = new ( caddytls . AutomationConfig )
}
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , catchAllAP )
}
for _ , p := range pairings {
for _ , p := range pairings {
for _ , sblock := range p . serverBlocks {
for _ , sblock := range p . serverBlocks {
// get values that populate an automation policy for this block
// get values that populate an automation policy for this block
var ap * caddytls . AutomationPolicy
ap , err := newBaseAutomationPolicy ( options , warnings , true )
if err != nil {
return nil , warnings , err
}
sblockHosts := sblock . hostsFromKeys ( false )
sblockHosts := sblock . hostsFromKeys ( false )
if len ( sblockHosts ) == 0 {
if len ( sblockHosts ) == 0 && catchAllAP != nil {
ap = catchAllAP
ap = catchAllAP
}
}
// on-demand tls
// on-demand tls
if _ , ok := sblock . pile [ "tls.on_demand" ] ; ok {
if _ , ok := sblock . pile [ "tls.on_demand" ] ; ok {
if ap == nil {
var err error
ap , err = newBaseAutomationPolicy ( options , warnings , true )
if err != nil {
return nil , warnings , err
}
}
ap . OnDemand = true
ap . OnDemand = true
}
}
@ -107,13 +112,6 @@ func (st ServerType) buildTLSApp(
if issuerVals , ok := sblock . pile [ "tls.cert_issuer" ] ; ok {
if issuerVals , ok := sblock . pile [ "tls.cert_issuer" ] ; ok {
for _ , issuerVal := range issuerVals {
for _ , issuerVal := range issuerVals {
issuer := issuerVal . Value . ( certmagic . Issuer )
issuer := issuerVal . Value . ( certmagic . Issuer )
if ap == nil {
var err error
ap , err = newBaseAutomationPolicy ( options , warnings , true )
if err != nil {
return nil , warnings , err
}
}
if ap == catchAllAP && ! reflect . DeepEqual ( ap . Issuer , issuer ) {
if ap == catchAllAP && ! reflect . DeepEqual ( ap . Issuer , issuer ) {
return nil , 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 )
return nil , 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 )
}
}
@ -123,15 +121,6 @@ func (st ServerType) buildTLSApp(
// custom bind host
// custom bind host
for _ , cfgVal := range sblock . pile [ "bind" ] {
for _ , cfgVal := range sblock . pile [ "bind" ] {
// either an existing issuer is already configured (and thus, ap is not
// nil), or we need to configure an issuer, so we need ap to be non-nil
if ap == nil {
ap , err = newBaseAutomationPolicy ( options , warnings , true )
if err != nil {
return nil , warnings , err
}
}
// if an issuer was already configured and it is NOT an ACME
// if an issuer was already configured and it is NOT an ACME
// issuer, skip, since we intend to adjust only ACME issuers
// issuer, skip, since we intend to adjust only ACME issuers
var acmeIssuer * caddytls . ACMEIssuer
var acmeIssuer * caddytls . ACMEIssuer
@ -164,13 +153,6 @@ func (st ServerType) buildTLSApp(
ap . Issuer = acmeIssuer // we'll encode it later
ap . Issuer = acmeIssuer // we'll encode it later
}
}
if ap != nil {
if ap . Issuer != nil {
// encode issuer now that it's all set up
issuerName := ap . Issuer . ( caddy . Module ) . CaddyModule ( ) . ID . Name ( )
ap . IssuerRaw = caddyconfig . JSONModuleObject ( ap . Issuer , "module" , issuerName , & warnings )
}
// first make sure this block is allowed to create an automation policy;
// first make sure this block is allowed to create an automation policy;
// doing so is forbidden if it has a key with no host (i.e. ":443")
// doing so is forbidden if it has a key with no host (i.e. ":443")
// and if there is a different server block that also has a key with no
// and if there is a different server block that also has a key with no
@ -196,13 +178,9 @@ func (st ServerType) buildTLSApp(
}
}
}
}
// 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
// case its automation policy becomes or blends with the default/global
// 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 ap != catchAllAP {
ap . Subjects = sblockHosts
ap . Subjects = sblockHosts
sort . Strings ( ap . Subjects ) // solely for deterministic test results
// if a combination of public and internal names were given
// if a combination of public and internal names were given
// for this same server block and no issuer was specified, we
// for this same server block and no issuer was specified, we
@ -244,8 +222,6 @@ func (st ServerType) buildTLSApp(
if ap2 != nil {
if ap2 != nil {
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , ap2 )
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , ap2 )
}
}
}
}
// certificate loaders
// certificate loaders
if clVals , ok := sblock . pile [ "tls.cert_loader" ] ; ok {
if clVals , ok := sblock . pile [ "tls.cert_loader" ] ; ok {
@ -319,23 +295,17 @@ func (st ServerType) buildTLSApp(
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , internalAP )
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , internalAP )
}
}
// if there is a global/catch-all automation policy, ensure it goes last
// finalize and verify policies; do cleanup
if catchAllAP != nil {
if tlsApp . Automation != nil {
// first, encode its issuer, if there is one
// encode any issuer values we created, so they will be rendered in the output
if catchAllAP . Issuer != nil {
for _ , ap := range tlsApp . Automation . Policies {
issuerName := catchAllAP . Issuer . ( caddy . Module ) . CaddyModule ( ) . ID . Name ( )
if ap . Issuer != nil && ap . IssuerRaw == nil {
catchAllAP . IssuerRaw = caddyconfig . JSONModuleObject ( catchAllAP . Issuer , "module" , issuerName , & warnings )
// encode issuer now that it's all set up
}
issuerName := ap . Issuer . ( caddy . Module ) . CaddyModule ( ) . ID . Name ( )
ap . IssuerRaw = caddyconfig . JSONModuleObject ( ap . Issuer , "module" , issuerName , & warnings )
// then append it to the end of the policies list
if tlsApp . Automation == nil {
tlsApp . Automation = new ( caddytls . AutomationConfig )
}
}
tlsApp . Automation . Policies = append ( tlsApp . Automation . Policies , catchAllAP )
}
}
// do a little verification & cleanup
if tlsApp . Automation != nil {
// consolidate automation policies that are the exact same
// consolidate automation policies that are the exact same
tlsApp . Automation . Policies = consolidateAutomationPolicies ( tlsApp . Automation . Policies )
tlsApp . Automation . Policies = consolidateAutomationPolicies ( tlsApp . Automation . Policies )
@ -351,6 +321,14 @@ func (st ServerType) buildTLSApp(
automationHostSet [ s ] = struct { } { }
automationHostSet [ s ] = struct { } { }
}
}
}
}
// if nothing remains, remove any excess values to clean up the resulting config
if len ( tlsApp . Automation . Policies ) == 0 {
tlsApp . Automation . Policies = nil
}
if reflect . DeepEqual ( tlsApp . Automation , new ( caddytls . AutomationConfig ) ) {
tlsApp . Automation = nil
}
}
}
return tlsApp , warnings , nil
return tlsApp , warnings , nil
@ -448,12 +426,30 @@ func disambiguateACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) certmagic.Issuer {
// consolidateAutomationPolicies combines automation policies that are the same,
// consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output.
// for a cleaner overall output.
func consolidateAutomationPolicies ( aps [ ] * caddytls . AutomationPolicy ) [ ] * caddytls . AutomationPolicy {
func consolidateAutomationPolicies ( aps [ ] * caddytls . AutomationPolicy ) [ ] * caddytls . AutomationPolicy {
// sort from most specific to least specific; we depend on this ordering
sort . SliceStable ( aps , func ( i , j int ) bool {
if automationPolicyIsSubset ( aps [ i ] , aps [ j ] ) {
return true
}
if automationPolicyIsSubset ( aps [ j ] , aps [ i ] ) {
return false
}
return len ( aps [ i ] . Subjects ) > len ( aps [ j ] . Subjects )
} )
// remove any empty policies (except subjects, of course)
emptyAP := new ( caddytls . AutomationPolicy )
for i := 0 ; i < len ( aps ) ; i ++ {
for i := 0 ; i < len ( aps ) ; i ++ {
for j := 0 ; j < len ( aps ) ; j ++ {
emptyAP . Subjects = aps [ i ] . Subjects
if j == i {
if reflect . DeepEqual ( aps [ i ] , emptyAP ) {
continue
aps = append ( aps [ : i ] , aps [ i + 1 : ] ... )
i --
}
}
}
// remove or combine duplicate policies
for i := 0 ; i < len ( aps ) ; i ++ {
for j := i + 1 ; j < len ( aps ) ; j ++ {
// if they're exactly equal in every way, just keep one of them
// if they're exactly equal in every way, just keep one of them
if reflect . DeepEqual ( aps [ i ] , aps [ j ] ) {
if reflect . DeepEqual ( aps [ i ] , aps [ j ] ) {
aps = append ( aps [ : j ] , aps [ j + 1 : ] ... )
aps = append ( aps [ : j ] , aps [ j + 1 : ] ... )
@ -473,10 +469,17 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
aps [ i ] . KeyType == aps [ j ] . KeyType &&
aps [ i ] . KeyType == aps [ j ] . KeyType &&
aps [ i ] . OnDemand == aps [ j ] . OnDemand &&
aps [ i ] . OnDemand == aps [ j ] . OnDemand &&
aps [ i ] . RenewalWindowRatio == aps [ j ] . RenewalWindowRatio {
aps [ i ] . RenewalWindowRatio == aps [ j ] . RenewalWindowRatio {
if len ( aps [ i ] . Subjects ) == 0 && len ( aps [ j ] . Subjects ) > 0 {
if len ( aps [ i ] . Subjects ) > 0 && len ( aps [ j ] . Subjects ) == 0 {
aps = append ( aps [ : j ] , aps [ j + 1 : ] ... )
// later policy (at j) has no subjects ("catch-all"), so we can
} else if len ( aps [ i ] . Subjects ) > 0 && len ( aps [ j ] . Subjects ) == 0 {
// remove the identical-but-more-specific policy that comes first
// AS LONG AS it is not shadowed by another policy before it; e.g.
// if policy i is for example.com, policy i+1 is '*.com', and policy
// j is catch-all, we cannot remove policy i because that would
// cause example.com to be served by the less specific policy for
// '*.com', which might be different (yes we've seen this happen)
if automationPolicyShadows ( i , aps ) >= j {
aps = append ( aps [ : i ] , aps [ i + 1 : ] ... )
aps = append ( aps [ : i ] , aps [ i + 1 : ] ... )
}
} else {
} else {
// avoid repeated subjects
// avoid repeated subjects
for _ , subj := range aps [ j ] . Subjects {
for _ , subj := range aps [ j ] . Subjects {
@ -486,16 +489,48 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
}
}
aps = append ( aps [ : j ] , aps [ j + 1 : ] ... )
aps = append ( aps [ : j ] , aps [ j + 1 : ] ... )
}
}
i --
break
}
}
}
}
}
}
// ensure any catch-all policies go last
sort . SliceStable ( aps , func ( i , j int ) bool {
return len ( aps [ i ] . Subjects ) > len ( aps [ j ] . Subjects )
} )
return aps
return aps
}
}
// automationPolicyIsSubset returns true if a's subjects are a subset
// of b's subjects.
func automationPolicyIsSubset ( a , b * caddytls . AutomationPolicy ) bool {
if len ( b . Subjects ) == 0 {
return true
}
if len ( a . Subjects ) == 0 {
return false
}
for _ , aSubj := range a . Subjects {
var inSuperset bool
for _ , bSubj := range b . Subjects {
if certmagic . MatchWildcard ( aSubj , bSubj ) {
inSuperset = true
break
}
}
if ! inSuperset {
return false
}
}
return true
}
// automationPolicyShadows returns the index of a policy that aps[i] shadows;
// in other words, for all policies after position i, if that policy covers
// the same subjects but is less specific, that policy's position is returned,
// or -1 if no shadowing is found. For example, if policy i is for
// "foo.example.com" and policy i+2 is for "*.example.com", then i+2 will be
// returned, since that policy is shadowed by i, which is in front.
func automationPolicyShadows ( i int , aps [ ] * caddytls . AutomationPolicy ) int {
for j := i + 1 ; j < len ( aps ) ; j ++ {
if automationPolicyIsSubset ( aps [ i ] , aps [ j ] ) {
return j
}
}
return - 1
}