2020-11-23 14:46:50 -05:00
// 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"
2024-09-25 15:30:56 -05:00
"slices"
2020-11-23 14:46:50 -05:00
2023-08-14 10:41:15 -05:00
"github.com/dustin/go-humanize"
2020-11-23 14:46:50 -05:00
"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
2023-01-27 14:56:39 -05:00
Name string
2021-12-02 15:26:24 -05:00
ListenerWrappersRaw [ ] json . RawMessage
ReadTimeout caddy . Duration
ReadHeaderTimeout caddy . Duration
WriteTimeout caddy . Duration
IdleTimeout caddy . Duration
2022-09-02 17:59:11 -05:00
KeepAliveInterval caddy . Duration
2021-12-02 15:26:24 -05:00
MaxHeaderBytes int
2023-08-02 15:03:26 -05:00
EnableFullDuplex bool
2022-08-15 13:01:58 -05:00
Protocols [ ] string
2021-12-02 15:26:24 -05:00
StrictSNIHost * bool
2023-02-06 14:44:11 -05:00
TrustedProxiesRaw json . RawMessage
2024-01-13 15:46:37 -05:00
TrustedProxiesStrict int
2023-03-27 15:22:59 -05:00
ClientIPHeaders [ ] string
2021-12-02 15:26:24 -05:00
ShouldLogCredentials bool
2022-09-16 14:32:49 -05:00
Metrics * caddyhttp . Metrics
2024-05-18 15:48:42 -05:00
Trace bool // TODO: EXPERIMENTAL
2020-11-23 14:46:50 -05:00
}
2022-08-02 15:39:09 -05:00
func unmarshalCaddyfileServerOptions ( d * caddyfile . Dispenser ) ( any , error ) {
2024-01-23 19:36:59 -05:00
d . Next ( ) // consume option name
2020-11-23 14:46:50 -05:00
serverOpts := serverOptions { }
2024-01-23 19:36:59 -05:00
if d . NextArg ( ) {
serverOpts . ListenerAddress = d . Val ( )
2020-11-23 14:46:50 -05:00
if d . NextArg ( ) {
2024-01-23 19:36:59 -05:00
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 ( ) {
2020-11-23 14:46:50 -05:00
return nil , d . ArgErr ( )
}
2024-01-23 19:36:59 -05:00
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
2023-01-27 14:56:39 -05:00
}
2024-01-23 19:36:59 -05:00
listenerWrapper , ok := unm . ( caddy . ListenerWrapper )
if ! ok {
return nil , fmt . Errorf ( "module %s (%T) is not a listener wrapper" , modID , unm )
2023-01-27 14:56:39 -05:00
}
2024-01-23 19:36:59 -05:00
jsonListenerWrapper := caddyconfig . JSONModuleObject (
listenerWrapper ,
"wrapper" ,
listenerWrapper . ( caddy . Module ) . CaddyModule ( ) . ID . Name ( ) ,
nil ,
)
serverOpts . ListenerWrappersRaw = append ( serverOpts . ListenerWrappersRaw , jsonListenerWrapper )
}
2023-01-27 14:56:39 -05:00
2024-01-23 19:36:59 -05:00
case "timeouts" :
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
switch d . Val ( ) {
case "read_body" :
if ! d . NextArg ( ) {
return nil , d . ArgErr ( )
2020-11-23 14:46:50 -05:00
}
2024-01-23 19:36:59 -05:00
dur , err := caddy . ParseDuration ( d . Val ( ) )
if err != nil {
return nil , d . Errf ( "parsing read_body timeout duration: %v" , err )
2020-11-23 14:46:50 -05:00
}
2024-01-23 19:36:59 -05:00
serverOpts . ReadTimeout = caddy . Duration ( dur )
2020-11-23 14:46:50 -05:00
2024-01-23 19:36:59 -05:00
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 )
2020-11-23 14:46:50 -05:00
2024-01-23 19:36:59 -05:00
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 )
2020-11-23 14:46:50 -05:00
2024-01-23 19:36:59 -05:00
case "idle" :
if ! d . NextArg ( ) {
return nil , d . ArgErr ( )
2020-11-23 14:46:50 -05:00
}
2024-01-23 19:36:59 -05:00
dur , err := caddy . ParseDuration ( d . Val ( ) )
if err != nil {
return nil , d . Errf ( "parsing idle timeout duration: %v" , err )
}
serverOpts . IdleTimeout = caddy . Duration ( dur )
2020-11-23 14:46:50 -05:00
2024-01-23 19:36:59 -05:00
default :
return nil , d . Errf ( "unrecognized timeouts option '%s'" , d . Val ( ) )
2020-11-23 14:46:50 -05:00
}
2024-01-23 19:36:59 -05:00
}
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 )
2020-11-23 14:46:50 -05:00
2024-01-23 19:36:59 -05:00
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 )
2023-08-02 15:03:26 -05:00
2024-01-23 19:36:59 -05:00
case "enable_full_duplex" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
serverOpts . EnableFullDuplex = true
2021-12-02 15:26:24 -05:00
2024-01-23 19:36:59 -05:00
case "log_credentials" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
serverOpts . ShouldLogCredentials = true
2022-08-15 13:01:58 -05:00
2024-01-23 19:36:59 -05:00
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 )
2022-08-15 13:01:58 -05:00
}
2024-09-25 15:30:56 -05:00
if slices . Contains ( serverOpts . Protocols , proto ) {
2024-01-23 19:36:59 -05:00
return nil , d . Errf ( "protocol %s specified more than once" , proto )
2022-08-15 13:01:58 -05:00
}
2024-01-23 19:36:59 -05:00
serverOpts . Protocols = append ( serverOpts . Protocols , proto )
}
if nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) {
return nil , d . ArgErr ( )
}
2022-08-15 13:01:58 -05:00
2024-01-23 19:36:59 -05:00
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
2023-01-10 00:08:23 -05:00
2024-01-23 19:36:59 -05:00
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
2024-01-13 15:46:37 -05:00
2024-01-23 19:36:59 -05:00
case "trusted_proxies_strict" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
serverOpts . TrustedProxiesStrict = 1
2023-03-27 15:22:59 -05:00
2024-01-23 19:36:59 -05:00
case "client_ip_headers" :
headers := d . RemainingArgs ( )
for _ , header := range headers {
2024-09-25 15:30:56 -05:00
if slices . Contains ( serverOpts . ClientIPHeaders , header ) {
2024-01-23 19:36:59 -05:00
return nil , d . Errf ( "client IP header %s specified more than once" , header )
2022-09-16 14:32:49 -05:00
}
2024-01-23 19:36:59 -05:00
serverOpts . ClientIPHeaders = append ( serverOpts . ClientIPHeaders , header )
}
if nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) {
return nil , d . ArgErr ( )
}
2022-09-16 14:32:49 -05:00
2024-01-23 19:36:59 -05:00
case "metrics" :
2024-10-18 10:54:21 -05:00
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." )
2024-01-23 19:36:59 -05:00
serverOpts . Metrics = new ( caddyhttp . Metrics )
2024-10-02 09:23:26 -05:00
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
switch d . Val ( ) {
case "per_host" :
serverOpts . Metrics . PerHost = true
}
}
2022-08-15 13:01:58 -05:00
2024-05-18 15:48:42 -05:00
case "trace" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
2020-11-23 14:46:50 -05:00
}
2024-05-18 15:48:42 -05:00
serverOpts . Trace = true
2024-01-23 19:36:59 -05:00
default :
return nil , d . Errf ( "unrecognized servers option '%s'" , d . Val ( ) )
2020-11-23 14:46:50 -05:00
}
}
return serverOpts , nil
}
// applyServerOptions sets the server options on the appropriate servers
func applyServerOptions (
servers map [ string ] * caddyhttp . Server ,
2022-08-02 15:39:09 -05:00
options map [ string ] any ,
2024-04-17 13:19:14 -05:00
_ * [ ] caddyconfig . Warning ,
2020-11-23 14:46:50 -05:00
) error {
serverOpts , ok := options [ "servers" ] . ( [ ] serverOptions )
if ! ok {
return nil
}
2023-01-27 14:56:39 -05:00
// 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 {
2020-11-23 14:46:50 -05:00
// find the options that apply to this server
2024-09-25 15:30:56 -05:00
optsIndex := slices . IndexFunc ( serverOpts , func ( s serverOptions ) bool {
return s . ListenerAddress == "" || slices . Contains ( server . Listen , s . ListenerAddress )
} )
2020-11-23 14:46:50 -05:00
// if none apply, then move to the next server
2024-09-25 15:30:56 -05:00
if optsIndex == - 1 {
2020-11-23 14:46:50 -05:00
continue
}
2024-09-25 15:30:56 -05:00
opts := serverOpts [ optsIndex ]
2020-11-23 14:46:50 -05:00
// set all the options
server . ListenerWrappersRaw = opts . ListenerWrappersRaw
server . ReadTimeout = opts . ReadTimeout
server . ReadHeaderTimeout = opts . ReadHeaderTimeout
server . WriteTimeout = opts . WriteTimeout
server . IdleTimeout = opts . IdleTimeout
2022-09-02 17:59:11 -05:00
server . KeepAliveInterval = opts . KeepAliveInterval
2020-11-23 14:46:50 -05:00
server . MaxHeaderBytes = opts . MaxHeaderBytes
2023-08-02 15:03:26 -05:00
server . EnableFullDuplex = opts . EnableFullDuplex
2022-08-15 13:01:58 -05:00
server . Protocols = opts . Protocols
2020-11-23 14:46:50 -05:00
server . StrictSNIHost = opts . StrictSNIHost
2023-02-06 14:44:11 -05:00
server . TrustedProxiesRaw = opts . TrustedProxiesRaw
2023-03-27 15:22:59 -05:00
server . ClientIPHeaders = opts . ClientIPHeaders
2024-01-13 15:46:37 -05:00
server . TrustedProxiesStrict = opts . TrustedProxiesStrict
2022-09-16 14:32:49 -05:00
server . Metrics = opts . Metrics
2021-12-02 15:26:24 -05:00
if opts . ShouldLogCredentials {
if server . Logs == nil {
2024-05-18 15:48:42 -05:00
server . Logs = new ( caddyhttp . ServerLogConfig )
2021-12-02 15:26:24 -05:00
}
server . Logs . ShouldLogCredentials = opts . ShouldLogCredentials
}
2024-05-18 15:48:42 -05:00
if opts . Trace {
// TODO: THIS IS EXPERIMENTAL (MAY 2024)
if server . Logs == nil {
server . Logs = new ( caddyhttp . ServerLogConfig )
}
server . Logs . Trace = opts . Trace
}
2023-01-27 14:56:39 -05:00
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 )
2020-11-23 14:46:50 -05:00
}
return nil
}