@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

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