@marcel91200 / caddy Mirror · read-only / commits / e16a8868
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 05:30 UTC

caddytls: Eval replacer on automation policy subjects (#5459)

Also renamed the field to SubjectsRaw, which can be considered a breaking change but I don't expect this to affect much.

Francis Lavoie committed Mar 27, 2023 at 17:16 UTC e16a886814d8cd43d545de38a4d6b98313fb31cb
6 files changed +58 -39
caddyconfig/httpcaddyfile/tlsapp.go
+19 -19
@@ -206,8 +206,8 @@ func (st ServerType) buildTLSApp(
206 }
207
208 // associate our new automation policy with this server block's hosts
209 - ap.Subjects = sblock.hostsFromKeysNotHTTP(httpPort)
210 - sort.Strings(ap.Subjects) // solely for deterministic test results
209 + ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
210 + sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
211
212 // if a combination of public and internal names were given
213 // for this same server block and no issuer was specified, we
@@ -217,7 +217,7 @@ func (st ServerType) buildTLSApp(
217 var ap2 *caddytls.AutomationPolicy
218 if len(ap.Issuers) == 0 {
219 var internal, external []string
220 - for _, s := range ap.Subjects {
220 + for _, s := range ap.SubjectsRaw {
221 if !certmagic.SubjectQualifiesForCert(s) {
222 return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
223 }
@@ -235,10 +235,10 @@ func (st ServerType) buildTLSApp(
235 }
236 }
237 if len(external) > 0 && len(internal) > 0 {
238 - ap.Subjects = external
238 + ap.SubjectsRaw = external
239 apCopy := *ap
240 ap2 = &apCopy
241 - ap2.Subjects = internal
241 + ap2.SubjectsRaw = internal
242 ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
243 }
244 }
@@ -339,14 +339,14 @@ func (st ServerType) buildTLSApp(
339 for h := range httpsHostsSharedWithHostlessKey {
340 al = append(al, h)
341 if !certmagic.SubjectQualifiesForPublicCert(h) {
342 - internalAP.Subjects = append(internalAP.Subjects, h)
342 + internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h)
343 }
344 }
345 }
346 if len(al) > 0 {
347 tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
348 }
349 - if len(internalAP.Subjects) > 0 {
349 + if len(internalAP.SubjectsRaw) > 0 {
350 if tlsApp.Automation == nil {
351 tlsApp.Automation = new(caddytls.AutomationConfig)
352 }
@@ -412,7 +412,7 @@ func (st ServerType) buildTLSApp(
412 // for convenience)
413 automationHostSet := make(map[string]struct{})
414 for _, ap := range tlsApp.Automation.Policies {
415 - for _, s := range ap.Subjects {
415 + for _, s := range ap.SubjectsRaw {
416 if _, ok := automationHostSet[s]; ok {
417 return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
418 }
@@ -533,7 +533,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
533 if automationPolicyIsSubset(aps[j], aps[i]) {
534 return false
535 }
536 - return len(aps[i].Subjects) > len(aps[j].Subjects)
536 + return len(aps[i].SubjectsRaw) > len(aps[j].SubjectsRaw)
537 })
538
539 emptyAPCount := 0
@@ -541,7 +541,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
541 // compute the number of empty policies (disregarding subjects) - see #4128
542 emptyAP := new(caddytls.AutomationPolicy)
543 for i := 0; i < len(aps); i++ {
544 - emptyAP.Subjects = aps[i].Subjects
544 + emptyAP.SubjectsRaw = aps[i].SubjectsRaw
545 if reflect.DeepEqual(aps[i], emptyAP) {
546 emptyAPCount++
547 if !automationPolicyHasAllPublicNames(aps[i]) {
@@ -583,7 +583,7 @@ outer:
583 aps[i].KeyType == aps[j].KeyType &&
584 aps[i].OnDemand == aps[j].OnDemand &&
585 aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
586 - if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
586 + if len(aps[i].SubjectsRaw) > 0 && len(aps[j].SubjectsRaw) == 0 {
587 // later policy (at j) has no subjects ("catch-all"), so we can
588 // remove the identical-but-more-specific policy that comes first
589 // AS LONG AS it is not shadowed by another policy before it; e.g.
@@ -598,9 +598,9 @@ outer:
598 }
599 } else {
600 // avoid repeated subjects
601 - for _, subj := range aps[j].Subjects {
602 - if !sliceContains(aps[i].Subjects, subj) {
603 - aps[i].Subjects = append(aps[i].Subjects, subj)
601 + for _, subj := range aps[j].SubjectsRaw {
602 + if !sliceContains(aps[i].SubjectsRaw, subj) {
603 + aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
604 }
605 }
606 aps = append(aps[:j], aps[j+1:]...)
@@ -616,15 +616,15 @@ outer:
616 // automationPolicyIsSubset returns true if a's subjects are a subset
617 // of b's subjects.
618 func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
619 - if len(b.Subjects) == 0 {
619 + if len(b.SubjectsRaw) == 0 {
620 return true
621 }
622 - if len(a.Subjects) == 0 {
622 + if len(a.SubjectsRaw) == 0 {
623 return false
624 }
625 - for _, aSubj := range a.Subjects {
625 + for _, aSubj := range a.SubjectsRaw {
626 var inSuperset bool
627 - for _, bSubj := range b.Subjects {
627 + for _, bSubj := range b.SubjectsRaw {
628 if certmagic.MatchWildcard(aSubj, bSubj) {
629 inSuperset = true
630 break
@@ -662,7 +662,7 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
662 }
663
664 func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
665 - for _, subj := range ap.Subjects {
665 + for _, subj := range ap.SubjectsRaw {
666 if !subjectQualifiesForPublicCert(ap, subj) {
667 return false
668 }
caddyconfig/httpcaddyfile/tlsapp_test.go
+2 -2
@@ -47,8 +47,8 @@ func TestAutomationPolicyIsSubset(t *testing.T) {
47 expect: false,
48 },
49 } {
50 - apA := &caddytls.AutomationPolicy{Subjects: test.a}
51 - apB := &caddytls.AutomationPolicy{Subjects: test.b}
50 + apA := &caddytls.AutomationPolicy{SubjectsRaw: test.a}
51 + apB := &caddytls.AutomationPolicy{SubjectsRaw: test.b}
52 if actual := automationPolicyIsSubset(apA, apB); actual != test.expect {
53 t.Errorf("Test %d: Expected %t but got %t (A: %v B: %v)", i, test.expect, actual, test.a, test.b)
54 }
modules/caddyhttp/autohttps.go
+3 -3
@@ -285,7 +285,7 @@ uniqueDomainsLoop:
285 // one automation policy would be confusing and an error
286 if app.tlsApp.Automation != nil {
287 for _, ap := range app.tlsApp.Automation.Policies {
288 - for _, apHost := range ap.Subjects {
288 + for _, apHost := range ap.Subjects() {
289 if apHost == d {
290 continue uniqueDomainsLoop
291 }
@@ -518,7 +518,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
518 }
519
520 // while we're here, is this the catch-all/base policy?
521 - if !foundBasePolicy && len(ap.Subjects) == 0 {
521 + if !foundBasePolicy && len(ap.SubjectsRaw) == 0 {
522 basePolicy = ap
523 foundBasePolicy = true
524 }
@@ -634,7 +634,7 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
634 // rather they just want to change the CA for the set
635 // of names that would normally use the production API;
636 // anyway, that gets into the weeds a bit...
637 - newPolicy.Subjects = internalNames
637 + newPolicy.SubjectsRaw = internalNames
638 newPolicy.Issuers = []certmagic.Issuer{internalIssuer}
639 err := app.tlsApp.AddAutomationPolicy(newPolicy)
640 if err != nil {
modules/caddyhttp/reverseproxy/command.go
+2 -2
@@ -259,8 +259,8 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
259 tlsApp := caddytls.TLS{
260 Automation: &caddytls.AutomationConfig{
261 Policies: []*caddytls.AutomationPolicy{{
262 - Subjects: []string{fromAddr.Host},
263 - IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
262 + SubjectsRaw: []string{fromAddr.Host},
263 + IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
264 }},
265 },
266 }
modules/caddytls/automation.go
+19 -5
@@ -85,7 +85,7 @@ type AutomationConfig struct {
85 // TLS app to properly provision a new policy.
86 type AutomationPolicy struct {
87 // Which subjects (hostnames or IP addresses) this policy applies to.
88 - Subjects []string `json:"subjects,omitempty"`
88 + SubjectsRaw []string `json:"subjects,omitempty"`
89
90 // The modules that may issue certificates. Default: internal if all
91 // subjects do not qualify for public certificates; othewise acme and
@@ -147,12 +147,21 @@ type AutomationPolicy struct {
147 Issuers []certmagic.Issuer `json:"-"`
148 Managers []certmagic.Manager `json:"-"`
149
150 - magic *certmagic.Config
151 - storage certmagic.Storage
150 + subjects []string
151 + magic *certmagic.Config
152 + storage certmagic.Storage
153 }
154
155 // Provision sets up ap and builds its underlying CertMagic config.
156 func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
157 + // replace placeholders in subjects to allow environment variables
158 + repl := caddy.NewReplacer()
159 + subjects := make([]string, len(ap.SubjectsRaw))
160 + for i, sub := range ap.SubjectsRaw {
161 + subjects[i] = repl.ReplaceAll(sub, "")
162 + }
163 + ap.subjects = subjects
164 +
165 // policy-specific storage implementation
166 if ap.StorageRaw != nil {
167 val, err := tlsApp.ctx.LoadModule(ap, "StorageRaw")
@@ -289,6 +298,11 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
298 return nil
299 }
300
301 +// Subjects returns the list of subjects with all placeholders replaced.
302 +func (ap *AutomationPolicy) Subjects() []string {
303 + return ap.subjects
304 +}
305 +
306 func (ap *AutomationPolicy) onlyInternalIssuer() bool {
307 if len(ap.Issuers) != 1 {
308 return false
@@ -301,10 +315,10 @@ func (ap *AutomationPolicy) onlyInternalIssuer() bool {
315 // or is the "default" policy (i.e. no subjects) which is unbounded.
316 func (ap *AutomationPolicy) isWildcardOrDefault() bool {
317 isWildcardOrDefault := false
304 - if len(ap.Subjects) == 0 {
318 + if len(ap.subjects) == 0 {
319 isWildcardOrDefault = true
320 }
307 - for _, sub := range ap.Subjects {
321 + for _, sub := range ap.subjects {
322 if strings.HasPrefix(sub, "*") {
323 isWildcardOrDefault = true
324 break
modules/caddytls/tls.go
+13 -8
@@ -126,7 +126,12 @@ func (t *TLS) Provision(ctx caddy.Context) error {
126 // special case; these will be loaded in later using our automation facilities,
127 // which we want to avoid doing during provisioning
128 if automateNames, ok := modIface.(*AutomateLoader); ok && automateNames != nil {
129 - t.automateNames = []string(*automateNames)
129 + repl := caddy.NewReplacer()
130 + subjects := make([]string, len(*automateNames))
131 + for i, sub := range *automateNames {
132 + subjects[i] = repl.ReplaceAll(sub, "")
133 + }
134 + t.automateNames = subjects
135 } else {
136 return fmt.Errorf("loading certificates with 'automate' requires array of strings, got: %T", modIface)
137 }
@@ -231,13 +236,13 @@ func (t *TLS) Validate() error {
236 var hasDefault bool
237 hostSet := make(map[string]int)
238 for i, ap := range t.Automation.Policies {
234 - if len(ap.Subjects) == 0 {
239 + if len(ap.subjects) == 0 {
240 if hasDefault {
241 return fmt.Errorf("automation policy %d is the second policy that acts as default/catch-all, but will never be used", i)
242 }
243 hasDefault = true
244 }
240 - for _, h := range ap.Subjects {
245 + for _, h := range ap.subjects {
246 if first, ok := hostSet[h]; ok {
247 return fmt.Errorf("automation policy %d: cannot apply more than one automation policy to host: %s (first match in policy %d)", i, h, first)
248 }
@@ -388,8 +393,8 @@ func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
393 // first see if existing is superset of ap for all names
394 var otherIsSuperset bool
395 outer:
391 - for _, thisSubj := range ap.Subjects {
392 - for _, otherSubj := range existing.Subjects {
396 + for _, thisSubj := range ap.subjects {
397 + for _, otherSubj := range existing.subjects {
398 if certmagic.MatchWildcard(thisSubj, otherSubj) {
399 otherIsSuperset = true
400 break outer
@@ -398,7 +403,7 @@ func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
403 }
404 // if existing AP is a superset or if it contains fewer names (i.e. is
405 // more general), then new AP is more specific, so insert before it
401 - if otherIsSuperset || len(existing.Subjects) < len(ap.Subjects) {
406 + if otherIsSuperset || len(existing.SubjectsRaw) < len(ap.SubjectsRaw) {
407 t.Automation.Policies = append(t.Automation.Policies[:i],
408 append([]*AutomationPolicy{ap}, t.Automation.Policies[i:]...)...)
409 return nil
@@ -420,10 +425,10 @@ func (t *TLS) getConfigForName(name string) *certmagic.Config {
425 // public certificate or not.
426 func (t *TLS) getAutomationPolicyForName(name string) *AutomationPolicy {
427 for _, ap := range t.Automation.Policies {
423 - if len(ap.Subjects) == 0 {
428 + if len(ap.subjects) == 0 {
429 return ap // no host filter is an automatic match
430 }
426 - for _, h := range ap.Subjects {
431 + for _, h := range ap.subjects {
432 if certmagic.MatchWildcard(name, h) {
433 return ap
434 }