1
+package caddytls
2
+
3
+import (
4
+ "crypto/tls"
5
+ "crypto/x509"
6
+ "encoding/json"
7
+ "errors"
8
+ "fmt"
9
+ "io"
10
+ "net/http"
11
+ "net/url"
12
+ "os"
13
+ "reflect"
14
+
15
+ "github.com/caddyserver/certmagic"
16
+
17
+ "github.com/caddyserver/caddy/v2"
18
+ "github.com/caddyserver/caddy/v2/caddyconfig"
19
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
20
+ "github.com/caddyserver/caddy/v2/modules/caddypki"
21
+)
22
+
23
+func init() {
24
+ caddy.RegisterModule(InlineCAPool{})
25
+ caddy.RegisterModule(FileCAPool{})
26
+ caddy.RegisterModule(PKIRootCAPool{})
27
+ caddy.RegisterModule(PKIIntermediateCAPool{})
28
+ caddy.RegisterModule(StoragePool{})
29
+ caddy.RegisterModule(HTTPCertPool{})
30
+ caddy.RegisterModule(LazyCertPool{})
31
+}
32
+
33
+// The interface to be implemented by all guest modules part of
34
+// the namespace 'tls.ca_pool.source.'
35
+type CA interface {
36
+ CertPool() *x509.CertPool
37
+}
38
+
39
+// InlineCAPool is a certificate authority pool provider coming from
40
+// a DER-encoded certificates in the config
41
+type InlineCAPool struct {
42
+ // A list of base64 DER-encoded CA certificates
43
+ // against which to validate client certificates.
44
+ // Client certs which are not signed by any of
45
+ // these CAs will be rejected.
46
+ TrustedCACerts []string `json:"trusted_ca_certs,omitempty"`
47
+
48
+ pool *x509.CertPool
49
+}
50
+
51
+// CaddyModule implements caddy.Module.
52
+func (icp InlineCAPool) CaddyModule() caddy.ModuleInfo {
53
+ return caddy.ModuleInfo{
54
+ ID: "tls.ca_pool.source.inline",
55
+ New: func() caddy.Module {
56
+ return new(InlineCAPool)
57
+ },
58
+ }
59
+}
60
+
61
+// Provision implements caddy.Provisioner.
62
+func (icp *InlineCAPool) Provision(ctx caddy.Context) error {
63
+ caPool := x509.NewCertPool()
64
+ for i, clientCAString := range icp.TrustedCACerts {
65
+ clientCA, err := decodeBase64DERCert(clientCAString)
66
+ if err != nil {
67
+ return fmt.Errorf("parsing certificate at index %d: %v", i, err)
68
+ }
69
+ caPool.AddCert(clientCA)
70
+ }
71
+ icp.pool = caPool
72
+
73
+ return nil
74
+}
75
+
76
+// Syntax:
77
+//
78
+// trust_pool inline {
79
+// trust_der <base64_der_cert>...
80
+// }
81
+//
82
+// The 'trust_der' directive can be specified multiple times.
83
+func (icp *InlineCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
84
+ d.Next() // consume module name
85
+ if d.CountRemainingArgs() > 0 {
86
+ return d.ArgErr()
87
+ }
88
+ for d.NextBlock(0) {
89
+ switch d.Val() {
90
+ case "trust_der":
91
+ icp.TrustedCACerts = append(icp.TrustedCACerts, d.RemainingArgs()...)
92
+ default:
93
+ return d.Errf("unrecognized directive: %s", d.Val())
94
+ }
95
+ }
96
+ if len(icp.TrustedCACerts) == 0 {
97
+ return d.Err("no certificates specified")
98
+ }
99
+ return nil
100
+}
101
+
102
+// CertPool implements CA.
103
+func (icp InlineCAPool) CertPool() *x509.CertPool {
104
+ return icp.pool
105
+}
106
+
107
+// FileCAPool generates trusted root certificates pool from the designated DER and PEM file
108
+type FileCAPool struct {
109
+ // TrustedCACertPEMFiles is a list of PEM file names
110
+ // from which to load certificates of trusted CAs.
111
+ // Client certificates which are not signed by any of
112
+ // these CA certificates will be rejected.
113
+ TrustedCACertPEMFiles []string `json:"pem_files,omitempty"`
114
+
115
+ pool *x509.CertPool
116
+}
117
+
118
+// CaddyModule implements caddy.Module.
119
+func (FileCAPool) CaddyModule() caddy.ModuleInfo {
120
+ return caddy.ModuleInfo{
121
+ ID: "tls.ca_pool.source.file",
122
+ New: func() caddy.Module {
123
+ return new(FileCAPool)
124
+ },
125
+ }
126
+}
127
+
128
+// Loads and decodes the DER and pem files to generate the certificate pool
129
+func (f *FileCAPool) Provision(ctx caddy.Context) error {
130
+ caPool := x509.NewCertPool()
131
+ for _, pemFile := range f.TrustedCACertPEMFiles {
132
+ pemContents, err := os.ReadFile(pemFile)
133
+ if err != nil {
134
+ return fmt.Errorf("reading %s: %v", pemFile, err)
135
+ }
136
+ caPool.AppendCertsFromPEM(pemContents)
137
+ }
138
+ f.pool = caPool
139
+ return nil
140
+}
141
+
142
+// Syntax:
143
+//
144
+// trust_pool file [<pem_file>...] {
145
+// pem_file <pem_file>...
146
+// }
147
+//
148
+// The 'pem_file' directive can be specified multiple times.
149
+func (fcap *FileCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
150
+ d.Next() // consume module name
151
+ fcap.TrustedCACertPEMFiles = append(fcap.TrustedCACertPEMFiles, d.RemainingArgs()...)
152
+ for d.NextBlock(0) {
153
+ switch d.Val() {
154
+ case "pem_file":
155
+ fcap.TrustedCACertPEMFiles = append(fcap.TrustedCACertPEMFiles, d.RemainingArgs()...)
156
+ default:
157
+ return d.Errf("unrecognized directive: %s", d.Val())
158
+ }
159
+ }
160
+ if len(fcap.TrustedCACertPEMFiles) == 0 {
161
+ return d.Err("no certificates specified")
162
+ }
163
+ return nil
164
+}
165
+
166
+func (f FileCAPool) CertPool() *x509.CertPool {
167
+ return f.pool
168
+}
169
+
170
+// PKIRootCAPool extracts the trusted root certificates from Caddy's native 'pki' app
171
+type PKIRootCAPool struct {
172
+ // List of the Authority names that are configured in the `pki` app whose root certificates are trusted
173
+ Authority []string `json:"authority,omitempty"`
174
+
175
+ ca []*caddypki.CA
176
+ pool *x509.CertPool
177
+}
178
+
179
+// CaddyModule implements caddy.Module.
180
+func (PKIRootCAPool) CaddyModule() caddy.ModuleInfo {
181
+ return caddy.ModuleInfo{
182
+ ID: "tls.ca_pool.source.pki_root",
183
+ New: func() caddy.Module {
184
+ return new(PKIRootCAPool)
185
+ },
186
+ }
187
+}
188
+
189
+// Loads the PKI app and load the root certificates into the certificate pool
190
+func (p *PKIRootCAPool) Provision(ctx caddy.Context) error {
191
+ pkiApp := ctx.AppIfConfigured("pki")
192
+ if pkiApp == nil {
193
+ return fmt.Errorf("PKI app not configured")
194
+ }
195
+ pki := pkiApp.(*caddypki.PKI)
196
+ for _, caID := range p.Authority {
197
+ c, err := pki.GetCA(ctx, caID)
198
+ if err != nil || c == nil {
199
+ return fmt.Errorf("getting CA %s: %v", caID, err)
200
+ }
201
+ p.ca = append(p.ca, c)
202
+ }
203
+
204
+ caPool := x509.NewCertPool()
205
+ for _, ca := range p.ca {
206
+ caPool.AddCert(ca.RootCertificate())
207
+ }
208
+ p.pool = caPool
209
+
210
+ return nil
211
+}
212
+
213
+// Syntax:
214
+//
215
+// trust_pool pki_root [<ca_name>...] {
216
+// authority <ca_name>...
217
+// }
218
+//
219
+// The 'authority' directive can be specified multiple times.
220
+func (pkir *PKIRootCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
221
+ d.Next() // consume module name
222
+ pkir.Authority = append(pkir.Authority, d.RemainingArgs()...)
223
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
224
+ switch d.Val() {
225
+ case "authority":
226
+ pkir.Authority = append(pkir.Authority, d.RemainingArgs()...)
227
+ default:
228
+ return d.Errf("unrecognized directive: %s", d.Val())
229
+ }
230
+ }
231
+ if len(pkir.Authority) == 0 {
232
+ return d.Err("no authorities specified")
233
+ }
234
+ return nil
235
+}
236
+
237
+// return the certificate pool generated with root certificates from the PKI app
238
+func (p PKIRootCAPool) CertPool() *x509.CertPool {
239
+ return p.pool
240
+}
241
+
242
+// PKIIntermediateCAPool extracts the trusted intermediate certificates from Caddy's native 'pki' app
243
+type PKIIntermediateCAPool struct {
244
+ // List of the Authority names that are configured in the `pki` app whose intermediate certificates are trusted
245
+ Authority []string `json:"authority,omitempty"`
246
+
247
+ ca []*caddypki.CA
248
+ pool *x509.CertPool
249
+}
250
+
251
+// CaddyModule implements caddy.Module.
252
+func (PKIIntermediateCAPool) CaddyModule() caddy.ModuleInfo {
253
+ return caddy.ModuleInfo{
254
+ ID: "tls.ca_pool.source.pki_intermediate",
255
+ New: func() caddy.Module {
256
+ return new(PKIIntermediateCAPool)
257
+ },
258
+ }
259
+}
260
+
261
+// Loads the PKI app and load the intermediate certificates into the certificate pool
262
+func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error {
263
+ pkiApp := ctx.AppIfConfigured("pki")
264
+ if pkiApp == nil {
265
+ return fmt.Errorf("PKI app not configured")
266
+ }
267
+ pki := pkiApp.(*caddypki.PKI)
268
+ for _, caID := range p.Authority {
269
+ c, err := pki.GetCA(ctx, caID)
270
+ if err != nil || c == nil {
271
+ return fmt.Errorf("getting CA %s: %v", caID, err)
272
+ }
273
+ p.ca = append(p.ca, c)
274
+ }
275
+
276
+ caPool := x509.NewCertPool()
277
+ for _, ca := range p.ca {
278
+ caPool.AddCert(ca.IntermediateCertificate())
279
+ }
280
+ p.pool = caPool
281
+ return nil
282
+}
283
+
284
+// Syntax:
285
+//
286
+// trust_pool pki_intermediate [<ca_name>...] {
287
+// authority <ca_name>...
288
+// }
289
+//
290
+// The 'authority' directive can be specified multiple times.
291
+func (pic *PKIIntermediateCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
292
+ d.Next() // consume module name
293
+ pic.Authority = append(pic.Authority, d.RemainingArgs()...)
294
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
295
+ switch d.Val() {
296
+ case "authority":
297
+ pic.Authority = append(pic.Authority, d.RemainingArgs()...)
298
+ default:
299
+ return d.Errf("unrecognized directive: %s", d.Val())
300
+ }
301
+ }
302
+ if len(pic.Authority) == 0 {
303
+ return d.Err("no authorities specified")
304
+ }
305
+ return nil
306
+}
307
+
308
+// return the certificate pool generated with intermediate certificates from the PKI app
309
+func (p PKIIntermediateCAPool) CertPool() *x509.CertPool {
310
+ return p.pool
311
+}
312
+
313
+// StoragePool extracts the trusted certificates root from Caddy storage
314
+type StoragePool struct {
315
+ // The storage module where the trusted root certificates are stored. Absent
316
+ // explicit storage implies the use of Caddy default storage.
317
+ StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
318
+
319
+ // The storage key/index to the location of the certificates
320
+ PEMKeys []string `json:"pem_keys,omitempty"`
321
+
322
+ storage certmagic.Storage
323
+ pool *x509.CertPool
324
+}
325
+
326
+// CaddyModule implements caddy.Module.
327
+func (StoragePool) CaddyModule() caddy.ModuleInfo {
328
+ return caddy.ModuleInfo{
329
+ ID: "tls.ca_pool.source.storage",
330
+ New: func() caddy.Module {
331
+ return new(StoragePool)
332
+ },
333
+ }
334
+}
335
+
336
+// Provision implements caddy.Provisioner.
337
+func (ca *StoragePool) Provision(ctx caddy.Context) error {
338
+ if ca.StorageRaw != nil {
339
+ val, err := ctx.LoadModule(ca, "StorageRaw")
340
+ if err != nil {
341
+ return fmt.Errorf("loading storage module: %v", err)
342
+ }
343
+ cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
344
+ if err != nil {
345
+ return fmt.Errorf("creating storage configuration: %v", err)
346
+ }
347
+ ca.storage = cmStorage
348
+ }
349
+ if ca.storage == nil {
350
+ ca.storage = ctx.Storage()
351
+ }
352
+ if len(ca.PEMKeys) == 0 {
353
+ return fmt.Errorf("no PEM keys specified")
354
+ }
355
+ caPool := x509.NewCertPool()
356
+ for _, caID := range ca.PEMKeys {
357
+ bs, err := ca.storage.Load(ctx, caID)
358
+ if err != nil {
359
+ return fmt.Errorf("error loading cert '%s' from storage: %s", caID, err)
360
+ }
361
+ if !caPool.AppendCertsFromPEM(bs) {
362
+ return fmt.Errorf("failed to add certificate '%s' to pool", caID)
363
+ }
364
+ }
365
+ ca.pool = caPool
366
+
367
+ return nil
368
+}
369
+
370
+// Syntax:
371
+//
372
+// trust_pool storage [<storage_keys>...] {
373
+// storage <storage_module>
374
+// keys <storage_keys>...
375
+// }
376
+//
377
+// The 'keys' directive can be specified multiple times.
378
+// The'storage' directive is optional and defaults to the default storage module.
379
+func (sp *StoragePool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
380
+ d.Next() // consume module name
381
+ sp.PEMKeys = append(sp.PEMKeys, d.RemainingArgs()...)
382
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
383
+ switch d.Val() {
384
+ case "storage":
385
+ if sp.StorageRaw != nil {
386
+ return d.Err("storage module already set")
387
+ }
388
+ if !d.NextArg() {
389
+ return d.ArgErr()
390
+ }
391
+ modStem := d.Val()
392
+ modID := "caddy.storage." + modStem
393
+ unm, err := caddyfile.UnmarshalModule(d, modID)
394
+ if err != nil {
395
+ return err
396
+ }
397
+ storage, ok := unm.(caddy.StorageConverter)
398
+ if !ok {
399
+ return d.Errf("module %s is not a caddy.StorageConverter", modID)
400
+ }
401
+ sp.StorageRaw = caddyconfig.JSONModuleObject(storage, "module", modStem, nil)
402
+ case "keys":
403
+ sp.PEMKeys = append(sp.PEMKeys, d.RemainingArgs()...)
404
+ default:
405
+ return d.Errf("unrecognized directive: %s", d.Val())
406
+ }
407
+ }
408
+ return nil
409
+}
410
+
411
+func (p StoragePool) CertPool() *x509.CertPool {
412
+ return p.pool
413
+}
414
+
415
+// TLSConfig holds configuration related to the TLS configuration for the
416
+// transport/client.
417
+// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go
418
+type TLSConfig struct {
419
+ // Provides the guest module that provides the trusted certificate authority (CA) certificates
420
+ CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
421
+
422
+ // If true, TLS verification of server certificates will be disabled.
423
+ // This is insecure and may be removed in the future. Do not use this
424
+ // option except in testing or local development environments.
425
+ InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"`
426
+
427
+ // The duration to allow a TLS handshake to a server. Default: No timeout.
428
+ HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"`
429
+
430
+ // The server name used when verifying the certificate received in the TLS
431
+ // handshake. By default, this will use the upstream address' host part.
432
+ // You only need to override this if your upstream address does not match the
433
+ // certificate the upstream is likely to use. For example if the upstream
434
+ // address is an IP address, then you would need to configure this to the
435
+ // hostname being served by the upstream server. Currently, this does not
436
+ // support placeholders because the TLS config is not provisioned on each
437
+ // connection, so a static value must be used.
438
+ ServerName string `json:"server_name,omitempty"`
439
+
440
+ // TLS renegotiation level. TLS renegotiation is the act of performing
441
+ // subsequent handshakes on a connection after the first.
442
+ // The level can be:
443
+ // - "never": (the default) disables renegotiation.
444
+ // - "once": allows a remote server to request renegotiation once per connection.
445
+ // - "freely": allows a remote server to repeatedly request renegotiation.
446
+ Renegotiation string `json:"renegotiation,omitempty"`
447
+}
448
+
449
+func (t *TLSConfig) unmarshalCaddyfile(d *caddyfile.Dispenser) error {
450
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
451
+ switch d.Val() {
452
+ case "ca":
453
+ if !d.NextArg() {
454
+ return d.ArgErr()
455
+ }
456
+ modStem := d.Val()
457
+ modID := "tls.ca_pool.source." + modStem
458
+ unm, err := caddyfile.UnmarshalModule(d, modID)
459
+ if err != nil {
460
+ return err
461
+ }
462
+ ca, ok := unm.(CA)
463
+ if !ok {
464
+ return d.Errf("module %s is not a caddytls.CA", modID)
465
+ }
466
+ t.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil)
467
+ case "insecure_skip_verify":
468
+ t.InsecureSkipVerify = true
469
+ case "handshake_timeout":
470
+ if !d.NextArg() {
471
+ return d.ArgErr()
472
+ }
473
+ dur, err := caddy.ParseDuration(d.Val())
474
+ if err != nil {
475
+ return d.Errf("bad timeout value '%s': %v", d.Val(), err)
476
+ }
477
+ t.HandshakeTimeout = caddy.Duration(dur)
478
+ case "server_name":
479
+ if !d.Args(&t.ServerName) {
480
+ return d.ArgErr()
481
+ }
482
+ case "renegotiation":
483
+ if !d.Args(&t.Renegotiation) {
484
+ return d.ArgErr()
485
+ }
486
+ switch t.Renegotiation {
487
+ case "never", "once", "freely":
488
+ continue
489
+ default:
490
+ t.Renegotiation = ""
491
+ return d.Errf("unrecognized renegotiation level: %s", t.Renegotiation)
492
+ }
493
+ default:
494
+ return d.Errf("unrecognized directive: %s", d.Val())
495
+ }
496
+ }
497
+ return nil
498
+}
499
+
500
+// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
501
+// If there is no custom TLS configuration, a nil config may be returned.
502
+// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go
503
+func (t TLSConfig) makeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
504
+ repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
505
+ if repl == nil {
506
+ repl = caddy.NewReplacer()
507
+ }
508
+ cfg := new(tls.Config)
509
+
510
+ if t.CARaw != nil {
511
+ caRaw, err := ctx.LoadModule(t, "CARaw")
512
+ if err != nil {
513
+ return nil, err
514
+ }
515
+ ca := caRaw.(CA)
516
+ cfg.RootCAs = ca.CertPool()
517
+ }
518
+
519
+ // Renegotiation
520
+ switch t.Renegotiation {
521
+ case "never", "":
522
+ cfg.Renegotiation = tls.RenegotiateNever
523
+ case "once":
524
+ cfg.Renegotiation = tls.RenegotiateOnceAsClient
525
+ case "freely":
526
+ cfg.Renegotiation = tls.RenegotiateFreelyAsClient
527
+ default:
528
+ return nil, fmt.Errorf("invalid TLS renegotiation level: %v", t.Renegotiation)
529
+ }
530
+
531
+ // override for the server name used verify the TLS handshake
532
+ cfg.ServerName = repl.ReplaceKnown(cfg.ServerName, "")
533
+
534
+ // throw all security out the window
535
+ cfg.InsecureSkipVerify = t.InsecureSkipVerify
536
+
537
+ // only return a config if it's not empty
538
+ if reflect.DeepEqual(cfg, new(tls.Config)) {
539
+ return nil, nil
540
+ }
541
+
542
+ return cfg, nil
543
+}
544
+
545
+// The HTTPCertPool fetches the trusted root certificates from HTTP(S)
546
+// endpoints. The TLS connection properties can be customized, including custom
547
+// trusted root certificate. One example usage of this module is to get the trusted
548
+// certificates from another Caddy instance that is running the PKI app and ACME server.
549
+type HTTPCertPool struct {
550
+ // the list of URLs that respond with PEM-encoded certificates to trust.
551
+ Endpoints []string `json:"endpoints,omitempty"`
552
+
553
+ // Customize the TLS connection knobs to used during the HTTP call
554
+ TLS *TLSConfig `json:"tls,omitempty"`
555
+
556
+ pool *x509.CertPool
557
+}
558
+
559
+// CaddyModule implements caddy.Module.
560
+func (HTTPCertPool) CaddyModule() caddy.ModuleInfo {
561
+ return caddy.ModuleInfo{
562
+ ID: "tls.ca_pool.source.http",
563
+ New: func() caddy.Module {
564
+ return new(HTTPCertPool)
565
+ },
566
+ }
567
+}
568
+
569
+// Provision implements caddy.Provisioner.
570
+func (hcp *HTTPCertPool) Provision(ctx caddy.Context) error {
571
+ caPool := x509.NewCertPool()
572
+
573
+ customTransport := http.DefaultTransport.(*http.Transport).Clone()
574
+ if hcp.TLS != nil {
575
+ tlsConfig, err := hcp.TLS.makeTLSClientConfig(ctx)
576
+ if err != nil {
577
+ return err
578
+ }
579
+ customTransport.TLSClientConfig = tlsConfig
580
+ }
581
+
582
+ var httpClient *http.Client
583
+ *httpClient = *http.DefaultClient
584
+ httpClient.Transport = customTransport
585
+
586
+ for _, uri := range hcp.Endpoints {
587
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
588
+ if err != nil {
589
+ return err
590
+ }
591
+ res, err := httpClient.Do(req)
592
+ if err != nil {
593
+ return err
594
+ }
595
+ pembs, err := io.ReadAll(res.Body)
596
+ res.Body.Close()
597
+ if err != nil {
598
+ return err
599
+ }
600
+ if !caPool.AppendCertsFromPEM(pembs) {
601
+ return fmt.Errorf("failed to add certs from URL: %s", uri)
602
+ }
603
+ }
604
+ hcp.pool = caPool
605
+ return nil
606
+}
607
+
608
+// Syntax:
609
+//
610
+// trust_pool http [<endpoints...>] {
611
+// endpoints <endpoints...>
612
+// tls <tls_config>
613
+// }
614
+//
615
+// tls_config:
616
+//
617
+// ca <ca_module>
618
+// insecure_skip_verify
619
+// handshake_timeout <duration>
620
+// server_name <name>
621
+// renegotiation <never|once|freely>
622
+//
623
+// <ca_module> is the name of the CA module to source the trust
624
+//
625
+// certificate pool and follows the syntax of the named CA module.
626
+func (hcp *HTTPCertPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
627
+ d.Next() // consume module name
628
+ hcp.Endpoints = append(hcp.Endpoints, d.RemainingArgs()...)
629
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
630
+ switch d.Val() {
631
+ case "endpoints":
632
+ if d.CountRemainingArgs() == 0 {
633
+ return d.ArgErr()
634
+ }
635
+ hcp.Endpoints = append(hcp.Endpoints, d.RemainingArgs()...)
636
+ case "tls":
637
+ if hcp.TLS != nil {
638
+ return d.Err("tls block already defined")
639
+ }
640
+ hcp.TLS = new(TLSConfig)
641
+ if err := hcp.TLS.unmarshalCaddyfile(d); err != nil {
642
+ return err
643
+ }
644
+ default:
645
+ return d.Errf("unrecognized directive: %s", d.Val())
646
+ }
647
+ }
648
+
649
+ return nil
650
+}
651
+
652
+// report error if the endpoints are not valid URLs
653
+func (hcp HTTPCertPool) Validate() (err error) {
654
+ for _, u := range hcp.Endpoints {
655
+ _, e := url.Parse(u)
656
+ if e != nil {
657
+ err = errors.Join(err, e)
658
+ }
659
+ }
660
+ return err
661
+}
662
+
663
+// CertPool return the certificate pool generated from the HTTP responses
664
+func (hcp HTTPCertPool) CertPool() *x509.CertPool {
665
+ return hcp.pool
666
+}
667
+
668
+// LazyCertPool defers the generation of the certificate pool from the
669
+// guest module to demand-time rather than at provisionig time. The gain of the
670
+// lazy load adds a risk of failure to load the certificates at demand time
671
+// because the validation that's typically done at provisioning is deferred.
672
+// The validation can be enforced to run before runtime by setting
673
+// `EagerValidation`/`eager_validation` to `true`. It is the operator's responsibility
674
+// to ensure the resources are available if `EagerValidation`/`eager_validation`
675
+// is set to `true`. The module also incurs performance cost at every demand.
676
+type LazyCertPool struct {
677
+ // Provides the guest module that provides the trusted certificate authority (CA) certificates
678
+ CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
679
+
680
+ // Whether the validation step should try to load and provision the guest module to validate
681
+ // the correctness of the configuration. Depeneding on the type of the guest module,
682
+ // the resources may not be available at validation time. It is the
683
+ // operator's responsibility to ensure the resources are available if `EagerValidation`/`eager_validation`
684
+ // is set to `true`.
685
+ EagerValidation bool `json:"eager_validation,omitempty"`
686
+
687
+ ctx caddy.Context
688
+}
689
+
690
+// CaddyModule implements caddy.Module.
691
+func (LazyCertPool) CaddyModule() caddy.ModuleInfo {
692
+ return caddy.ModuleInfo{
693
+ ID: "tls.ca_pool.source.lazy",
694
+ New: func() caddy.Module {
695
+ return new(LazyCertPool)
696
+ },
697
+ }
698
+}
699
+
700
+// Provision implements caddy.Provisioner.
701
+func (lcp *LazyCertPool) Provision(ctx caddy.Context) error {
702
+ if len(lcp.CARaw) == 0 {
703
+ return fmt.Errorf("missing backing CA source")
704
+ }
705
+ lcp.ctx = ctx
706
+ return nil
707
+}
708
+
709
+// Syntax:
710
+//
711
+// trust_pool lazy {
712
+// backend <ca_module>
713
+// eager_validation
714
+// }
715
+//
716
+// The `backend` directive specifies the CA module to use to provision the
717
+// certificate pool. The `eager_validation` directive specifies that the
718
+// validation step should try to load and provision the guest module to validate
719
+// the correctness of the configuration. Depeneding on the type of the guest module,
720
+// the resources may not be available at validation time. It is the
721
+// operator's responsibility to ensure the resources are available if `EagerValidation`/`eager_validation`
722
+// is set to `true`.
723
+//
724
+// The `backend` directive is required.
725
+func (lcp *LazyCertPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
726
+ d.Next() // consume module name
727
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
728
+ switch d.Val() {
729
+ case "backend":
730
+ if lcp.CARaw != nil {
731
+ return d.Err("backend block already defined")
732
+ }
733
+ if !d.NextArg() {
734
+ return d.ArgErr()
735
+ }
736
+ modStem := d.Val()
737
+ modID := "tls.ca_pool.source." + modStem
738
+ unm, err := caddyfile.UnmarshalModule(d, modID)
739
+ if err != nil {
740
+ return err
741
+ }
742
+ backend, ok := unm.(CA)
743
+ if !ok {
744
+ return d.Errf("module %s is not a caddytls.CA", modID)
745
+ }
746
+ lcp.CARaw = caddyconfig.JSONModuleObject(backend, "provider", modStem, nil)
747
+ case "eager_validation":
748
+ lcp.EagerValidation = true
749
+ default:
750
+ return d.Errf("unrecognized directive: %s", d.Val())
751
+ }
752
+ }
753
+ if lcp.CARaw == nil {
754
+ return d.Err("backend block is required")
755
+ }
756
+ return nil
757
+}
758
+
759
+// If EagerValidation is `true`, it attempts to load and provision the guest module
760
+// to ensure the guesst module's configuration is correct. Depeneding on the type of the
761
+// guest module, the resources may not be available at validation time. It is the
762
+// operator's responsibility to ensure the resources are available if `EagerValidation` is
763
+// set to `true`.
764
+func (lcp LazyCertPool) Validate() error {
765
+ if lcp.EagerValidation {
766
+ _, err := lcp.ctx.LoadModule(lcp, "CARaw")
767
+ return err
768
+ }
769
+ return nil
770
+}
771
+
772
+// CertPool loads the guest module and returns the CertPool from there
773
+// TODO: Cache?
774
+func (lcp LazyCertPool) CertPool() *x509.CertPool {
775
+ caRaw, err := lcp.ctx.LoadModule(lcp, "CARaw")
776
+ if err != nil {
777
+ return nil
778
+ }
779
+ ca := caRaw.(CA)
780
+ return ca.CertPool()
781
+}
782
+
783
+var (
784
+ _ caddy.Module = (*InlineCAPool)(nil)
785
+ _ caddy.Provisioner = (*InlineCAPool)(nil)
786
+ _ CA = (*InlineCAPool)(nil)
787
+ _ caddyfile.Unmarshaler = (*InlineCAPool)(nil)
788
+
789
+ _ caddy.Module = (*FileCAPool)(nil)
790
+ _ caddy.Provisioner = (*FileCAPool)(nil)
791
+ _ CA = (*FileCAPool)(nil)
792
+ _ caddyfile.Unmarshaler = (*FileCAPool)(nil)
793
+
794
+ _ caddy.Module = (*PKIRootCAPool)(nil)
795
+ _ caddy.Provisioner = (*PKIRootCAPool)(nil)
796
+ _ CA = (*PKIRootCAPool)(nil)
797
+ _ caddyfile.Unmarshaler = (*PKIRootCAPool)(nil)
798
+
799
+ _ caddy.Module = (*PKIIntermediateCAPool)(nil)
800
+ _ caddy.Provisioner = (*PKIIntermediateCAPool)(nil)
801
+ _ CA = (*PKIIntermediateCAPool)(nil)
802
+ _ caddyfile.Unmarshaler = (*PKIIntermediateCAPool)(nil)
803
+
804
+ _ caddy.Module = (*StoragePool)(nil)
805
+ _ caddy.Provisioner = (*StoragePool)(nil)
806
+ _ CA = (*StoragePool)(nil)
807
+ _ caddyfile.Unmarshaler = (*StoragePool)(nil)
808
+
809
+ _ caddy.Module = (*HTTPCertPool)(nil)
810
+ _ caddy.Provisioner = (*HTTPCertPool)(nil)
811
+ _ caddy.Validator = (*HTTPCertPool)(nil)
812
+ _ CA = (*HTTPCertPool)(nil)
813
+ _ caddyfile.Unmarshaler = (*HTTPCertPool)(nil)
814
+
815
+ _ caddy.Module = (*LazyCertPool)(nil)
816
+ _ caddy.Provisioner = (*LazyCertPool)(nil)
817
+ _ caddy.Validator = (*LazyCertPool)(nil)
818
+ _ CA = (*LazyCertPool)(nil)
819
+ _ caddyfile.Unmarshaler = (*LazyCertPool)(nil)
820
+)