2020-11-24 03:46:50 +08: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"
2023-08-14 23:41:15 +08:00
"github.com/dustin/go-humanize"
2020-11-24 03:46:50 +08: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-28 03:56:39 +08:00
Name string
2021-12-03 04:26:24 +08:00
ListenerWrappersRaw [ ] json . RawMessage
ReadTimeout caddy . Duration
ReadHeaderTimeout caddy . Duration
WriteTimeout caddy . Duration
IdleTimeout caddy . Duration
2022-09-03 06:59:11 +08:00
KeepAliveInterval caddy . Duration
2021-12-03 04:26:24 +08:00
MaxHeaderBytes int
2023-08-03 04:03:26 +08:00
EnableFullDuplex bool
2022-08-16 02:01:58 +08:00
Protocols [ ] string
2021-12-03 04:26:24 +08:00
StrictSNIHost * bool
2023-02-07 03:44:11 +08:00
TrustedProxiesRaw json . RawMessage
2024-01-14 04:46:37 +08:00
TrustedProxiesStrict int
2023-03-28 04:22:59 +08:00
ClientIPHeaders [ ] string
2021-12-03 04:26:24 +08:00
ShouldLogCredentials bool
2022-09-17 03:32:49 +08:00
Metrics * caddyhttp . Metrics
2020-11-24 03:46:50 +08:00
}
2022-08-03 04:39:09 +08:00
func unmarshalCaddyfileServerOptions ( d * caddyfile . Dispenser ) ( any , error ) {
2020-11-24 03:46:50 +08:00
serverOpts := serverOptions { }
for d . Next ( ) {
if d . NextArg ( ) {
serverOpts . ListenerAddress = d . Val ( )
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
}
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
switch d . Val ( ) {
2023-01-28 03:56:39 +08:00
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 ( )
2020-11-24 03:46:50 +08:00
case "listener_wrappers" :
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
2021-01-06 05:39:30 +08:00
modID := "caddy.listeners." + d . Val ( )
unm , err := caddyfile . UnmarshalModule ( d , modID )
2020-11-24 03:46:50 +08:00
if err != nil {
return nil , err
}
listenerWrapper , ok := unm . ( caddy . ListenerWrapper )
if ! ok {
2021-01-06 05:39:30 +08:00
return nil , fmt . Errorf ( "module %s (%T) is not a listener wrapper" , modID , unm )
2020-11-24 03:46:50 +08:00
}
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 ( ) )
}
}
2022-09-03 06:59:11 +08: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-24 03:46:50 +08: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-03 04:03:26 +08:00
case "enable_full_duplex" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
serverOpts . EnableFullDuplex = true
2021-12-03 04:26:24 +08:00
case "log_credentials" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
serverOpts . ShouldLogCredentials = true
2022-08-16 02:01:58 +08: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 )
}
if sliceContains ( serverOpts . Protocols , proto ) {
return nil , d . Errf ( "protocol %s specified more than once" , proto )
}
serverOpts . Protocols = append ( serverOpts . Protocols , proto )
}
2022-09-20 22:09:04 +08:00
if nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) {
2022-08-16 02:01:58 +08:00
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
2023-01-10 13:08:23 +08:00
case "trusted_proxies" :
2023-02-07 03:44:11 +08:00
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 )
2023-01-10 13:08:23 +08:00
}
2023-02-07 03:44:11 +08:00
jsonSource := caddyconfig . JSONModuleObject (
source ,
"source" ,
source . ( caddy . Module ) . CaddyModule ( ) . ID . Name ( ) ,
nil ,
)
serverOpts . TrustedProxiesRaw = jsonSource
2023-01-10 13:08:23 +08:00
2024-01-14 04:46:37 +08:00
case "trusted_proxies_strict" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
serverOpts . TrustedProxiesStrict = 1
2023-03-28 04:22:59 +08:00
case "client_ip_headers" :
headers := d . RemainingArgs ( )
for _ , header := range headers {
if sliceContains ( 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 ( )
}
2022-09-17 03:32:49 +08:00
case "metrics" :
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
2022-10-07 09:40:08 +08:00
if nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) {
2022-09-17 03:32:49 +08:00
return nil , d . ArgErr ( )
}
serverOpts . Metrics = new ( caddyhttp . Metrics )
2022-08-16 02:01:58 +08:00
// TODO: DEPRECATED. (August 2022)
2020-11-24 03:46:50 +08:00
case "protocol" :
2022-08-16 02:01:58 +08:00
caddy . Log ( ) . Named ( "caddyfile" ) . Warn ( "DEPRECATED: protocol sub-option will be removed soon" )
2020-11-24 03:46:50 +08:00
for nesting := d . Nesting ( ) ; d . NextBlock ( nesting ) ; {
switch d . Val ( ) {
case "allow_h2c" :
2022-08-16 02:01:58 +08:00
caddy . Log ( ) . Named ( "caddyfile" ) . Warn ( "DEPRECATED: allow_h2c will be removed soon; use protocols option instead" )
2020-11-24 03:46:50 +08:00
if d . NextArg ( ) {
return nil , d . ArgErr ( )
}
2022-08-16 02:01:58 +08:00
if sliceContains ( serverOpts . Protocols , "h2c" ) {
return nil , d . Errf ( "protocol h2c already specified" )
}
serverOpts . Protocols = append ( serverOpts . Protocols , "h2c" )
2020-11-24 03:46:50 +08:00
case "strict_sni_host" :
2022-08-16 02:01:58 +08:00
caddy . Log ( ) . Named ( "caddyfile" ) . Warn ( "DEPRECATED: protocol > strict_sni_host in this position will be removed soon; move up to the servers block instead" )
2022-03-02 09:02:39 +08:00
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
2020-11-24 03:46:50 +08:00
}
2022-03-02 09:02:39 +08:00
serverOpts . StrictSNIHost = & boolVal
2020-11-24 03:46:50 +08:00
default :
return nil , d . Errf ( "unrecognized protocol option '%s'" , d . Val ( ) )
}
}
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 ,
2022-08-03 04:39:09 +08:00
options map [ string ] any ,
2020-11-24 03:46:50 +08:00
warnings * [ ] caddyconfig . Warning ,
) error {
serverOpts , ok := options [ "servers" ] . ( [ ] serverOptions )
if ! ok {
return nil
}
2023-01-28 03:56:39 +08: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-24 03:46:50 +08:00
// find the options that apply to this server
opts := func ( ) * serverOptions {
for _ , entry := range serverOpts {
if entry . ListenerAddress == "" {
return & entry
}
for _ , listener := range server . Listen {
if entry . ListenerAddress == listener {
return & entry
}
}
}
return nil
} ( )
// if none apply, then move to the next server
if opts == nil {
continue
}
// 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-03 06:59:11 +08:00
server . KeepAliveInterval = opts . KeepAliveInterval
2020-11-24 03:46:50 +08:00
server . MaxHeaderBytes = opts . MaxHeaderBytes
2023-08-03 04:03:26 +08:00
server . EnableFullDuplex = opts . EnableFullDuplex
2022-08-16 02:01:58 +08:00
server . Protocols = opts . Protocols
2020-11-24 03:46:50 +08:00
server . StrictSNIHost = opts . StrictSNIHost
2023-02-07 03:44:11 +08:00
server . TrustedProxiesRaw = opts . TrustedProxiesRaw
2023-03-28 04:22:59 +08:00
server . ClientIPHeaders = opts . ClientIPHeaders
2024-01-14 04:46:37 +08:00
server . TrustedProxiesStrict = opts . TrustedProxiesStrict
2022-09-17 03:32:49 +08:00
server . Metrics = opts . Metrics
2021-12-03 04:26:24 +08:00
if opts . ShouldLogCredentials {
if server . Logs == nil {
server . Logs = & caddyhttp . ServerLogConfig { }
}
server . Logs . ShouldLogCredentials = opts . ShouldLogCredentials
}
2023-01-28 03:56:39 +08: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-24 03:46:50 +08:00
}
return nil
}