Skip to content

Commit afa778a

Browse files
httpcaddyfile: Implement experimental force_automate option (#6712)
1 parent 5ba1e06 commit afa778a

File tree

5 files changed

+334
-3
lines changed

5 files changed

+334
-3
lines changed

caddyconfig/httpcaddyfile/builtins.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
8484

8585
// parseTLS parses the tls directive. Syntax:
8686
//
87-
// tls [<email>|internal]|[<cert_file> <key_file>] {
87+
// tls [<email>|internal|force_automate]|[<cert_file> <key_file>] {
8888
// protocols <min> [<max>]
8989
// ciphers <cipher_suites...>
9090
// curves <curves...>
@@ -107,6 +107,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
107107
// dns_challenge_override_domain <domain>
108108
// on_demand
109109
// reuse_private_keys
110+
// force_automate
110111
// eab <key_id> <mac_key>
111112
// issuer <module_name> [...]
112113
// get_certificate <module_name> [...]
@@ -126,15 +127,18 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
126127
var certManagers []certmagic.Manager
127128
var onDemand bool
128129
var reusePrivateKeys bool
130+
var forceAutomate bool
129131

130132
firstLine := h.RemainingArgs()
131133
switch len(firstLine) {
132134
case 0:
133135
case 1:
134136
if firstLine[0] == "internal" {
135137
internalIssuer = new(caddytls.InternalIssuer)
138+
} else if firstLine[0] == "force_automate" {
139+
forceAutomate = true
136140
} 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")
138142
} else {
139143
acmeIssuer = &caddytls.ACMEIssuer{
140144
Email: firstLine[0],
@@ -569,6 +573,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
569573
})
570574
}
571575

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+
572585
// custom certificate selection
573586
if len(certSelector.AnyTag) > 0 {
574587
cp.CertSelection = &certSelector

caddyconfig/httpcaddyfile/httptype.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,14 @@ func (st *ServerType) serversFromPairings(
763763
}
764764
}
765765

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+
766774
// tls: connection policies
767775
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
768776
// tls connection policies
@@ -794,7 +802,7 @@ func (st *ServerType) serversFromPairings(
794802
}
795803

796804
// only append this policy if it actually changes something
797-
if !cp.SettingsEmpty() {
805+
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
798806
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
799807
hasCatchAllTLSConnPolicy = len(hosts) == 0
800808
}
@@ -1661,6 +1669,18 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool {
16611669
return false
16621670
}
16631671

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+
16641684
// specificity returns len(s) minus any wildcards (*) and
16651685
// placeholders ({...}). Basically, it's a length count
16661686
// that penalizes the use of wildcards and placeholders.

caddyconfig/httpcaddyfile/tlsapp.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ func (st ServerType) buildTLSApp(
9494

9595
// collect all hosts that have a wildcard in them, and arent HTTP
9696
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{})
97100
for _, p := range pairings {
98101
var addresses []string
99102
for _, addressWithProtocols := range p.addressesWithProtocols {
@@ -150,6 +153,13 @@ func (st ServerType) buildTLSApp(
150153
ap.OnDemand = true
151154
}
152155

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+
153163
// reuse private keys tls
154164
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
155165
ap.ReusePrivateKeys = true
@@ -407,6 +417,12 @@ func (st ServerType) buildTLSApp(
407417
}
408418
}
409419
}
420+
for name := range forcedAutomatedNames {
421+
if slices.Contains(al, name) {
422+
continue
423+
}
424+
al = append(al, name)
425+
}
410426
if len(al) > 0 {
411427
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
412428
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)