mirror of
				https://github.com/traefik/traefik.git
				synced 2025-10-31 08:21:27 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package rules
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/containous/traefik/v2/pkg/log"
 | |
| 	"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
 | |
| 	"github.com/gorilla/mux"
 | |
| 	"github.com/vulcand/predicate"
 | |
| )
 | |
| 
 | |
| var funcs = map[string]func(*mux.Route, ...string) error{
 | |
| 	"Host":          hostSecure,
 | |
| 	"HostHeader":    host,
 | |
| 	"HostSNI":       hostSNI,
 | |
| 	"HostRegexp":    hostRegexp,
 | |
| 	"Path":          path,
 | |
| 	"PathPrefix":    pathPrefix,
 | |
| 	"Method":        methods,
 | |
| 	"Headers":       headers,
 | |
| 	"HeadersRegexp": headersRegexp,
 | |
| 	"Query":         query,
 | |
| }
 | |
| 
 | |
| // EnableDomainFronting initialize the matcher functions to used on routers.
 | |
| // InsecureSNI defines if the domain fronting is allowed.
 | |
| func EnableDomainFronting(ok bool) {
 | |
| 	if ok {
 | |
| 		log.WithoutContext().Warn("With insecureSNI enabled, router rules do not prevent domain fronting techniques. Please use `HostHeader` and `HostSNI` rules if domain fronting is not desired.")
 | |
| 		funcs["Host"] = host
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	funcs["Host"] = hostSecure
 | |
| }
 | |
| 
 | |
| // Router handle routing with rules.
 | |
| type Router struct {
 | |
| 	*mux.Router
 | |
| 	parser predicate.Parser
 | |
| }
 | |
| 
 | |
| // NewRouter returns a new router instance.
 | |
| func NewRouter() (*Router, error) {
 | |
| 	parser, err := newParser()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &Router{
 | |
| 		Router: mux.NewRouter().SkipClean(true),
 | |
| 		parser: parser,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // AddRoute add a new route to the router.
 | |
| func (r *Router) AddRoute(rule string, priority int, handler http.Handler) error {
 | |
| 	parse, err := r.parser.Parse(rule)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error while parsing rule %s: %w", rule, err)
 | |
| 	}
 | |
| 
 | |
| 	buildTree, ok := parse.(treeBuilder)
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("error while parsing rule %s", rule)
 | |
| 	}
 | |
| 
 | |
| 	if priority == 0 {
 | |
| 		priority = len(rule)
 | |
| 	}
 | |
| 
 | |
| 	route := r.NewRoute().Handler(handler).Priority(priority)
 | |
| 	return addRuleOnRoute(route, buildTree())
 | |
| }
 | |
| 
 | |
| type tree struct {
 | |
| 	matcher   string
 | |
| 	value     []string
 | |
| 	ruleLeft  *tree
 | |
| 	ruleRight *tree
 | |
| }
 | |
| 
 | |
| func path(route *mux.Route, paths ...string) error {
 | |
| 	rt := route.Subrouter()
 | |
| 
 | |
| 	for _, path := range paths {
 | |
| 		tmpRt := rt.Path(path)
 | |
| 		if tmpRt.GetError() != nil {
 | |
| 			return tmpRt.GetError()
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func pathPrefix(route *mux.Route, paths ...string) error {
 | |
| 	rt := route.Subrouter()
 | |
| 
 | |
| 	for _, path := range paths {
 | |
| 		tmpRt := rt.PathPrefix(path)
 | |
| 		if tmpRt.GetError() != nil {
 | |
| 			return tmpRt.GetError()
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func host(route *mux.Route, hosts ...string) error {
 | |
| 	for i, host := range hosts {
 | |
| 		hosts[i] = strings.ToLower(host)
 | |
| 	}
 | |
| 
 | |
| 	route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
 | |
| 		return matchHost(req, true, hosts...)
 | |
| 	})
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func matchHost(req *http.Request, insecureSNI bool, hosts ...string) bool {
 | |
| 	logger := log.FromContext(req.Context())
 | |
| 
 | |
| 	reqHost := requestdecorator.GetCanonizedHost(req.Context())
 | |
| 	if len(reqHost) == 0 {
 | |
| 		logger.Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	flatH := requestdecorator.GetCNAMEFlatten(req.Context())
 | |
| 	if len(flatH) > 0 {
 | |
| 		for _, host := range hosts {
 | |
| 			if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
 | |
| 				return true
 | |
| 			}
 | |
| 			logger.Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	for _, host := range hosts {
 | |
| 		if reqHost == host {
 | |
| 			logHostSNI(insecureSNI, req, reqHost)
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		// Check for match on trailing period on host
 | |
| 		if last := len(host) - 1; last >= 0 && host[last] == '.' {
 | |
| 			h := host[:last]
 | |
| 			if reqHost == h {
 | |
| 				logHostSNI(insecureSNI, req, reqHost)
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Check for match on trailing period on request
 | |
| 		if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
 | |
| 			h := reqHost[:last]
 | |
| 			if h == host {
 | |
| 				logHostSNI(insecureSNI, req, reqHost)
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func logHostSNI(insecureSNI bool, req *http.Request, reqHost string) {
 | |
| 	if insecureSNI && req.TLS != nil && !strings.EqualFold(reqHost, req.TLS.ServerName) {
 | |
| 		log.FromContext(req.Context()).Debugf("Router reached with Host(%q) different from SNI(%q)", reqHost, req.TLS.ServerName)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func hostSNI(route *mux.Route, hosts ...string) error {
 | |
| 	for i, host := range hosts {
 | |
| 		hosts[i] = strings.ToLower(host)
 | |
| 	}
 | |
| 
 | |
| 	route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
 | |
| 		return matchSNI(req, hosts...)
 | |
| 	})
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func matchSNI(req *http.Request, hosts ...string) bool {
 | |
| 	if req.TLS == nil {
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	if req.TLS.ServerName == "" {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	for _, host := range hosts {
 | |
| 		if strings.EqualFold(req.TLS.ServerName, host) {
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		// Check for match on trailing period on host
 | |
| 		if last := len(host) - 1; last >= 0 && host[last] == '.' {
 | |
| 			h := host[:last]
 | |
| 			if strings.EqualFold(req.TLS.ServerName, h) {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Check for match on trailing period on request
 | |
| 		if last := len(req.TLS.ServerName) - 1; last >= 0 && req.TLS.ServerName[last] == '.' {
 | |
| 			h := req.TLS.ServerName[:last]
 | |
| 			if strings.EqualFold(h, host) {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func hostSecure(route *mux.Route, hosts ...string) error {
 | |
| 	for i, host := range hosts {
 | |
| 		hosts[i] = strings.ToLower(host)
 | |
| 	}
 | |
| 
 | |
| 	route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
 | |
| 		for _, host := range hosts {
 | |
| 			if matchSNI(req, host) && matchHost(req, false, host) {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return false
 | |
| 	})
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func hostRegexp(route *mux.Route, hosts ...string) error {
 | |
| 	router := route.Subrouter()
 | |
| 	for _, host := range hosts {
 | |
| 		tmpRt := router.Host(host)
 | |
| 		if tmpRt.GetError() != nil {
 | |
| 			return tmpRt.GetError()
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func methods(route *mux.Route, methods ...string) error {
 | |
| 	return route.Methods(methods...).GetError()
 | |
| }
 | |
| 
 | |
| func headers(route *mux.Route, headers ...string) error {
 | |
| 	return route.Headers(headers...).GetError()
 | |
| }
 | |
| 
 | |
| func headersRegexp(route *mux.Route, headers ...string) error {
 | |
| 	return route.HeadersRegexp(headers...).GetError()
 | |
| }
 | |
| 
 | |
| func query(route *mux.Route, query ...string) error {
 | |
| 	var queries []string
 | |
| 	for _, elem := range query {
 | |
| 		queries = append(queries, strings.Split(elem, "=")...)
 | |
| 	}
 | |
| 
 | |
| 	route.Queries(queries...)
 | |
| 	// Queries can return nil so we can't chain the GetError()
 | |
| 	return route.GetError()
 | |
| }
 | |
| 
 | |
| func addRuleOnRouter(router *mux.Router, rule *tree) error {
 | |
| 	switch rule.matcher {
 | |
| 	case "and":
 | |
| 		route := router.NewRoute()
 | |
| 		err := addRuleOnRoute(route, rule.ruleLeft)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return addRuleOnRoute(route, rule.ruleRight)
 | |
| 	case "or":
 | |
| 		err := addRuleOnRouter(router, rule.ruleLeft)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return addRuleOnRouter(router, rule.ruleRight)
 | |
| 	default:
 | |
| 		err := checkRule(rule)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return funcs[rule.matcher](router.NewRoute(), rule.value...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func addRuleOnRoute(route *mux.Route, rule *tree) error {
 | |
| 	switch rule.matcher {
 | |
| 	case "and":
 | |
| 		err := addRuleOnRoute(route, rule.ruleLeft)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return addRuleOnRoute(route, rule.ruleRight)
 | |
| 	case "or":
 | |
| 		subRouter := route.Subrouter()
 | |
| 
 | |
| 		err := addRuleOnRouter(subRouter, rule.ruleLeft)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return addRuleOnRouter(subRouter, rule.ruleRight)
 | |
| 	default:
 | |
| 		err := checkRule(rule)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return funcs[rule.matcher](route, rule.value...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func checkRule(rule *tree) error {
 | |
| 	if len(rule.value) == 0 {
 | |
| 		return fmt.Errorf("no args for matcher %s", rule.matcher)
 | |
| 	}
 | |
| 
 | |
| 	for _, v := range rule.value {
 | |
| 		if len(v) == 0 {
 | |
| 			return fmt.Errorf("empty args for matcher %s, %v", rule.matcher, rule.value)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |