mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			944 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			944 lines
		
	
	
		
			27 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/base64"
 | |
| 	"encoding/pem"
 | |
| 	"fmt"
 | |
| 	"html"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"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"
 | |
| 	"github.com/caddyserver/caddy/v2/modules/caddytls"
 | |
| 	"github.com/caddyserver/certmagic"
 | |
| 	"github.com/mholt/acmez/acme"
 | |
| 	"go.uber.org/zap/zapcore"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	RegisterDirective("bind", parseBind)
 | |
| 	RegisterDirective("tls", parseTLS)
 | |
| 	RegisterHandlerDirective("root", parseRoot)
 | |
| 	RegisterHandlerDirective("vars", parseVars)
 | |
| 	RegisterHandlerDirective("redir", parseRedir)
 | |
| 	RegisterHandlerDirective("respond", parseRespond)
 | |
| 	RegisterHandlerDirective("abort", parseAbort)
 | |
| 	RegisterHandlerDirective("error", parseError)
 | |
| 	RegisterHandlerDirective("route", parseRoute)
 | |
| 	RegisterHandlerDirective("handle", parseHandle)
 | |
| 	RegisterDirective("handle_errors", parseHandleErrors)
 | |
| 	RegisterDirective("log", parseLog)
 | |
| 	RegisterHandlerDirective("skip_log", parseSkipLog)
 | |
| }
 | |
| 
 | |
| // parseBind parses the bind directive. Syntax:
 | |
| //
 | |
| //	bind <addresses...>
 | |
| func parseBind(h Helper) ([]ConfigValue, error) {
 | |
| 	var lnHosts []string
 | |
| 	for h.Next() {
 | |
| 		lnHosts = append(lnHosts, h.RemainingArgs()...)
 | |
| 	}
 | |
| 	return h.NewBindAddresses(lnHosts), nil
 | |
| }
 | |
| 
 | |
| // parseTLS parses the tls directive. Syntax:
 | |
| //
 | |
| //	tls [<email>|internal]|[<cert_file> <key_file>] {
 | |
| //	    protocols <min> [<max>]
 | |
| //	    ciphers   <cipher_suites...>
 | |
| //	    curves    <curves...>
 | |
| //	    client_auth {
 | |
| //	        mode                   [request|require|verify_if_given|require_and_verify]
 | |
| //	        trusted_ca_cert        <base64_der>
 | |
| //	        trusted_ca_cert_file   <filename>
 | |
| //	        trusted_leaf_cert      <base64_der>
 | |
| //	        trusted_leaf_cert_file <filename>
 | |
| //	    }
 | |
| //	    alpn                          <values...>
 | |
| //	    load                          <paths...>
 | |
| //	    ca                            <acme_ca_endpoint>
 | |
| //	    ca_root                       <pem_file>
 | |
| //	    key_type                      [ed25519|p256|p384|rsa2048|rsa4096]
 | |
| //	    dns                           <provider_name> [...]
 | |
| //	    propagation_delay             <duration>
 | |
| //	    propagation_timeout           <duration>
 | |
| //	    resolvers                     <dns_servers...>
 | |
| //	    dns_ttl                       <duration>
 | |
| //	    dns_challenge_override_domain <domain>
 | |
| //	    on_demand
 | |
| //	    eab                           <key_id> <mac_key>
 | |
| //	    issuer                        <module_name> [...]
 | |
| //	    get_certificate               <module_name> [...]
 | |
| //	    insecure_secrets_log          <log_file>
 | |
| //	}
 | |
| func parseTLS(h Helper) ([]ConfigValue, error) {
 | |
| 	cp := new(caddytls.ConnectionPolicy)
 | |
| 	var fileLoader caddytls.FileLoader
 | |
| 	var folderLoader caddytls.FolderLoader
 | |
| 	var certSelector caddytls.CustomCertSelectionPolicy
 | |
| 	var acmeIssuer *caddytls.ACMEIssuer
 | |
| 	var keyType string
 | |
| 	var internalIssuer *caddytls.InternalIssuer
 | |
| 	var issuers []certmagic.Issuer
 | |
| 	var certManagers []certmagic.Manager
 | |
| 	var onDemand bool
 | |
| 
 | |
| 	for h.Next() {
 | |
| 		// file certificate loader
 | |
| 		firstLine := h.RemainingArgs()
 | |
| 		switch len(firstLine) {
 | |
| 		case 0:
 | |
| 		case 1:
 | |
| 			if firstLine[0] == "internal" {
 | |
| 				internalIssuer = new(caddytls.InternalIssuer)
 | |
| 			} else if !strings.Contains(firstLine[0], "@") {
 | |
| 				return nil, h.Err("single argument must either be 'internal' or an email address")
 | |
| 			} else {
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				acmeIssuer.Email = firstLine[0]
 | |
| 			}
 | |
| 
 | |
| 		case 2:
 | |
| 			certFilename := firstLine[0]
 | |
| 			keyFilename := firstLine[1]
 | |
| 
 | |
| 			// tag this certificate so if multiple certs match, specifically
 | |
| 			// this one that the user has provided will be used, see #2588:
 | |
| 			// https://github.com/caddyserver/caddy/issues/2588 ... but we
 | |
| 			// must be careful about how we do this; being careless will
 | |
| 			// lead to failed handshakes
 | |
| 			//
 | |
| 			// we need to remember which cert files we've seen, since we
 | |
| 			// must load each cert only once; otherwise, they each get a
 | |
| 			// different tag... since a cert loaded twice has the same
 | |
| 			// bytes, it will overwrite the first one in the cache, and
 | |
| 			// only the last cert (and its tag) will survive, so any conn
 | |
| 			// policy that is looking for any tag other than the last one
 | |
| 			// to be loaded won't find it, and TLS handshakes will fail
 | |
| 			// (see end of issue #3004)
 | |
| 			//
 | |
| 			// tlsCertTags maps certificate filenames to their tag.
 | |
| 			// This is used to remember which tag is used for each
 | |
| 			// certificate files, since we need to avoid loading
 | |
| 			// the same certificate files more than once, overwriting
 | |
| 			// previous tags
 | |
| 			tlsCertTags, ok := h.State["tlsCertTags"].(map[string]string)
 | |
| 			if !ok {
 | |
| 				tlsCertTags = make(map[string]string)
 | |
| 				h.State["tlsCertTags"] = tlsCertTags
 | |
| 			}
 | |
| 
 | |
| 			tag, ok := tlsCertTags[certFilename]
 | |
| 			if !ok {
 | |
| 				// haven't seen this cert file yet, let's give it a tag
 | |
| 				// and add a loader for it
 | |
| 				tag = fmt.Sprintf("cert%d", len(tlsCertTags))
 | |
| 				fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
 | |
| 					Certificate: certFilename,
 | |
| 					Key:         keyFilename,
 | |
| 					Tags:        []string{tag},
 | |
| 				})
 | |
| 				// remember this for next time we see this cert file
 | |
| 				tlsCertTags[certFilename] = tag
 | |
| 			}
 | |
| 			certSelector.AnyTag = append(certSelector.AnyTag, tag)
 | |
| 
 | |
| 		default:
 | |
| 			return nil, h.ArgErr()
 | |
| 		}
 | |
| 
 | |
| 		var hasBlock bool
 | |
| 		for nesting := h.Nesting(); h.NextBlock(nesting); {
 | |
| 			hasBlock = true
 | |
| 
 | |
| 			switch h.Val() {
 | |
| 			case "protocols":
 | |
| 				args := h.RemainingArgs()
 | |
| 				if len(args) == 0 {
 | |
| 					return nil, h.SyntaxErr("one or two protocols")
 | |
| 				}
 | |
| 				if len(args) > 0 {
 | |
| 					if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
 | |
| 						return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
 | |
| 					}
 | |
| 					cp.ProtocolMin = args[0]
 | |
| 				}
 | |
| 				if len(args) > 1 {
 | |
| 					if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
 | |
| 						return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
 | |
| 					}
 | |
| 					cp.ProtocolMax = args[1]
 | |
| 				}
 | |
| 
 | |
| 			case "ciphers":
 | |
| 				for h.NextArg() {
 | |
| 					if !caddytls.CipherSuiteNameSupported(h.Val()) {
 | |
| 						return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
 | |
| 					}
 | |
| 					cp.CipherSuites = append(cp.CipherSuites, h.Val())
 | |
| 				}
 | |
| 
 | |
| 			case "curves":
 | |
| 				for h.NextArg() {
 | |
| 					if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
 | |
| 						return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val())
 | |
| 					}
 | |
| 					cp.Curves = append(cp.Curves, h.Val())
 | |
| 				}
 | |
| 
 | |
| 			case "client_auth":
 | |
| 				cp.ClientAuthentication = &caddytls.ClientAuthentication{}
 | |
| 				for nesting := h.Nesting(); h.NextBlock(nesting); {
 | |
| 					subdir := h.Val()
 | |
| 					switch subdir {
 | |
| 					case "mode":
 | |
| 						if !h.Args(&cp.ClientAuthentication.Mode) {
 | |
| 							return nil, h.ArgErr()
 | |
| 						}
 | |
| 						if h.NextArg() {
 | |
| 							return nil, h.ArgErr()
 | |
| 						}
 | |
| 
 | |
| 					case "trusted_ca_cert",
 | |
| 						"trusted_leaf_cert":
 | |
| 						if !h.NextArg() {
 | |
| 							return nil, h.ArgErr()
 | |
| 						}
 | |
| 						if subdir == "trusted_ca_cert" {
 | |
| 							cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts, h.Val())
 | |
| 						} else {
 | |
| 							cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts, h.Val())
 | |
| 						}
 | |
| 
 | |
| 					case "trusted_ca_cert_file",
 | |
| 						"trusted_leaf_cert_file":
 | |
| 						if !h.NextArg() {
 | |
| 							return nil, h.ArgErr()
 | |
| 						}
 | |
| 						filename := h.Val()
 | |
| 						certDataPEM, err := os.ReadFile(filename)
 | |
| 						if err != nil {
 | |
| 							return nil, err
 | |
| 						}
 | |
| 						block, _ := pem.Decode(certDataPEM)
 | |
| 						if block == nil || block.Type != "CERTIFICATE" {
 | |
| 							return nil, h.Errf("no CERTIFICATE pem block found in %s", h.Val())
 | |
| 						}
 | |
| 						if subdir == "trusted_ca_cert_file" {
 | |
| 							cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts,
 | |
| 								base64.StdEncoding.EncodeToString(block.Bytes))
 | |
| 						} else {
 | |
| 							cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts,
 | |
| 								base64.StdEncoding.EncodeToString(block.Bytes))
 | |
| 						}
 | |
| 
 | |
| 					default:
 | |
| 						return nil, h.Errf("unknown subdirective for client_auth: %s", subdir)
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 			case "alpn":
 | |
| 				args := h.RemainingArgs()
 | |
| 				if len(args) == 0 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				cp.ALPN = args
 | |
| 
 | |
| 			case "load":
 | |
| 				folderLoader = append(folderLoader, h.RemainingArgs()...)
 | |
| 
 | |
| 			case "ca":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				acmeIssuer.CA = arg[0]
 | |
| 
 | |
| 			case "key_type":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				keyType = arg[0]
 | |
| 
 | |
| 			case "eab":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 2 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				acmeIssuer.ExternalAccount = &acme.EAB{
 | |
| 					KeyID:  arg[0],
 | |
| 					MACKey: arg[1],
 | |
| 				}
 | |
| 
 | |
| 			case "issuer":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				modName := h.Val()
 | |
| 				modID := "tls.issuance." + modName
 | |
| 				unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				issuer, ok := unm.(certmagic.Issuer)
 | |
| 				if !ok {
 | |
| 					return nil, h.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm)
 | |
| 				}
 | |
| 				issuers = append(issuers, issuer)
 | |
| 
 | |
| 			case "get_certificate":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				modName := h.Val()
 | |
| 				modID := "tls.get_certificate." + modName
 | |
| 				unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				certManager, ok := unm.(certmagic.Manager)
 | |
| 				if !ok {
 | |
| 					return nil, h.Errf("module %s (%T) is not a certmagic.CertificateManager", modID, unm)
 | |
| 				}
 | |
| 				certManagers = append(certManagers, certManager)
 | |
| 
 | |
| 			case "dns":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				provName := h.Val()
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges == nil {
 | |
| 					acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges.DNS == nil {
 | |
| 					acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
 | |
| 				}
 | |
| 				modID := "dns.providers." + provName
 | |
| 				unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
 | |
| 
 | |
| 			case "resolvers":
 | |
| 				args := h.RemainingArgs()
 | |
| 				if len(args) == 0 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges == nil {
 | |
| 					acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges.DNS == nil {
 | |
| 					acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
 | |
| 				}
 | |
| 				acmeIssuer.Challenges.DNS.Resolvers = args
 | |
| 
 | |
| 			case "propagation_delay":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				delayStr := arg[0]
 | |
| 				delay, err := caddy.ParseDuration(delayStr)
 | |
| 				if err != nil {
 | |
| 					return nil, h.Errf("invalid propagation_delay duration %s: %v", delayStr, err)
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges == nil {
 | |
| 					acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges.DNS == nil {
 | |
| 					acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
 | |
| 				}
 | |
| 				acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
 | |
| 
 | |
| 			case "propagation_timeout":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				timeoutStr := arg[0]
 | |
| 				var timeout time.Duration
 | |
| 				if timeoutStr == "-1" {
 | |
| 					timeout = time.Duration(-1)
 | |
| 				} else {
 | |
| 					var err error
 | |
| 					timeout, err = caddy.ParseDuration(timeoutStr)
 | |
| 					if err != nil {
 | |
| 						return nil, h.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err)
 | |
| 					}
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges == nil {
 | |
| 					acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges.DNS == nil {
 | |
| 					acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
 | |
| 				}
 | |
| 				acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout)
 | |
| 
 | |
| 			case "dns_ttl":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				ttlStr := arg[0]
 | |
| 				ttl, err := caddy.ParseDuration(ttlStr)
 | |
| 				if err != nil {
 | |
| 					return nil, h.Errf("invalid dns_ttl duration %s: %v", ttlStr, err)
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges == nil {
 | |
| 					acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges.DNS == nil {
 | |
| 					acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
 | |
| 				}
 | |
| 				acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl)
 | |
| 
 | |
| 			case "dns_challenge_override_domain":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges == nil {
 | |
| 					acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
 | |
| 				}
 | |
| 				if acmeIssuer.Challenges.DNS == nil {
 | |
| 					acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
 | |
| 				}
 | |
| 				acmeIssuer.Challenges.DNS.OverrideDomain = arg[0]
 | |
| 
 | |
| 			case "ca_root":
 | |
| 				arg := h.RemainingArgs()
 | |
| 				if len(arg) != 1 {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				if acmeIssuer == nil {
 | |
| 					acmeIssuer = new(caddytls.ACMEIssuer)
 | |
| 				}
 | |
| 				acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0])
 | |
| 
 | |
| 			case "on_demand":
 | |
| 				if h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				onDemand = true
 | |
| 
 | |
| 			case "insecure_secrets_log":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				cp.InsecureSecretsLog = h.Val()
 | |
| 
 | |
| 			default:
 | |
| 				return nil, h.Errf("unknown subdirective: %s", h.Val())
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// a naked tls directive is not allowed
 | |
| 		if len(firstLine) == 0 && !hasBlock {
 | |
| 			return nil, h.ArgErr()
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// begin building the final config values
 | |
| 	configVals := []ConfigValue{}
 | |
| 
 | |
| 	// certificate loaders
 | |
| 	if len(fileLoader) > 0 {
 | |
| 		configVals = append(configVals, ConfigValue{
 | |
| 			Class: "tls.cert_loader",
 | |
| 			Value: fileLoader,
 | |
| 		})
 | |
| 	}
 | |
| 	if len(folderLoader) > 0 {
 | |
| 		configVals = append(configVals, ConfigValue{
 | |
| 			Class: "tls.cert_loader",
 | |
| 			Value: folderLoader,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// some tls subdirectives are shortcuts that implicitly configure issuers, and the
 | |
| 	// user can also configure issuers explicitly using the issuer subdirective; the
 | |
| 	// logic to support both would likely be complex, or at least unintuitive
 | |
| 	if len(issuers) > 0 && (acmeIssuer != nil || internalIssuer != nil) {
 | |
| 		return nil, h.Err("cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)")
 | |
| 	}
 | |
| 	if acmeIssuer != nil && internalIssuer != nil {
 | |
| 		return nil, h.Err("cannot create both ACME and internal certificate issuers")
 | |
| 	}
 | |
| 
 | |
| 	// now we should either have: explicitly-created issuers, or an implicitly-created
 | |
| 	// ACME or internal issuer, or no issuers at all
 | |
| 	switch {
 | |
| 	case len(issuers) > 0:
 | |
| 		for _, issuer := range issuers {
 | |
| 			configVals = append(configVals, ConfigValue{
 | |
| 				Class: "tls.cert_issuer",
 | |
| 				Value: issuer,
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 	case acmeIssuer != nil:
 | |
| 		// implicit ACME issuers (from various subdirectives) - use defaults; there might be more than one
 | |
| 		defaultIssuers := caddytls.DefaultIssuers()
 | |
| 
 | |
| 		// if a CA endpoint was set, override multiple implicit issuers since it's a specific one
 | |
| 		if acmeIssuer.CA != "" {
 | |
| 			defaultIssuers = []certmagic.Issuer{acmeIssuer}
 | |
| 		}
 | |
| 
 | |
| 		for _, issuer := range defaultIssuers {
 | |
| 			switch iss := issuer.(type) {
 | |
| 			case *caddytls.ACMEIssuer:
 | |
| 				issuer = acmeIssuer
 | |
| 			case *caddytls.ZeroSSLIssuer:
 | |
| 				iss.ACMEIssuer = acmeIssuer
 | |
| 			}
 | |
| 			configVals = append(configVals, ConfigValue{
 | |
| 				Class: "tls.cert_issuer",
 | |
| 				Value: issuer,
 | |
| 			})
 | |
| 		}
 | |
| 
 | |
| 	case internalIssuer != nil:
 | |
| 		configVals = append(configVals, ConfigValue{
 | |
| 			Class: "tls.cert_issuer",
 | |
| 			Value: internalIssuer,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// certificate key type
 | |
| 	if keyType != "" {
 | |
| 		configVals = append(configVals, ConfigValue{
 | |
| 			Class: "tls.key_type",
 | |
| 			Value: keyType,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// on-demand TLS
 | |
| 	if onDemand {
 | |
| 		configVals = append(configVals, ConfigValue{
 | |
| 			Class: "tls.on_demand",
 | |
| 			Value: true,
 | |
| 		})
 | |
| 	}
 | |
| 	for _, certManager := range certManagers {
 | |
| 		configVals = append(configVals, ConfigValue{
 | |
| 			Class: "tls.cert_manager",
 | |
| 			Value: certManager,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	// custom certificate selection
 | |
| 	if len(certSelector.AnyTag) > 0 {
 | |
| 		cp.CertSelection = &certSelector
 | |
| 	}
 | |
| 
 | |
| 	// connection policy -- always add one, to ensure that TLS
 | |
| 	// is enabled, because this directive was used (this is
 | |
| 	// needed, for instance, when a site block has a key of
 | |
| 	// just ":5000" - i.e. no hostname, and only on-demand TLS
 | |
| 	// is enabled)
 | |
| 	configVals = append(configVals, ConfigValue{
 | |
| 		Class: "tls.connection_policy",
 | |
| 		Value: cp,
 | |
| 	})
 | |
| 
 | |
| 	return configVals, nil
 | |
| }
 | |
| 
 | |
| // parseRoot parses the root directive. Syntax:
 | |
| //
 | |
| //	root [<matcher>] <path>
 | |
| func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	var root string
 | |
| 	for h.Next() {
 | |
| 		if !h.NextArg() {
 | |
| 			return nil, h.ArgErr()
 | |
| 		}
 | |
| 		root = h.Val()
 | |
| 		if h.NextArg() {
 | |
| 			return nil, h.ArgErr()
 | |
| 		}
 | |
| 	}
 | |
| 	return caddyhttp.VarsMiddleware{"root": root}, nil
 | |
| }
 | |
| 
 | |
| // parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax.
 | |
| func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	v := new(caddyhttp.VarsMiddleware)
 | |
| 	err := v.UnmarshalCaddyfile(h.Dispenser)
 | |
| 	return v, err
 | |
| }
 | |
| 
 | |
| // parseRedir parses the redir directive. Syntax:
 | |
| //
 | |
| //	redir [<matcher>] <to> [<code>]
 | |
| //
 | |
| // <code> can be "permanent" for 301, "temporary" for 302 (default),
 | |
| // a placeholder, or any number in the 3xx range or 401. The special
 | |
| // code "html" can be used to redirect only browser clients (will
 | |
| // respond with HTTP 200 and no Location header; redirect is performed
 | |
| // with JS and a meta tag).
 | |
| func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	if !h.Next() {
 | |
| 		return nil, h.ArgErr()
 | |
| 	}
 | |
| 
 | |
| 	if !h.NextArg() {
 | |
| 		return nil, h.ArgErr()
 | |
| 	}
 | |
| 	to := h.Val()
 | |
| 
 | |
| 	var code string
 | |
| 	if h.NextArg() {
 | |
| 		code = h.Val()
 | |
| 	}
 | |
| 
 | |
| 	var body string
 | |
| 	var hdr http.Header
 | |
| 	switch code {
 | |
| 	case "permanent":
 | |
| 		code = "301"
 | |
| 	case "temporary", "":
 | |
| 		code = "302"
 | |
| 	case "html":
 | |
| 		// Script tag comes first since that will better imitate a redirect in the browser's
 | |
| 		// history, but the meta tag is a fallback for most non-JS clients.
 | |
| 		const metaRedir = `<!DOCTYPE html>
 | |
| <html>
 | |
| 	<head>
 | |
| 		<title>Redirecting...</title>
 | |
| 		<script>window.location.replace("%s");</script>
 | |
| 		<meta http-equiv="refresh" content="0; URL='%s'">
 | |
| 	</head>
 | |
| 	<body>Redirecting to <a href="%s">%s</a>...</body>
 | |
| </html>
 | |
| `
 | |
| 		safeTo := html.EscapeString(to)
 | |
| 		body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
 | |
| 		code = "200" // don't redirect non-browser clients
 | |
| 	default:
 | |
| 		// Allow placeholders for the code
 | |
| 		if strings.HasPrefix(code, "{") {
 | |
| 			break
 | |
| 		}
 | |
| 		// Try to validate as an integer otherwise
 | |
| 		codeInt, err := strconv.Atoi(code)
 | |
| 		if err != nil {
 | |
| 			return nil, h.Errf("Not a supported redir code type or not valid integer: '%s'", code)
 | |
| 		}
 | |
| 		// Sometimes, a 401 with Location header is desirable because
 | |
| 		// requests made with XHR will "eat" the 3xx redirect; so if
 | |
| 		// the intent was to redirect to an auth page, a 3xx won't
 | |
| 		// work. Responding with 401 allows JS code to read the
 | |
| 		// Location header and do a window.location redirect manually.
 | |
| 		// see https://stackoverflow.com/a/2573589/846934
 | |
| 		// see https://github.com/oauth2-proxy/oauth2-proxy/issues/1522
 | |
| 		if codeInt < 300 || (codeInt > 399 && codeInt != 401) {
 | |
| 			return nil, h.Errf("Redir code not in the 3xx range or 401: '%v'", codeInt)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// don't redirect non-browser clients
 | |
| 	if code != "200" {
 | |
| 		hdr = http.Header{"Location": []string{to}}
 | |
| 	}
 | |
| 
 | |
| 	return caddyhttp.StaticResponse{
 | |
| 		StatusCode: caddyhttp.WeakString(code),
 | |
| 		Headers:    hdr,
 | |
| 		Body:       body,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // parseRespond parses the respond directive.
 | |
| func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	sr := new(caddyhttp.StaticResponse)
 | |
| 	err := sr.UnmarshalCaddyfile(h.Dispenser)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return sr, nil
 | |
| }
 | |
| 
 | |
| // parseAbort parses the abort directive.
 | |
| func parseAbort(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	h.Next() // consume directive
 | |
| 	for h.Next() || h.NextBlock(0) {
 | |
| 		return nil, h.ArgErr()
 | |
| 	}
 | |
| 	return &caddyhttp.StaticResponse{Abort: true}, nil
 | |
| }
 | |
| 
 | |
| // parseError parses the error directive.
 | |
| func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	se := new(caddyhttp.StaticError)
 | |
| 	err := se.UnmarshalCaddyfile(h.Dispenser)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return se, nil
 | |
| }
 | |
| 
 | |
| // parseRoute parses the route directive.
 | |
| func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	allResults, err := parseSegmentAsConfig(h)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, result := range allResults {
 | |
| 		switch result.Value.(type) {
 | |
| 		case caddyhttp.Route, caddyhttp.Subroute:
 | |
| 		default:
 | |
| 			return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", result.directive, result.Value)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return buildSubroute(allResults, h.groupCounter, false)
 | |
| }
 | |
| 
 | |
| func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	return ParseSegmentAsSubroute(h)
 | |
| }
 | |
| 
 | |
| func parseHandleErrors(h Helper) ([]ConfigValue, error) {
 | |
| 	subroute, err := ParseSegmentAsSubroute(h)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return []ConfigValue{
 | |
| 		{
 | |
| 			Class: "error_route",
 | |
| 			Value: subroute,
 | |
| 		},
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // parseLog parses the log directive. Syntax:
 | |
| //
 | |
| //	log {
 | |
| //	    output <writer_module> ...
 | |
| //	    format <encoder_module> ...
 | |
| //	    level  <level>
 | |
| //	}
 | |
| func parseLog(h Helper) ([]ConfigValue, error) {
 | |
| 	return parseLogHelper(h, nil)
 | |
| }
 | |
| 
 | |
| // parseLogHelper is used both for the parseLog directive within Server Blocks,
 | |
| // as well as the global "log" option for configuring loggers at the global
 | |
| // level. The parseAsGlobalOption parameter is used to distinguish any differing logic
 | |
| // between the two.
 | |
| func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue, error) {
 | |
| 	// When the globalLogNames parameter is passed in, we make
 | |
| 	// modifications to the parsing behavior.
 | |
| 	parseAsGlobalOption := globalLogNames != nil
 | |
| 
 | |
| 	var configValues []ConfigValue
 | |
| 	for h.Next() {
 | |
| 		// Logic below expects that a name is always present when a
 | |
| 		// global option is being parsed.
 | |
| 		var globalLogName string
 | |
| 		if parseAsGlobalOption {
 | |
| 			if h.NextArg() {
 | |
| 				globalLogName = h.Val()
 | |
| 
 | |
| 				// Only a single argument is supported.
 | |
| 				if h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 			} else {
 | |
| 				// If there is no log name specified, we
 | |
| 				// reference the default logger. See the
 | |
| 				// setupNewDefault function in the logging
 | |
| 				// package for where this is configured.
 | |
| 				globalLogName = caddy.DefaultLoggerName
 | |
| 			}
 | |
| 
 | |
| 			// Verify this name is unused.
 | |
| 			_, used := globalLogNames[globalLogName]
 | |
| 			if used {
 | |
| 				return nil, h.Err("duplicate global log option for: " + globalLogName)
 | |
| 			}
 | |
| 			globalLogNames[globalLogName] = struct{}{}
 | |
| 		} else {
 | |
| 			// No arguments are supported for the server block log directive
 | |
| 			if h.NextArg() {
 | |
| 				return nil, h.ArgErr()
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		cl := new(caddy.CustomLog)
 | |
| 
 | |
| 		for h.NextBlock(0) {
 | |
| 			switch h.Val() {
 | |
| 			case "output":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				moduleName := h.Val()
 | |
| 
 | |
| 				// can't use the usual caddyfile.Unmarshaler flow with the
 | |
| 				// standard writers because they are in the caddy package
 | |
| 				// (because they are the default) and implementing that
 | |
| 				// interface there would unfortunately create circular import
 | |
| 				var wo caddy.WriterOpener
 | |
| 				switch moduleName {
 | |
| 				case "stdout":
 | |
| 					wo = caddy.StdoutWriter{}
 | |
| 				case "stderr":
 | |
| 					wo = caddy.StderrWriter{}
 | |
| 				case "discard":
 | |
| 					wo = caddy.DiscardWriter{}
 | |
| 				default:
 | |
| 					modID := "caddy.logging.writers." + moduleName
 | |
| 					unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 					var ok bool
 | |
| 					wo, ok = unm.(caddy.WriterOpener)
 | |
| 					if !ok {
 | |
| 						return nil, h.Errf("module %s (%T) is not a WriterOpener", modID, unm)
 | |
| 					}
 | |
| 				}
 | |
| 				cl.WriterRaw = caddyconfig.JSONModuleObject(wo, "output", moduleName, h.warnings)
 | |
| 
 | |
| 			case "format":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				moduleName := h.Val()
 | |
| 				moduleID := "caddy.logging.encoders." + moduleName
 | |
| 				unm, err := caddyfile.UnmarshalModule(h.Dispenser, moduleID)
 | |
| 				if err != nil {
 | |
| 					return nil, err
 | |
| 				}
 | |
| 				enc, ok := unm.(zapcore.Encoder)
 | |
| 				if !ok {
 | |
| 					return nil, h.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm)
 | |
| 				}
 | |
| 				cl.EncoderRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, h.warnings)
 | |
| 
 | |
| 			case "level":
 | |
| 				if !h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				cl.Level = h.Val()
 | |
| 				if h.NextArg() {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 
 | |
| 			case "include":
 | |
| 				// This configuration is only allowed in the global options
 | |
| 				if !parseAsGlobalOption {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				for h.NextArg() {
 | |
| 					cl.Include = append(cl.Include, h.Val())
 | |
| 				}
 | |
| 
 | |
| 			case "exclude":
 | |
| 				// This configuration is only allowed in the global options
 | |
| 				if !parseAsGlobalOption {
 | |
| 					return nil, h.ArgErr()
 | |
| 				}
 | |
| 				for h.NextArg() {
 | |
| 					cl.Exclude = append(cl.Exclude, h.Val())
 | |
| 				}
 | |
| 
 | |
| 			default:
 | |
| 				return nil, h.Errf("unrecognized subdirective: %s", h.Val())
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		var val namedCustomLog
 | |
| 		// Skip handling of empty logging configs
 | |
| 		if !reflect.DeepEqual(cl, new(caddy.CustomLog)) {
 | |
| 			if parseAsGlobalOption {
 | |
| 				// Use indicated name for global log options
 | |
| 				val.name = globalLogName
 | |
| 				val.log = cl
 | |
| 			} else {
 | |
| 				// Construct a log name for server log streams
 | |
| 				logCounter, ok := h.State["logCounter"].(int)
 | |
| 				if !ok {
 | |
| 					logCounter = 0
 | |
| 				}
 | |
| 				val.name = fmt.Sprintf("log%d", logCounter)
 | |
| 				cl.Include = []string{"http.log.access." + val.name}
 | |
| 				val.log = cl
 | |
| 				logCounter++
 | |
| 				h.State["logCounter"] = logCounter
 | |
| 			}
 | |
| 		}
 | |
| 		configValues = append(configValues, ConfigValue{
 | |
| 			Class: "custom_log",
 | |
| 			Value: val,
 | |
| 		})
 | |
| 	}
 | |
| 	return configValues, nil
 | |
| }
 | |
| 
 | |
| // parseSkipLog parses the skip_log directive. Syntax:
 | |
| //
 | |
| //	skip_log [<matcher>]
 | |
| func parseSkipLog(h Helper) (caddyhttp.MiddlewareHandler, error) {
 | |
| 	for h.Next() {
 | |
| 		if h.NextArg() {
 | |
| 			return nil, h.ArgErr()
 | |
| 		}
 | |
| 	}
 | |
| 	return caddyhttp.VarsMiddleware{"skip_log": true}, nil
 | |
| }
 |