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, 18:15 UTC
caddytls: Make on-demand 'ask' permission modular (#6055)
* caddytls: Make on-demand 'ask' permission modular This makes the 'ask' endpoint a module, which means that developers can write custom plugins for granting permission for on-demand certificates. Kicking myself that we didn't do it this way at the beginning, but who coulda known... * Lint * Error on conflicting config * Fix bad merge --------- Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Matt Holt committed
Jan 30, 2024 at 16:11 UTC
57c5b921a4283b4efa44d2fd77dce50f3113fb5a
8 files changed
+267
-137
caddyconfig/httpcaddyfile/options.go
+4
-2
@@ -335,7 +335,8 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
335
}
336
337
var ond *caddytls.OnDemandConfig
338
- for d.NextBlock(0) {
338
+
339
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
340
switch d.Val() {
341
case "ask":
342
if !d.NextArg() {
@@ -344,7 +345,8 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
345
if ond == nil {
346
ond = new(caddytls.OnDemandConfig)
347
}
347
- ond.Ask = d.Val()
348
+ perm := caddytls.PermissionByHTTP{Endpoint: d.Val()}
349
+ ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", "http", nil)
350
351
case "interval":
352
if !d.NextArg() {
caddytest/integration/caddyfile_adapt/global_options.txt
+4
-1
@@ -69,7 +69,10 @@
69
}
70
],
71
"on_demand": {
72
- "ask": "https://example.com",
72
+ "permission": {
73
+ "endpoint": "https://example.com",
74
+ "module": "http"
75
+ },
76
"rate_limit": {
77
"interval": 30000000000,
78
"burst": 20
caddytest/integration/caddyfile_adapt/global_options_acme.txt
+4
-1
@@ -78,7 +78,10 @@
78
}
79
],
80
"on_demand": {
81
- "ask": "https://example.com",
81
+ "permission": {
82
+ "endpoint": "https://example.com",
83
+ "module": "http"
84
+ },
85
"rate_limit": {
86
"interval": 30000000000,
87
"burst": 20
caddytest/integration/caddyfile_adapt/global_options_admin.txt
+4
-1
@@ -71,7 +71,10 @@
71
}
72
],
73
"on_demand": {
74
- "ask": "https://example.com",
74
+ "permission": {
75
+ "endpoint": "https://example.com",
76
+ "module": "http"
77
+ },
78
"rate_limit": {
79
"interval": 30000000000,
80
"burst": 20
modules/caddytls/acmeissuer.go
-52
@@ -16,12 +16,8 @@ package caddytls
16
17
import (
18
"context"
19
- "crypto/tls"
19
"crypto/x509"
21
- "errors"
20
"fmt"
23
- "net"
24
- "net/url"
21
"os"
22
"strconv"
23
"time"
@@ -495,49 +491,6 @@ func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
491
return nil
492
}
493
498
-// onDemandAskRequest makes a request to the ask URL
499
-// to see if a certificate can be obtained for name.
500
-// The certificate request should be denied if this
501
-// returns an error.
502
-func onDemandAskRequest(ctx context.Context, logger *zap.Logger, ask string, name string) error {
503
- askURL, err := url.Parse(ask)
504
- if err != nil {
505
- return fmt.Errorf("parsing ask URL: %v", err)
506
- }
507
- qs := askURL.Query()
508
- qs.Set("domain", name)
509
- askURL.RawQuery = qs.Encode()
510
-
511
- askURLString := askURL.String()
512
- resp, err := onDemandAskClient.Get(askURLString)
513
- if err != nil {
514
- return fmt.Errorf("error checking %v to determine if certificate for hostname '%s' should be allowed: %v",
515
- ask, name, err)
516
- }
517
- resp.Body.Close()
518
-
519
- // logging out the client IP can be useful for servers that want to count
520
- // attempts from clients to detect patterns of abuse
521
- var clientIP string
522
- if hello, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && hello != nil {
523
- if remote := hello.Conn.RemoteAddr(); remote != nil {
524
- clientIP, _, _ = net.SplitHostPort(remote.String())
525
- }
526
- }
527
-
528
- logger.Debug("response from ask endpoint",
529
- zap.String("client_ip", clientIP),
530
- zap.String("domain", name),
531
- zap.String("url", askURLString),
532
- zap.Int("status", resp.StatusCode))
533
-
534
- if resp.StatusCode < 200 || resp.StatusCode > 299 {
535
- return fmt.Errorf("%s: %w %s - non-2xx status code %d", name, errAskDenied, ask, resp.StatusCode)
536
- }
537
-
538
- return nil
539
-}
540
-
494
func ParseCaddyfilePreferredChainsOptions(d *caddyfile.Dispenser) (*ChainPreference, error) {
495
chainPref := new(ChainPreference)
496
if d.NextArg() {
@@ -605,11 +558,6 @@ type ChainPreference struct {
558
AnyCommonName []string `json:"any_common_name,omitempty"`
559
}
560
608
-// errAskDenied is an error that should be wrapped or returned when the
609
-// configured "ask" endpoint does not allow a certificate to be issued,
610
-// to distinguish that from other errors such as connection failure.
611
-var errAskDenied = errors.New("certificate not allowed by ask endpoint")
612
-
561
// Interface guards
562
var (
563
_ certmagic.PreChecker = (*ACMEIssuer)(nil)
modules/caddytls/automation.go
+28
-60
@@ -16,12 +16,12 @@ package caddytls
16
17
import (
18
"context"
19
+ "crypto/tls"
20
"encoding/json"
21
"errors"
22
"fmt"
22
- "net/http"
23
+ "net"
24
"strings"
24
- "time"
25
26
"github.com/caddyserver/certmagic"
27
"github.com/mholt/acmez"
@@ -254,37 +254,52 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
254
// on-demand TLS
255
var ond *certmagic.OnDemandConfig
256
if ap.OnDemand || len(ap.Managers) > 0 {
257
- // ask endpoint is now required after a number of negligence cases causing abuse;
258
- // but is still allowed for explicit subjects (non-wildcard, non-unbounded),
259
- // for the internal issuer since it doesn't cause ACME issuer pressure
260
- if ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.Ask == "") {
261
- return fmt.Errorf("on-demand TLS cannot be enabled without an 'ask' endpoint to prevent abuse; please refer to documentation for details")
257
+ // permission module is now required after a number of negligence cases that allowed abuse;
258
+ // but it may still be optional for explicit subjects (bounded, non-wildcard), for the
259
+ // internal issuer since it doesn't cause public PKI pressure on ACME servers
260
+ if ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.permission == nil) {
261
+ return fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details")
262
}
263
ond = &certmagic.OnDemandConfig{
264
DecisionFunc: func(ctx context.Context, name string) error {
265
if tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil {
266
return nil
267
}
268
- if err := onDemandAskRequest(ctx, tlsApp.logger, tlsApp.Automation.OnDemand.Ask, name); err != nil {
268
+
269
+ // logging the remote IP can be useful for servers that want to count
270
+ // attempts from clients to detect patterns of abuse -- it should NOT be
271
+ // used solely for decision making, however
272
+ var remoteIP string
273
+ if hello, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && hello != nil {
274
+ if remote := hello.Conn.RemoteAddr(); remote != nil {
275
+ remoteIP, _, _ = net.SplitHostPort(remote.String())
276
+ }
277
+ }
278
+ tlsApp.logger.Debug("asking for permission for on-demand certificate",
279
+ zap.String("remote_ip", remoteIP),
280
+ zap.String("domain", name))
281
+
282
+ // ask the permission module if this cert is allowed
283
+ if err := tlsApp.Automation.OnDemand.permission.CertificateAllowed(ctx, name); err != nil {
284
// distinguish true errors from denials, because it's important to elevate actual errors
270
- if errors.Is(err, errAskDenied) {
271
- tlsApp.logger.Debug("certificate issuance denied",
272
- zap.String("ask_endpoint", tlsApp.Automation.OnDemand.Ask),
285
+ if errors.Is(err, ErrPermissionDenied) {
286
+ tlsApp.logger.Debug("on-demand certificate issuance denied",
287
zap.String("domain", name),
288
zap.Error(err))
289
} else {
276
- tlsApp.logger.Error("request to 'ask' endpoint failed",
277
- zap.String("ask_endpoint", tlsApp.Automation.OnDemand.Ask),
290
+ tlsApp.logger.Error("failed to get permission for on-demand certificate",
291
zap.String("domain", name),
292
zap.Error(err))
293
}
294
return err
295
}
296
+
297
// check the rate limiter last because
298
// doing so makes a reservation
299
if !onDemandRateLimiter.Allow() {
300
return fmt.Errorf("on-demand rate limit exceeded")
301
}
302
+
303
return nil
304
},
305
Managers: ap.Managers,
@@ -464,42 +479,6 @@ type DNSChallengeConfig struct {
479
solver acmez.Solver
480
}
481
467
-// OnDemandConfig configures on-demand TLS, for obtaining
468
-// needed certificates at handshake-time. Because this
469
-// feature can easily be abused, you should use this to
470
-// establish rate limits and/or an internal endpoint that
471
-// Caddy can "ask" if it should be allowed to manage
472
-// certificates for a given hostname.
473
-type OnDemandConfig struct {
474
- // REQUIRED. If Caddy needs to load a certificate from
475
- // storage or obtain/renew a certificate during a TLS
476
- // handshake, it will perform a quick HTTP request to
477
- // this URL to check if it should be allowed to try to
478
- // get a certificate for the name in the "domain" query
479
- // string parameter, like so: `?domain=example.com`.
480
- // The endpoint must return a 200 OK status if a certificate
481
- // is allowed; anything else will cause it to be denied.
482
- // Redirects are not followed.
483
- Ask string `json:"ask,omitempty"`
484
-
485
- // DEPRECATED. An optional rate limit to throttle
486
- // the checking of storage and the issuance of
487
- // certificates from handshakes if not already in
488
- // storage. WILL BE REMOVED IN A FUTURE RELEASE.
489
- RateLimit *RateLimit `json:"rate_limit,omitempty"`
490
-}
491
-
492
-// DEPRECATED. RateLimit specifies an interval with optional burst size.
493
-type RateLimit struct {
494
- // A duration value. Storage may be checked and a certificate may be
495
- // obtained 'burst' times during this interval.
496
- Interval caddy.Duration `json:"interval,omitempty"`
497
-
498
- // How many times during an interval storage can be checked or a
499
- // certificate can be obtained.
500
- Burst int `json:"burst,omitempty"`
501
-}
502
-
482
// ConfigSetter is implemented by certmagic.Issuers that
483
// need access to a parent certmagic.Config as part of
484
// their provisioning phase. For example, the ACMEIssuer
@@ -508,14 +487,3 @@ type RateLimit struct {
487
type ConfigSetter interface {
488
SetConfig(cfg *certmagic.Config)
489
}
511
-
512
-// These perpetual values are used for on-demand TLS.
513
-var (
514
- onDemandRateLimiter = certmagic.NewRateLimiter(0, 0)
515
- onDemandAskClient = &http.Client{
516
- Timeout: 10 * time.Second,
517
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
518
- return fmt.Errorf("following http redirects is not allowed")
519
- },
520
- }
521
-)
modules/caddytls/ondemand.go
new
+192
@@ -0,0 +1,192 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddytls
16
+
17
+import (
18
+ "context"
19
+ "crypto/tls"
20
+ "encoding/json"
21
+ "errors"
22
+ "fmt"
23
+ "net/http"
24
+ "net/url"
25
+ "time"
26
+
27
+ "github.com/caddyserver/certmagic"
28
+ "go.uber.org/zap"
29
+
30
+ "github.com/caddyserver/caddy/v2"
31
+)
32
+
33
+func init() {
34
+ caddy.RegisterModule(PermissionByHTTP{})
35
+}
36
+
37
+// OnDemandConfig configures on-demand TLS, for obtaining
38
+// needed certificates at handshake-time. Because this
39
+// feature can easily be abused, you should use this to
40
+// establish rate limits and/or an internal endpoint that
41
+// Caddy can "ask" if it should be allowed to manage
42
+// certificates for a given hostname.
43
+type OnDemandConfig struct {
44
+ // DEPRECATED. WILL BE REMOVED SOON. Use 'permission' instead.
45
+ Ask string `json:"ask,omitempty"`
46
+
47
+ // REQUIRED. A module that will determine whether a
48
+ // certificate is allowed to be loaded from storage
49
+ // or obtained from an issuer on demand.
50
+ PermissionRaw json.RawMessage `json:"permission,omitempty" caddy:"namespace=tls.permission inline_key=module"`
51
+ permission OnDemandPermission
52
+
53
+ // DEPRECATED. An optional rate limit to throttle
54
+ // the checking of storage and the issuance of
55
+ // certificates from handshakes if not already in
56
+ // storage. WILL BE REMOVED IN A FUTURE RELEASE.
57
+ RateLimit *RateLimit `json:"rate_limit,omitempty"`
58
+}
59
+
60
+// DEPRECATED. WILL LIKELY BE REMOVED SOON.
61
+// Instead of using this rate limiter, use a proper tool such as a
62
+// level 3 or 4 firewall and/or a permission module to apply rate limits.
63
+type RateLimit struct {
64
+ // A duration value. Storage may be checked and a certificate may be
65
+ // obtained 'burst' times during this interval.
66
+ Interval caddy.Duration `json:"interval,omitempty"`
67
+
68
+ // How many times during an interval storage can be checked or a
69
+ // certificate can be obtained.
70
+ Burst int `json:"burst,omitempty"`
71
+}
72
+
73
+// OnDemandPermission is a type that can give permission for
74
+// whether a certificate should be allowed to be obtained or
75
+// loaded from storage on-demand.
76
+// EXPERIMENTAL: This API is experimental and subject to change.
77
+type OnDemandPermission interface {
78
+ // CertificateAllowed returns nil if a certificate for the given
79
+ // name is allowed to be either obtained from an issuer or loaded
80
+ // from storage on-demand.
81
+ //
82
+ // The context passed in has the associated *tls.ClientHelloInfo
83
+ // value available at the certmagic.ClientHelloInfoCtxKey key.
84
+ //
85
+ // In the worst case, this function may be called as frequently
86
+ // as every TLS handshake, so it should return as quick as possible
87
+ // to reduce latency. In the normal case, this function is only
88
+ // called when a certificate is needed that is not already loaded
89
+ // into memory ready to serve.
90
+ CertificateAllowed(ctx context.Context, name string) error
91
+}
92
+
93
+// PermissionByHTTP determines permission for a TLS certificate by
94
+// making a request to an HTTP endpoint.
95
+type PermissionByHTTP struct {
96
+ // The endpoint to access. It should be a full URL.
97
+ // A query string parameter "domain" will be added to it,
98
+ // containing the domain (or IP) for the desired certificate,
99
+ // like so: `?domain=example.com`. Generally, this endpoint
100
+ // is not exposed publicly to avoid a minor information leak
101
+ // (which domains are serviced by your application).
102
+ //
103
+ // The endpoint must return a 200 OK status if a certificate
104
+ // is allowed; anything else will cause it to be denied.
105
+ // Redirects are not followed.
106
+ Endpoint string `json:"endpoint"`
107
+
108
+ logger *zap.Logger
109
+ replacer *caddy.Replacer
110
+}
111
+
112
+// CaddyModule returns the Caddy module information.
113
+func (PermissionByHTTP) CaddyModule() caddy.ModuleInfo {
114
+ return caddy.ModuleInfo{
115
+ ID: "tls.permission.http",
116
+ New: func() caddy.Module { return new(PermissionByHTTP) },
117
+ }
118
+}
119
+
120
+func (p *PermissionByHTTP) Provision(ctx caddy.Context) error {
121
+ p.logger = ctx.Logger()
122
+ p.replacer = caddy.NewReplacer()
123
+ return nil
124
+}
125
+
126
+func (p PermissionByHTTP) CertificateAllowed(ctx context.Context, name string) error {
127
+ // run replacer on endpoint URL (for environment variables) -- return errors to prevent surprises (#5036)
128
+ askEndpoint, err := p.replacer.ReplaceOrErr(p.Endpoint, true, true)
129
+ if err != nil {
130
+ return fmt.Errorf("preparing 'ask' endpoint: %v", err)
131
+ }
132
+
133
+ askURL, err := url.Parse(askEndpoint)
134
+ if err != nil {
135
+ return fmt.Errorf("parsing ask URL: %v", err)
136
+ }
137
+ qs := askURL.Query()
138
+ qs.Set("domain", name)
139
+ askURL.RawQuery = qs.Encode()
140
+ askURLString := askURL.String()
141
+
142
+ var remote string
143
+ if chi, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && chi != nil {
144
+ remote = chi.Conn.RemoteAddr().String()
145
+ }
146
+
147
+ p.logger.Debug("asking permission endpoint",
148
+ zap.String("remote", remote),
149
+ zap.String("domain", name),
150
+ zap.String("url", askURLString))
151
+
152
+ resp, err := onDemandAskClient.Get(askURLString)
153
+ if err != nil {
154
+ return fmt.Errorf("checking %v to determine if certificate for hostname '%s' should be allowed: %v",
155
+ askEndpoint, name, err)
156
+ }
157
+ resp.Body.Close()
158
+
159
+ p.logger.Debug("response from permission endpoint",
160
+ zap.String("remote", remote),
161
+ zap.String("domain", name),
162
+ zap.String("url", askURLString),
163
+ zap.Int("status", resp.StatusCode))
164
+
165
+ if resp.StatusCode < 200 || resp.StatusCode > 299 {
166
+ return fmt.Errorf("%s: %w %s - non-2xx status code %d", name, ErrPermissionDenied, askEndpoint, resp.StatusCode)
167
+ }
168
+
169
+ return nil
170
+}
171
+
172
+// ErrPermissionDenied is an error that should be wrapped or returned when the
173
+// configured permission module does not allow a certificate to be issued,
174
+// to distinguish that from other errors such as connection failure.
175
+var ErrPermissionDenied = errors.New("certificate not allowed by permission module")
176
+
177
+// These perpetual values are used for on-demand TLS.
178
+var (
179
+ onDemandRateLimiter = certmagic.NewRateLimiter(0, 0)
180
+ onDemandAskClient = &http.Client{
181
+ Timeout: 10 * time.Second,
182
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
183
+ return fmt.Errorf("following http redirects is not allowed")
184
+ },
185
+ }
186
+)
187
+
188
+// Interface guards
189
+var (
190
+ _ OnDemandPermission = (*PermissionByHTTP)(nil)
191
+ _ caddy.Provisioner = (*PermissionByHTTP)(nil)
192
+)
modules/caddytls/tls.go
+31
-20
@@ -164,6 +164,36 @@ func (t *TLS) Provision(ctx caddy.Context) error {
164
t.certificateLoaders = append(t.certificateLoaders, modIface.(CertificateLoader))
165
}
166
167
+ // on-demand permission module
168
+ if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.PermissionRaw != nil {
169
+ if t.Automation.OnDemand.Ask != "" {
170
+ return fmt.Errorf("on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module")
171
+ }
172
+ val, err := ctx.LoadModule(t.Automation.OnDemand, "PermissionRaw")
173
+ if err != nil {
174
+ return fmt.Errorf("loading on-demand TLS permission module: %v", err)
175
+ }
176
+ t.Automation.OnDemand.permission = val.(OnDemandPermission)
177
+ }
178
+
179
+ // on-demand rate limiting
180
+ if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.RateLimit != nil {
181
+ onDemandRateLimiter.SetMaxEvents(t.Automation.OnDemand.RateLimit.Burst)
182
+ onDemandRateLimiter.SetWindow(time.Duration(t.Automation.OnDemand.RateLimit.Interval))
183
+ } else {
184
+ // remove any existing rate limiter
185
+ onDemandRateLimiter.SetWindow(0)
186
+ onDemandRateLimiter.SetMaxEvents(0)
187
+ }
188
+
189
+ // run replacer on ask URL (for environment variables) -- return errors to prevent surprises (#5036)
190
+ if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.Ask != "" {
191
+ t.Automation.OnDemand.Ask, err = repl.ReplaceOrErr(t.Automation.OnDemand.Ask, true, true)
192
+ if err != nil {
193
+ return fmt.Errorf("preparing 'ask' endpoint: %v", err)
194
+ }
195
+ }
196
+
197
// automation/management policies
198
if t.Automation == nil {
199
t.Automation = new(AutomationConfig)
@@ -204,24 +234,6 @@ func (t *TLS) Provision(ctx caddy.Context) error {
234
}
235
}
236
207
- // on-demand rate limiting
208
- if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.RateLimit != nil {
209
- onDemandRateLimiter.SetMaxEvents(t.Automation.OnDemand.RateLimit.Burst)
210
- onDemandRateLimiter.SetWindow(time.Duration(t.Automation.OnDemand.RateLimit.Interval))
211
- } else {
212
- // remove any existing rate limiter
213
- onDemandRateLimiter.SetWindow(0)
214
- onDemandRateLimiter.SetMaxEvents(0)
215
- }
216
-
217
- // run replacer on ask URL (for environment variables) -- return errors to prevent surprises (#5036)
218
- if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.Ask != "" {
219
- t.Automation.OnDemand.Ask, err = repl.ReplaceOrErr(t.Automation.OnDemand.Ask, true, true)
220
- if err != nil {
221
- return fmt.Errorf("preparing 'ask' endpoint: %v", err)
222
- }
223
- }
224
-
237
// load manual/static (unmanaged) certificates - we do this in
238
// provision so that other apps (such as http) can know which
239
// certificates have been manually loaded, and also so that
@@ -288,8 +300,7 @@ func (t *TLS) Validate() error {
300
// Start activates the TLS module.
301
func (t *TLS) Start() error {
302
// warn if on-demand TLS is enabled but no restrictions are in place
291
- if t.Automation.OnDemand == nil ||
292
- (t.Automation.OnDemand.Ask == "" && t.Automation.OnDemand.RateLimit == nil) {
303
+ if t.Automation.OnDemand == nil || (t.Automation.OnDemand.Ask == "" && t.Automation.OnDemand.permission == nil) {
304
for _, ap := range t.Automation.Policies {
305
if ap.OnDemand && ap.isWildcardOrDefault() {
306
t.logger.Warn("YOUR SERVER MAY BE VULNERABLE TO ABUSE: on-demand TLS is enabled, but no protections are in place",