mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-30 18:22:49 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			347 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package httpcaddyfile
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"slices"
 | |
| 
 | |
| 	"github.com/dustin/go-humanize"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2"
 | |
| 	"github.com/caddyserver/caddy/v2/caddyconfig"
 | |
| 	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | |
| 	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 | |
| )
 | |
| 
 | |
| // serverOptions collects server config overrides parsed from Caddyfile global options
 | |
| type serverOptions struct {
 | |
| 	// If set, will only apply these options to servers that contain a
 | |
| 	// listener address that matches exactly. If empty, will apply to all
 | |
| 	// servers that were not already matched by another serverOptions.
 | |
| 	ListenerAddress string
 | |
| 
 | |
| 	// These will all map 1:1 to the caddyhttp.Server struct
 | |
| 	Name                 string
 | |
| 	ListenerWrappersRaw  []json.RawMessage
 | |
| 	ReadTimeout          caddy.Duration
 | |
| 	ReadHeaderTimeout    caddy.Duration
 | |
| 	WriteTimeout         caddy.Duration
 | |
| 	IdleTimeout          caddy.Duration
 | |
| 	KeepAliveInterval    caddy.Duration
 | |
| 	MaxHeaderBytes       int
 | |
| 	EnableFullDuplex     bool
 | |
| 	Protocols            []string
 | |
| 	StrictSNIHost        *bool
 | |
| 	TrustedProxiesRaw    json.RawMessage
 | |
| 	TrustedProxiesStrict int
 | |
| 	ClientIPHeaders      []string
 | |
| 	ShouldLogCredentials bool
 | |
| 	Metrics              *caddyhttp.Metrics
 | |
| 	Trace                bool // TODO: EXPERIMENTAL
 | |
| }
 | |
| 
 | |
| func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
 | |
| 	d.Next() // consume option name
 | |
| 
 | |
| 	serverOpts := serverOptions{}
 | |
| 	if d.NextArg() {
 | |
| 		serverOpts.ListenerAddress = d.Val()
 | |
| 		if d.NextArg() {
 | |
| 			return nil, d.ArgErr()
 | |
| 		}
 | |
| 	}
 | |
| 	for d.NextBlock(0) {
 | |
| 		switch d.Val() {
 | |
| 		case "name":
 | |
| 			if serverOpts.ListenerAddress == "" {
 | |
| 				return nil, d.Errf("cannot set a name for a server without a listener address")
 | |
| 			}
 | |
| 			if !d.NextArg() {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			serverOpts.Name = d.Val()
 | |
| 
 | |
| 		case "listener_wrappers":
 | |
| 			for nesting := d.Nesting(); d.NextBlock(nesting); {
 | |
| 				modID := "caddy.listeners." + d.Val()
 | |
| 				unm, err := caddyfile.UnmarshalModule(d, modID)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				listenerWrapper, ok := unm.(caddy.ListenerWrapper)
 | |
| 				if !ok {
 | |
| 					return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm)
 | |
| 				}
 | |
| 				jsonListenerWrapper := caddyconfig.JSONModuleObject(
 | |
| 					listenerWrapper,
 | |
| 					"wrapper",
 | |
| 					listenerWrapper.(caddy.Module).CaddyModule().ID.Name(),
 | |
| 					nil,
 | |
| 				)
 | |
| 				serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
 | |
| 			}
 | |
| 
 | |
| 		case "timeouts":
 | |
| 			for nesting := d.Nesting(); d.NextBlock(nesting); {
 | |
| 				switch d.Val() {
 | |
| 				case "read_body":
 | |
| 					if !d.NextArg() {
 | |
| 						return nil, d.ArgErr()
 | |
| 					}
 | |
| 					dur, err := caddy.ParseDuration(d.Val())
 | |
| 					if err != nil {
 | |
| 						return nil, d.Errf("parsing read_body timeout duration: %v", err)
 | |
| 					}
 | |
| 					serverOpts.ReadTimeout = caddy.Duration(dur)
 | |
| 
 | |
| 				case "read_header":
 | |
| 					if !d.NextArg() {
 | |
| 						return nil, d.ArgErr()
 | |
| 					}
 | |
| 					dur, err := caddy.ParseDuration(d.Val())
 | |
| 					if err != nil {
 | |
| 						return nil, d.Errf("parsing read_header timeout duration: %v", err)
 | |
| 					}
 | |
| 					serverOpts.ReadHeaderTimeout = caddy.Duration(dur)
 | |
| 
 | |
| 				case "write":
 | |
| 					if !d.NextArg() {
 | |
| 						return nil, d.ArgErr()
 | |
| 					}
 | |
| 					dur, err := caddy.ParseDuration(d.Val())
 | |
| 					if err != nil {
 | |
| 						return nil, d.Errf("parsing write timeout duration: %v", err)
 | |
| 					}
 | |
| 					serverOpts.WriteTimeout = caddy.Duration(dur)
 | |
| 
 | |
| 				case "idle":
 | |
| 					if !d.NextArg() {
 | |
| 						return nil, d.ArgErr()
 | |
| 					}
 | |
| 					dur, err := caddy.ParseDuration(d.Val())
 | |
| 					if err != nil {
 | |
| 						return nil, d.Errf("parsing idle timeout duration: %v", err)
 | |
| 					}
 | |
| 					serverOpts.IdleTimeout = caddy.Duration(dur)
 | |
| 
 | |
| 				default:
 | |
| 					return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
 | |
| 				}
 | |
| 			}
 | |
| 		case "keepalive_interval":
 | |
| 			if !d.NextArg() {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			dur, err := caddy.ParseDuration(d.Val())
 | |
| 			if err != nil {
 | |
| 				return nil, d.Errf("parsing keepalive interval duration: %v", err)
 | |
| 			}
 | |
| 			serverOpts.KeepAliveInterval = caddy.Duration(dur)
 | |
| 
 | |
| 		case "max_header_size":
 | |
| 			var sizeStr string
 | |
| 			if !d.AllArgs(&sizeStr) {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			size, err := humanize.ParseBytes(sizeStr)
 | |
| 			if err != nil {
 | |
| 				return nil, d.Errf("parsing max_header_size: %v", err)
 | |
| 			}
 | |
| 			serverOpts.MaxHeaderBytes = int(size)
 | |
| 
 | |
| 		case "enable_full_duplex":
 | |
| 			if d.NextArg() {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			serverOpts.EnableFullDuplex = true
 | |
| 
 | |
| 		case "log_credentials":
 | |
| 			if d.NextArg() {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			serverOpts.ShouldLogCredentials = true
 | |
| 
 | |
| 		case "protocols":
 | |
| 			protos := d.RemainingArgs()
 | |
| 			for _, proto := range protos {
 | |
| 				if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
 | |
| 					return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
 | |
| 				}
 | |
| 				if slices.Contains(serverOpts.Protocols, proto) {
 | |
| 					return nil, d.Errf("protocol %s specified more than once", proto)
 | |
| 				}
 | |
| 				serverOpts.Protocols = append(serverOpts.Protocols, proto)
 | |
| 			}
 | |
| 			if nesting := d.Nesting(); d.NextBlock(nesting) {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 
 | |
| 		case "strict_sni_host":
 | |
| 			if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
 | |
| 				return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
 | |
| 			}
 | |
| 			boolVal := true
 | |
| 			if d.Val() == "insecure_off" {
 | |
| 				boolVal = false
 | |
| 			}
 | |
| 			serverOpts.StrictSNIHost = &boolVal
 | |
| 
 | |
| 		case "trusted_proxies":
 | |
| 			if !d.NextArg() {
 | |
| 				return nil, d.Err("trusted_proxies expects an IP range source module name as its first argument")
 | |
| 			}
 | |
| 			modID := "http.ip_sources." + d.Val()
 | |
| 			unm, err := caddyfile.UnmarshalModule(d, modID)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			source, ok := unm.(caddyhttp.IPRangeSource)
 | |
| 			if !ok {
 | |
| 				return nil, fmt.Errorf("module %s (%T) is not an IP range source", modID, unm)
 | |
| 			}
 | |
| 			jsonSource := caddyconfig.JSONModuleObject(
 | |
| 				source,
 | |
| 				"source",
 | |
| 				source.(caddy.Module).CaddyModule().ID.Name(),
 | |
| 				nil,
 | |
| 			)
 | |
| 			serverOpts.TrustedProxiesRaw = jsonSource
 | |
| 
 | |
| 		case "trusted_proxies_strict":
 | |
| 			if d.NextArg() {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			serverOpts.TrustedProxiesStrict = 1
 | |
| 
 | |
| 		case "client_ip_headers":
 | |
| 			headers := d.RemainingArgs()
 | |
| 			for _, header := range headers {
 | |
| 				if slices.Contains(serverOpts.ClientIPHeaders, header) {
 | |
| 					return nil, d.Errf("client IP header %s specified more than once", header)
 | |
| 				}
 | |
| 				serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
 | |
| 			}
 | |
| 			if nesting := d.Nesting(); d.NextBlock(nesting) {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 
 | |
| 		case "metrics":
 | |
| 			caddy.Log().Warn("The nested 'metrics' option inside `servers` is deprecated and will be removed in the next major version. Use the global 'metrics' option instead.")
 | |
| 			serverOpts.Metrics = new(caddyhttp.Metrics)
 | |
| 			for nesting := d.Nesting(); d.NextBlock(nesting); {
 | |
| 				switch d.Val() {
 | |
| 				case "per_host":
 | |
| 					serverOpts.Metrics.PerHost = true
 | |
| 				default:
 | |
| 					return nil, d.Errf("unrecognized metrics option '%s'", d.Val())
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		case "trace":
 | |
| 			if d.NextArg() {
 | |
| 				return nil, d.ArgErr()
 | |
| 			}
 | |
| 			serverOpts.Trace = true
 | |
| 
 | |
| 		default:
 | |
| 			return nil, d.Errf("unrecognized servers option '%s'", d.Val())
 | |
| 		}
 | |
| 	}
 | |
| 	return serverOpts, nil
 | |
| }
 | |
| 
 | |
| // applyServerOptions sets the server options on the appropriate servers
 | |
| func applyServerOptions(
 | |
| 	servers map[string]*caddyhttp.Server,
 | |
| 	options map[string]any,
 | |
| 	_ *[]caddyconfig.Warning,
 | |
| ) error {
 | |
| 	serverOpts, ok := options["servers"].([]serverOptions)
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// check for duplicate names, which would clobber the config
 | |
| 	existingNames := map[string]bool{}
 | |
| 	for _, opts := range serverOpts {
 | |
| 		if opts.Name == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		if existingNames[opts.Name] {
 | |
| 			return fmt.Errorf("cannot use duplicate server name '%s'", opts.Name)
 | |
| 		}
 | |
| 		existingNames[opts.Name] = true
 | |
| 	}
 | |
| 
 | |
| 	// collect the server name overrides
 | |
| 	nameReplacements := map[string]string{}
 | |
| 
 | |
| 	for key, server := range servers {
 | |
| 		// find the options that apply to this server
 | |
| 		optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool {
 | |
| 			return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress)
 | |
| 		})
 | |
| 
 | |
| 		// if none apply, then move to the next server
 | |
| 		if optsIndex == -1 {
 | |
| 			continue
 | |
| 		}
 | |
| 		opts := serverOpts[optsIndex]
 | |
| 
 | |
| 		// set all the options
 | |
| 		server.ListenerWrappersRaw = opts.ListenerWrappersRaw
 | |
| 		server.ReadTimeout = opts.ReadTimeout
 | |
| 		server.ReadHeaderTimeout = opts.ReadHeaderTimeout
 | |
| 		server.WriteTimeout = opts.WriteTimeout
 | |
| 		server.IdleTimeout = opts.IdleTimeout
 | |
| 		server.KeepAliveInterval = opts.KeepAliveInterval
 | |
| 		server.MaxHeaderBytes = opts.MaxHeaderBytes
 | |
| 		server.EnableFullDuplex = opts.EnableFullDuplex
 | |
| 		server.Protocols = opts.Protocols
 | |
| 		server.StrictSNIHost = opts.StrictSNIHost
 | |
| 		server.TrustedProxiesRaw = opts.TrustedProxiesRaw
 | |
| 		server.ClientIPHeaders = opts.ClientIPHeaders
 | |
| 		server.TrustedProxiesStrict = opts.TrustedProxiesStrict
 | |
| 		server.Metrics = opts.Metrics
 | |
| 		if opts.ShouldLogCredentials {
 | |
| 			if server.Logs == nil {
 | |
| 				server.Logs = new(caddyhttp.ServerLogConfig)
 | |
| 			}
 | |
| 			server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
 | |
| 		}
 | |
| 		if opts.Trace {
 | |
| 			// TODO: THIS IS EXPERIMENTAL (MAY 2024)
 | |
| 			if server.Logs == nil {
 | |
| 				server.Logs = new(caddyhttp.ServerLogConfig)
 | |
| 			}
 | |
| 			server.Logs.Trace = opts.Trace
 | |
| 		}
 | |
| 
 | |
| 		if opts.Name != "" {
 | |
| 			nameReplacements[key] = opts.Name
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// rename the servers if marked to do so
 | |
| 	for old, new := range nameReplacements {
 | |
| 		servers[new] = servers[old]
 | |
| 		delete(servers, old)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |