@marcel91200 / caddy Mirror · read-only / commits / afa778ae
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 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