@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 679 lines 17.8 KB
Raw
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 httpcaddyfile
16
17 import (
18 "slices"
19 "strconv"
20
21 "github.com/caddyserver/certmagic"
22 "github.com/libdns/libdns"
23 "github.com/mholt/acmez/v3/acme"
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/caddy/v2/caddyconfig"
27 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
28 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
29 "github.com/caddyserver/caddy/v2/modules/caddytls"
30 )
31
32 func init() {
33 RegisterGlobalOption("debug", parseOptTrue)
34 RegisterGlobalOption("http_port", parseOptHTTPPort)
35 RegisterGlobalOption("https_port", parseOptHTTPSPort)
36 RegisterGlobalOption("default_bind", parseOptDefaultBind)
37 RegisterGlobalOption("grace_period", parseOptDuration)
38 RegisterGlobalOption("shutdown_delay", parseOptDuration)
39 RegisterGlobalOption("default_sni", parseOptSingleString)
40 RegisterGlobalOption("fallback_sni", parseOptSingleString)
41 RegisterGlobalOption("order", parseOptOrder)
42 RegisterGlobalOption("storage", parseOptStorage)
43 RegisterGlobalOption("storage_check", parseStorageCheck)
44 RegisterGlobalOption("storage_clean_interval", parseStorageCleanInterval)
45 RegisterGlobalOption("renew_interval", parseOptDuration)
46 RegisterGlobalOption("ocsp_interval", parseOptDuration)
47 RegisterGlobalOption("acme_ca", parseOptSingleString)
48 RegisterGlobalOption("acme_ca_root", parseOptSingleString)
49 RegisterGlobalOption("acme_dns", parseOptDNS)
50 RegisterGlobalOption("acme_eab", parseOptACMEEAB)
51 RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
52 RegisterGlobalOption("skip_install_trust", parseOptTrue)
53 RegisterGlobalOption("email", parseOptSingleString)
54 RegisterGlobalOption("admin", parseOptAdmin)
55 RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
56 RegisterGlobalOption("local_certs", parseOptTrue)
57 RegisterGlobalOption("key_type", parseOptSingleString)
58 RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
59 RegisterGlobalOption("metrics", parseMetricsOptions)
60 RegisterGlobalOption("servers", parseServerOptions)
61 RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
62 RegisterGlobalOption("cert_lifetime", parseOptDuration)
63 RegisterGlobalOption("log", parseLogOptions)
64 RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
65 RegisterGlobalOption("persist_config", parseOptPersistConfig)
66 RegisterGlobalOption("dns", parseOptDNS)
67 RegisterGlobalOption("tls_resolvers", parseOptTLSResolvers)
68 RegisterGlobalOption("tls_automate_names", parseOptAutomateNames)
69 RegisterGlobalOption("ech", parseOptECH)
70 RegisterGlobalOption("renewal_window_ratio", parseOptRenewalWindowRatio)
71 }
72
73 func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
74
75 func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
76 d.Next() // consume option name
77 var httpPort int
78 var httpPortStr string
79 if !d.AllArgs(&httpPortStr) {
80 return 0, d.ArgErr()
81 }
82 var err error
83 httpPort, err = strconv.Atoi(httpPortStr)
84 if err != nil {
85 return 0, d.Errf("converting port '%s' to integer value: %v", httpPortStr, err)
86 }
87 return httpPort, nil
88 }
89
90 func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
91 d.Next() // consume option name
92 var httpsPort int
93 var httpsPortStr string
94 if !d.AllArgs(&httpsPortStr) {
95 return 0, d.ArgErr()
96 }
97 var err error
98 httpsPort, err = strconv.Atoi(httpsPortStr)
99 if err != nil {
100 return 0, d.Errf("converting port '%s' to integer value: %v", httpsPortStr, err)
101 }
102 return httpsPort, nil
103 }
104
105 func parseOptOrder(d *caddyfile.Dispenser, existing any) (any, error) {
106 d.Next() // consume option name
107
108 // get directive name
109 if !d.Next() {
110 return nil, d.ArgErr()
111 }
112 dirName := d.Val()
113 if _, ok := registeredDirectives[dirName]; !ok {
114 return nil, d.Errf("%s is not a registered directive", dirName)
115 }
116
117 // get positional token
118 if !d.Next() {
119 return nil, d.ArgErr()
120 }
121 pos := Positional(d.Val())
122
123 // build on what an earlier order option in this same Caddyfile produced;
124 // the first one starts from a clone of the order the plugins registered,
125 // so from here on we're only ever editing a slice this adaptation owns
126 order, ok := existing.([]string)
127 if !ok {
128 order = slices.Clone(directiveOrder)
129 }
130
131 // if directive already had an order, drop it
132 newOrder := slices.DeleteFunc(order, func(d string) bool {
133 return d == dirName
134 })
135
136 // act on the positional; if it's First or Last, we're done right away
137 switch pos {
138 case First:
139 newOrder = append([]string{dirName}, newOrder...)
140 if d.NextArg() {
141 return nil, d.ArgErr()
142 }
143 return newOrder, nil
144
145 case Last:
146 newOrder = append(newOrder, dirName)
147 if d.NextArg() {
148 return nil, d.ArgErr()
149 }
150 return newOrder, nil
151
152 // if it's Before or After, continue
153 case Before:
154 case After:
155
156 default:
157 return nil, d.Errf("unknown positional '%s'", pos)
158 }
159
160 // get name of other directive
161 if !d.NextArg() {
162 return nil, d.ArgErr()
163 }
164 otherDir := d.Val()
165 if d.NextArg() {
166 return nil, d.ArgErr()
167 }
168
169 // get the position of the target directive
170 targetIndex := slices.Index(newOrder, otherDir)
171 if targetIndex == -1 {
172 return nil, d.Errf("directive '%s' not found", otherDir)
173 }
174 // if we're inserting after, we need to increment the index to go after
175 if pos == After {
176 targetIndex++
177 }
178 // insert the directive into the new order
179 newOrder = slices.Insert(newOrder, targetIndex, dirName)
180
181 return newOrder, nil
182 }
183
184 func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
185 if !d.Next() { // consume option name
186 return nil, d.ArgErr()
187 }
188 if !d.Next() { // get storage module name
189 return nil, d.ArgErr()
190 }
191 modID := "caddy.storage." + d.Val()
192 unm, err := caddyfile.UnmarshalModule(d, modID)
193 if err != nil {
194 return nil, err
195 }
196 storage, ok := unm.(caddy.StorageConverter)
197 if !ok {
198 return nil, d.Errf("module %s is not a caddy.StorageConverter", modID)
199 }
200 return storage, nil
201 }
202
203 func parseStorageCheck(d *caddyfile.Dispenser, _ any) (any, error) {
204 d.Next() // consume option name
205 if !d.Next() {
206 return "", d.ArgErr()
207 }
208 val := d.Val()
209 if d.Next() {
210 return "", d.ArgErr()
211 }
212 if val != "off" {
213 return "", d.Errf("storage_check must be 'off'")
214 }
215 return val, nil
216 }
217
218 func parseStorageCleanInterval(d *caddyfile.Dispenser, _ any) (any, error) {
219 d.Next() // consume option name
220 if !d.Next() {
221 return "", d.ArgErr()
222 }
223 val := d.Val()
224 if d.Next() {
225 return "", d.ArgErr()
226 }
227 if val == "off" {
228 return false, nil
229 }
230 dur, err := caddy.ParseDuration(d.Val())
231 if err != nil {
232 return nil, d.Errf("failed to parse storage_clean_interval, must be a duration or 'off' %w", err)
233 }
234 return caddy.Duration(dur), nil
235 }
236
237 func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
238 if !d.Next() { // consume option name
239 return nil, d.ArgErr()
240 }
241 if !d.Next() { // get duration value
242 return nil, d.ArgErr()
243 }
244 dur, err := caddy.ParseDuration(d.Val())
245 if err != nil {
246 return nil, err
247 }
248 return caddy.Duration(dur), nil
249 }
250
251 func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
252 eab := new(acme.EAB)
253 d.Next() // consume option name
254 if d.NextArg() {
255 return nil, d.ArgErr()
256 }
257 for d.NextBlock(0) {
258 switch d.Val() {
259 case "key_id":
260 if !d.NextArg() {
261 return nil, d.ArgErr()
262 }
263 eab.KeyID = d.Val()
264
265 case "mac_key":
266 if !d.NextArg() {
267 return nil, d.ArgErr()
268 }
269 eab.MACKey = d.Val()
270
271 default:
272 return nil, d.Errf("unrecognized parameter '%s'", d.Val())
273 }
274 }
275 return eab, nil
276 }
277
278 func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
279 d.Next() // consume option name
280
281 var issuers []certmagic.Issuer
282 if existing != nil {
283 issuers = existing.([]certmagic.Issuer)
284 }
285
286 // get issuer module name
287 if !d.Next() {
288 return nil, d.ArgErr()
289 }
290 modID := "tls.issuance." + d.Val()
291 unm, err := caddyfile.UnmarshalModule(d, modID)
292 if err != nil {
293 return nil, err
294 }
295 iss, ok := unm.(certmagic.Issuer)
296 if !ok {
297 return nil, d.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm)
298 }
299 issuers = append(issuers, iss)
300 return issuers, nil
301 }
302
303 func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
304 d.Next() // consume option name
305 if !d.Next() {
306 return "", d.ArgErr()
307 }
308 val := d.Val()
309 if d.Next() {
310 return "", d.ArgErr()
311 }
312 return val, nil
313 }
314
315 func parseOptTLSResolvers(d *caddyfile.Dispenser, _ any) (any, error) {
316 d.Next() // consume option name
317 resolvers := d.RemainingArgs()
318 if len(resolvers) == 0 {
319 return nil, d.ArgErr()
320 }
321 return resolvers, nil
322 }
323
324 // parseOptAutomateNames parses the tls_automate_names global option, which
325 // names subjects to manage certificates for without serving them. Repeating
326 // the option appends to the list rather than replacing it, so a long list can
327 // be split over several lines.
328 func parseOptAutomateNames(d *caddyfile.Dispenser, existing any) (any, error) {
329 d.Next() // consume option name
330 names := d.RemainingArgs()
331 if len(names) == 0 {
332 return nil, d.ArgErr()
333 }
334 if previous, ok := existing.([]string); ok {
335 names = append(previous, names...)
336 }
337 return names, nil
338 }
339
340 func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
341 d.Next() // consume option name
342
343 var addresses, protocols []string
344 addresses = d.RemainingArgs()
345
346 if len(addresses) == 0 {
347 addresses = append(addresses, "")
348 }
349
350 for d.NextBlock(0) {
351 switch d.Val() {
352 case "protocols":
353 protocols = d.RemainingArgs()
354 if len(protocols) == 0 {
355 return nil, d.Errf("protocols requires one or more arguments")
356 }
357 default:
358 return nil, d.Errf("unknown subdirective: %s", d.Val())
359 }
360 }
361
362 return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
363 addresses: addresses,
364 protocols: protocols,
365 }}}, nil
366 }
367
368 func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
369 d.Next() // consume option name
370
371 adminCfg := new(caddy.AdminConfig)
372 if d.NextArg() {
373 listenAddress := d.Val()
374 if listenAddress == "off" {
375 adminCfg.Disabled = true
376 if d.Next() { // Do not accept any remaining options including block
377 return nil, d.Err("No more option is allowed after turning off admin config")
378 }
379 } else {
380 adminCfg.Listen = listenAddress
381 if d.NextArg() { // At most 1 arg is allowed
382 return nil, d.ArgErr()
383 }
384 }
385 }
386 for d.NextBlock(0) {
387 switch d.Val() {
388 case "enforce_origin":
389 adminCfg.EnforceOrigin = true
390
391 case "origins":
392 adminCfg.Origins = d.RemainingArgs()
393
394 default:
395 return nil, d.Errf("unrecognized parameter '%s'", d.Val())
396 }
397 }
398 if adminCfg.Listen == "" && !adminCfg.Disabled {
399 adminCfg.Listen = caddy.DefaultAdminListen
400 }
401 return adminCfg, nil
402 }
403
404 func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
405 d.Next() // consume option name
406 if d.NextArg() {
407 return nil, d.ArgErr()
408 }
409
410 var ond *caddytls.OnDemandConfig
411
412 for nesting := d.Nesting(); d.NextBlock(nesting); {
413 switch d.Val() {
414 case "ask":
415 if !d.NextArg() {
416 return nil, d.ArgErr()
417 }
418 if ond == nil {
419 ond = new(caddytls.OnDemandConfig)
420 }
421 if ond.PermissionRaw != nil {
422 return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
423 }
424 perm := caddytls.PermissionByHTTP{Endpoint: d.Val()}
425 ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", "http", nil)
426
427 case "permission":
428 if !d.NextArg() {
429 return nil, d.ArgErr()
430 }
431 if ond == nil {
432 ond = new(caddytls.OnDemandConfig)
433 }
434 if ond.PermissionRaw != nil {
435 return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
436 }
437 modName := d.Val()
438 modID := "tls.permission." + modName
439 unm, err := caddyfile.UnmarshalModule(d, modID)
440 if err != nil {
441 return nil, err
442 }
443 perm, ok := unm.(caddytls.OnDemandPermission)
444 if !ok {
445 return nil, d.Errf("module %s (%T) is not an on-demand TLS permission module", modID, unm)
446 }
447 ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
448
449 case "interval":
450 return nil, d.Errf("the on_demand_tls 'interval' option is no longer supported, remove it from your config")
451
452 case "burst":
453 return nil, d.Errf("the on_demand_tls 'burst' option is no longer supported, remove it from your config")
454
455 default:
456 return nil, d.Errf("unrecognized parameter '%s'", d.Val())
457 }
458 }
459 if ond == nil {
460 return nil, d.Err("expected at least one config parameter for on_demand_tls")
461 }
462 return ond, nil
463 }
464
465 func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
466 d.Next() // consume option name
467 if !d.Next() {
468 return "", d.ArgErr()
469 }
470 val := d.Val()
471 if d.Next() {
472 return "", d.ArgErr()
473 }
474 if val != "off" {
475 return "", d.Errf("persist_config must be 'off'")
476 }
477 return val, nil
478 }
479
480 func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
481 d.Next() // consume option name
482 val := d.RemainingArgs()
483 if len(val) == 0 {
484 return "", d.ArgErr()
485 }
486 for _, v := range val {
487 switch v {
488 case "off":
489 case "disable_redirects":
490 case "disable_certs":
491 case "ignore_loaded_certs":
492 default:
493 return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
494 }
495 }
496 return val, nil
497 }
498
499 func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
500 d.Next() // consume option name
501 metrics := new(caddyhttp.Metrics)
502 for d.NextBlock(0) {
503 switch d.Val() {
504 case "per_host":
505 metrics.PerHost = true
506 case "observe_catchall_hosts":
507 metrics.ObserveCatchallHosts = true
508 case "otlp":
509 metrics.OTLP = true
510 default:
511 return nil, d.Errf("unrecognized servers option '%s'", d.Val())
512 }
513 }
514 return metrics, nil
515 }
516
517 func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
518 return unmarshalCaddyfileMetricsOptions(d)
519 }
520
521 func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
522 return unmarshalCaddyfileServerOptions(d)
523 }
524
525 func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
526 d.Next() // consume option name
527 var val string
528 if !d.AllArgs(&val) {
529 return nil, d.ArgErr()
530 }
531 if val != "off" {
532 return nil, d.Errf("invalid argument '%s'", val)
533 }
534 return certmagic.OCSPConfig{
535 DisableStapling: val == "off",
536 }, nil
537 }
538
539 // parseLogOptions parses the global log option. Syntax:
540 //
541 // log [name] {
542 // output <writer_module> ...
543 // format <encoder_module> ...
544 // level <level>
545 // include <namespaces...>
546 // exclude <namespaces...>
547 // }
548 //
549 // When the name argument is unspecified, this directive modifies the default
550 // logger.
551 func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
552 currentNames := make(map[string]struct{})
553 if existingVal != nil {
554 innerVals, ok := existingVal.([]ConfigValue)
555 if !ok {
556 return nil, d.Errf("existing log values of unexpected type: %T", existingVal)
557 }
558 for _, rawVal := range innerVals {
559 val, ok := rawVal.Value.(namedCustomLog)
560 if !ok {
561 return nil, d.Errf("existing log value of unexpected type: %T", existingVal)
562 }
563 currentNames[val.name] = struct{}{}
564 }
565 }
566
567 var warnings []caddyconfig.Warning
568 // Call out the same parser that handles server-specific log configuration.
569 configValues, err := parseLogHelper(
570 Helper{
571 Dispenser: d,
572 warnings: &warnings,
573 },
574 currentNames,
575 )
576 if err != nil {
577 return nil, err
578 }
579 if len(warnings) > 0 {
580 return nil, d.Errf("warnings found in parsing global log options: %+v", warnings)
581 }
582
583 return configValues, nil
584 }
585
586 func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
587 d.Next()
588 return caddytls.ParseCaddyfilePreferredChainsOptions(d)
589 }
590
591 func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
592 d.Next() // consume option name
593 optName := d.Val()
594
595 // get DNS module name
596 if !d.Next() {
597 // this is allowed if this is the "acme_dns" option since it may refer to the globally-configured "dns" option's value
598 if optName == "acme_dns" {
599 return nil, nil
600 }
601 return nil, d.ArgErr()
602 }
603 modID := "dns.providers." + d.Val()
604 unm, err := caddyfile.UnmarshalModule(d, modID)
605 if err != nil {
606 return nil, err
607 }
608 switch unm.(type) {
609 case libdns.RecordGetter,
610 libdns.RecordSetter,
611 libdns.RecordAppender,
612 libdns.RecordDeleter:
613 default:
614 return nil, d.Errf("module %s (%T) is not a libdns provider", modID, unm)
615 }
616 return unm, nil
617 }
618
619 func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
620 d.Next() // consume option name
621
622 ech := new(caddytls.ECH)
623
624 publicNames := d.RemainingArgs()
625 for _, publicName := range publicNames {
626 ech.Configs = append(ech.Configs, caddytls.ECHConfiguration{
627 PublicName: publicName,
628 })
629 }
630 if len(ech.Configs) == 0 {
631 return nil, d.ArgErr()
632 }
633
634 for nesting := d.Nesting(); d.NextBlock(nesting); {
635 switch d.Val() {
636 case "dns":
637 if !d.Next() {
638 return nil, d.ArgErr()
639 }
640 providerName := d.Val()
641 modID := "dns.providers." + providerName
642 unm, err := caddyfile.UnmarshalModule(d, modID)
643 if err != nil {
644 return nil, err
645 }
646 ech.Publication = append(ech.Publication, &caddytls.ECHPublication{
647 Configs: publicNames,
648 PublishersRaw: caddy.ModuleMap{
649 "dns": caddyconfig.JSON(caddytls.ECHDNSPublisher{
650 ProviderRaw: caddyconfig.JSONModuleObject(unm, "name", providerName, nil),
651 }, nil),
652 },
653 })
654 default:
655 return nil, d.Errf("ech: unrecognized subdirective '%s'", d.Val())
656 }
657 }
658
659 return ech, nil
660 }
661
662 func parseOptRenewalWindowRatio(d *caddyfile.Dispenser, _ any) (any, error) {
663 d.Next() // consume option name
664 if !d.Next() {
665 return 0, d.ArgErr()
666 }
667 val := d.Val()
668 ratio, err := strconv.ParseFloat(val, 64)
669 if err != nil {
670 return 0, d.Errf("parsing renewal_window_ratio: %v", err)
671 }
672 if ratio <= 0 || ratio >= 1 {
673 return 0, d.Errf("renewal_window_ratio must be between 0 and 1 (exclusive)")
674 }
675 if d.Next() {
676 return 0, d.ArgErr()
677 }
678 return ratio, nil
679 }