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
}