Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 04:30 UTC
httpcaddyfile: Implement experimental `force_automate` option (#6712)
Francis Lavoie committed
Dec 24, 2024 at 10:58 UTC
afa778ae05503f563af0d1015cdf7e5e78b1eeec
5 files changed
+334
-3
caddyconfig/httpcaddyfile/builtins.go
+15
-2
@@ -84,7 +84,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
84
85
// parseTLS parses the tls directive. Syntax:
86
//
87
-// tls [<email>|internal]|[<cert_file> <key_file>] {
87
+// tls [<email>|internal|force_automate]|[<cert_file> <key_file>] {
88
// protocols <min> [<max>]
89
// ciphers <cipher_suites...>
90
// curves <curves...>
@@ -107,6 +107,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
107
// dns_challenge_override_domain <domain>
108
// on_demand
109
// reuse_private_keys
110
+// force_automate
111
// eab <key_id> <mac_key>
112
// issuer <module_name> [...]
113
// get_certificate <module_name> [...]
@@ -126,6 +127,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
127
var certManagers []certmagic.Manager
128
var onDemand bool
129
var reusePrivateKeys bool
130
+ var forceAutomate bool
131
132
firstLine := h.RemainingArgs()
133
switch len(firstLine) {
@@ -133,8 +135,10 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
135
case 1:
136
if firstLine[0] == "internal" {
137
internalIssuer = new(caddytls.InternalIssuer)
138
+ } else if firstLine[0] == "force_automate" {
139
+ forceAutomate = true
140
} else if !strings.Contains(firstLine[0], "@") {
137
- return nil, h.Err("single argument must either be 'internal' or an email address")
141
+ return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address")
142
} else {
143
acmeIssuer = &caddytls.ACMEIssuer{
144
Email: firstLine[0],
@@ -569,6 +573,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
573
})
574
}
575
576
+ // if enabled, the names in the site addresses will be
577
+ // added to the automation policies
578
+ if forceAutomate {
579
+ configVals = append(configVals, ConfigValue{
580
+ Class: "tls.force_automate",
581
+ Value: true,
582
+ })
583
+ }
584
+
585
// custom certificate selection
586
if len(certSelector.AnyTag) > 0 {
587
cp.CertSelection = &certSelector
caddyconfig/httpcaddyfile/httptype.go
+21
-1
@@ -763,6 +763,14 @@ func (st *ServerType) serversFromPairings(
763
}
764
}
765
766
+ // collect hosts that are forced to be automated
767
+ forceAutomatedNames := make(map[string]struct{})
768
+ if _, ok := sblock.pile["tls.force_automate"]; ok {
769
+ for _, host := range hosts {
770
+ forceAutomatedNames[host] = struct{}{}
771
+ }
772
+ }
773
+
774
// tls: connection policies
775
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
776
// tls connection policies
@@ -794,7 +802,7 @@ func (st *ServerType) serversFromPairings(
802
}
803
804
// only append this policy if it actually changes something
797
- if !cp.SettingsEmpty() {
805
+ if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
806
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
807
hasCatchAllTLSConnPolicy = len(hosts) == 0
808
}
@@ -1661,6 +1669,18 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool {
1669
return false
1670
}
1671
1672
+func mapContains[K comparable, V any](m map[K]V, keys []K) bool {
1673
+ if len(m) == 0 || len(keys) == 0 {
1674
+ return false
1675
+ }
1676
+ for _, key := range keys {
1677
+ if _, ok := m[key]; ok {
1678
+ return true
1679
+ }
1680
+ }
1681
+ return false
1682
+}
1683
+
1684
// specificity returns len(s) minus any wildcards (*) and
1685
// placeholders ({...}). Basically, it's a length count
1686
// that penalizes the use of wildcards and placeholders.
caddyconfig/httpcaddyfile/tlsapp.go
+16
@@ -94,6 +94,9 @@ func (st ServerType) buildTLSApp(
94
95
// collect all hosts that have a wildcard in them, and arent HTTP
96
wildcardHosts := []string{}
97
+ // hosts that have been explicitly marked to be automated,
98
+ // even if covered by another wildcard
99
+ forcedAutomatedNames := make(map[string]struct{})
100
for _, p := range pairings {
101
var addresses []string
102
for _, addressWithProtocols := range p.addressesWithProtocols {
@@ -150,6 +153,13 @@ func (st ServerType) buildTLSApp(
153
ap.OnDemand = true
154
}
155
156
+ // collect hosts that are forced to be automated
157
+ if _, ok := sblock.pile["tls.force_automate"]; ok {
158
+ for _, host := range sblockHosts {
159
+ forcedAutomatedNames[host] = struct{}{}
160
+ }
161
+ }
162
+
163
// reuse private keys tls
164
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
165
ap.ReusePrivateKeys = true
@@ -407,6 +417,12 @@ func (st ServerType) buildTLSApp(
417
}
418
}
419
}
420
+ for name := range forcedAutomatedNames {
421
+ if slices.Contains(al, name) {
422
+ continue
423
+ }
424
+ al = append(al, name)
425
+ }
426
if len(al) > 0 {
427
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
428
}
caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest
new
+180
@@ -0,0 +1,180 @@
1
+automated1.example.com {
2
+ tls force_automate
3
+ respond "Automated!"
4
+}
5
+
6
+automated2.example.com {
7
+ tls force_automate
8
+ respond "Automated!"
9
+}
10
+
11
+shadowed.example.com {
12
+ respond "Shadowed!"
13
+}
14
+
15
+*.example.com {
16
+ tls cert.pem key.pem
17
+ respond "Wildcard!"
18
+}
19
+----------
20
+{
21
+ "apps": {
22
+ "http": {
23
+ "servers": {
24
+ "srv0": {
25
+ "listen": [
26
+ ":443"
27
+ ],
28
+ "routes": [
29
+ {
30
+ "match": [
31
+ {
32
+ "host": [
33
+ "automated1.example.com"
34
+ ]
35
+ }
36
+ ],
37
+ "handle": [
38
+ {
39
+ "handler": "subroute",
40
+ "routes": [
41
+ {
42
+ "handle": [
43
+ {
44
+ "body": "Automated!",
45
+ "handler": "static_response"
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "terminal": true
53
+ },
54
+ {
55
+ "match": [
56
+ {
57
+ "host": [
58
+ "automated2.example.com"
59
+ ]
60
+ }
61
+ ],
62
+ "handle": [
63
+ {
64
+ "handler": "subroute",
65
+ "routes": [
66
+ {
67
+ "handle": [
68
+ {
69
+ "body": "Automated!",
70
+ "handler": "static_response"
71
+ }
72
+ ]
73
+ }
74
+ ]
75
+ }
76
+ ],
77
+ "terminal": true
78
+ },
79
+ {
80
+ "match": [
81
+ {
82
+ "host": [
83
+ "shadowed.example.com"
84
+ ]
85
+ }
86
+ ],
87
+ "handle": [
88
+ {
89
+ "handler": "subroute",
90
+ "routes": [
91
+ {
92
+ "handle": [
93
+ {
94
+ "body": "Shadowed!",
95
+ "handler": "static_response"
96
+ }
97
+ ]
98
+ }
99
+ ]
100
+ }
101
+ ],
102
+ "terminal": true
103
+ },
104
+ {
105
+ "match": [
106
+ {
107
+ "host": [
108
+ "*.example.com"
109
+ ]
110
+ }
111
+ ],
112
+ "handle": [
113
+ {
114
+ "handler": "subroute",
115
+ "routes": [
116
+ {
117
+ "handle": [
118
+ {
119
+ "body": "Wildcard!",
120
+ "handler": "static_response"
121
+ }
122
+ ]
123
+ }
124
+ ]
125
+ }
126
+ ],
127
+ "terminal": true
128
+ }
129
+ ],
130
+ "tls_connection_policies": [
131
+ {
132
+ "match": {
133
+ "sni": [
134
+ "automated1.example.com"
135
+ ]
136
+ }
137
+ },
138
+ {
139
+ "match": {
140
+ "sni": [
141
+ "automated2.example.com"
142
+ ]
143
+ }
144
+ },
145
+ {
146
+ "match": {
147
+ "sni": [
148
+ "*.example.com"
149
+ ]
150
+ },
151
+ "certificate_selection": {
152
+ "any_tag": [
153
+ "cert0"
154
+ ]
155
+ }
156
+ },
157
+ {}
158
+ ]
159
+ }
160
+ }
161
+ },
162
+ "tls": {
163
+ "certificates": {
164
+ "automate": [
165
+ "automated1.example.com",
166
+ "automated2.example.com"
167
+ ],
168
+ "load_files": [
169
+ {
170
+ "certificate": "cert.pem",
171
+ "key": "key.pem",
172
+ "tags": [
173
+ "cert0"
174
+ ]
175
+ }
176
+ ]
177
+ }
178
+ }
179
+ }
180
+}
\ No newline at end of file
caddytest/integration/caddyfile_adapt/tls_automation_wildcard_shadowing.caddyfiletest
new
+102
@@ -0,0 +1,102 @@
1
+subdomain.example.com {
2
+ respond "Subdomain!"
3
+}
4
+
5
+*.example.com {
6
+ tls cert.pem key.pem
7
+ respond "Wildcard!"
8
+}
9
+----------
10
+{
11
+ "apps": {
12
+ "http": {
13
+ "servers": {
14
+ "srv0": {
15
+ "listen": [
16
+ ":443"
17
+ ],
18
+ "routes": [
19
+ {
20
+ "match": [
21
+ {
22
+ "host": [
23
+ "subdomain.example.com"
24
+ ]
25
+ }
26
+ ],
27
+ "handle": [
28
+ {
29
+ "handler": "subroute",
30
+ "routes": [
31
+ {
32
+ "handle": [
33
+ {
34
+ "body": "Subdomain!",
35
+ "handler": "static_response"
36
+ }
37
+ ]
38
+ }
39
+ ]
40
+ }
41
+ ],
42
+ "terminal": true
43
+ },
44
+ {
45
+ "match": [
46
+ {
47
+ "host": [
48
+ "*.example.com"
49
+ ]
50
+ }
51
+ ],
52
+ "handle": [
53
+ {
54
+ "handler": "subroute",
55
+ "routes": [
56
+ {
57
+ "handle": [
58
+ {
59
+ "body": "Wildcard!",
60
+ "handler": "static_response"
61
+ }
62
+ ]
63
+ }
64
+ ]
65
+ }
66
+ ],
67
+ "terminal": true
68
+ }
69
+ ],
70
+ "tls_connection_policies": [
71
+ {
72
+ "match": {
73
+ "sni": [
74
+ "*.example.com"
75
+ ]
76
+ },
77
+ "certificate_selection": {
78
+ "any_tag": [
79
+ "cert0"
80
+ ]
81
+ }
82
+ },
83
+ {}
84
+ ]
85
+ }
86
+ }
87
+ },
88
+ "tls": {
89
+ "certificates": {
90
+ "load_files": [
91
+ {
92
+ "certificate": "cert.pem",
93
+ "key": "key.pem",
94
+ "tags": [
95
+ "cert0"
96
+ ]
97
+ }
98
+ ]
99
+ }
100
+ }
101
+ }
102
+}
\ No newline at end of file