mirror of
				https://github.com/traefik/traefik.git
				synced 2025-10-31 08:21:27 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			135 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package tcp
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"github.com/go-acme/lego/v4/challenge/tlsalpn01"
 | |
| 	"github.com/rs/zerolog/log"
 | |
| 	"github.com/traefik/traefik/v3/pkg/ip"
 | |
| )
 | |
| 
 | |
| var tcpFuncs = map[string]func(*matchersTree, ...string) error{
 | |
| 	"ALPN":          expect1Parameter(alpn),
 | |
| 	"ClientIP":      expect1Parameter(clientIP),
 | |
| 	"HostSNI":       expect1Parameter(hostSNI),
 | |
| 	"HostSNIRegexp": expect1Parameter(hostSNIRegexp),
 | |
| }
 | |
| 
 | |
| func expect1Parameter(fn func(*matchersTree, ...string) error) func(*matchersTree, ...string) error {
 | |
| 	return func(route *matchersTree, s ...string) error {
 | |
| 		if len(s) != 1 {
 | |
| 			return fmt.Errorf("unexpected number of parameters; got %d, expected 1", len(s))
 | |
| 		}
 | |
| 
 | |
| 		return fn(route, s...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // alpn checks if any of the connection ALPN protocols matches one of the matcher protocols.
 | |
| func alpn(tree *matchersTree, protos ...string) error {
 | |
| 	proto := protos[0]
 | |
| 
 | |
| 	if proto == tlsalpn01.ACMETLS1Protocol {
 | |
| 		return fmt.Errorf("invalid protocol value for ALPN matcher, %q is not allowed", proto)
 | |
| 	}
 | |
| 
 | |
| 	tree.matcher = func(meta ConnData) bool {
 | |
| 		for _, alpnProto := range meta.alpnProtos {
 | |
| 			if alpnProto == proto {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func clientIP(tree *matchersTree, clientIP ...string) error {
 | |
| 	checker, err := ip.NewChecker(clientIP)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("initializing IP checker for ClientIP matcher: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	tree.matcher = func(meta ConnData) bool {
 | |
| 		ok, err := checker.Contains(meta.remoteIP)
 | |
| 		if err != nil {
 | |
| 			log.Warn().Err(err).Msg("ClientIP matcher: could not match remote address")
 | |
| 			return false
 | |
| 		}
 | |
| 		return ok
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var hostOrIP = regexp.MustCompile(`^[[:alnum:]\.\-\:]+$`)
 | |
| 
 | |
| // hostSNI checks if the SNI Host of the connection match the matcher host.
 | |
| func hostSNI(tree *matchersTree, hosts ...string) error {
 | |
| 	host := hosts[0]
 | |
| 
 | |
| 	if host == "*" {
 | |
| 		// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
 | |
| 		// it allows matching with an empty serverName.
 | |
| 		tree.matcher = func(meta ConnData) bool { return true }
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if !hostOrIP.MatchString(host) {
 | |
| 		return fmt.Errorf("invalid value for HostSNI matcher, %q is not a valid hostname", host)
 | |
| 	}
 | |
| 
 | |
| 	tree.matcher = func(meta ConnData) bool {
 | |
| 		if meta.serverName == "" {
 | |
| 			return false
 | |
| 		}
 | |
| 
 | |
| 		if host == meta.serverName {
 | |
| 			return true
 | |
| 		}
 | |
| 
 | |
| 		// trim trailing period in case of FQDN
 | |
| 		host = strings.TrimSuffix(host, ".")
 | |
| 
 | |
| 		return host == meta.serverName
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp.
 | |
| func hostSNIRegexp(tree *matchersTree, templates ...string) error {
 | |
| 	template := templates[0]
 | |
| 
 | |
| 	if !isASCII(template) {
 | |
| 		return fmt.Errorf("invalid value for HostSNIRegexp matcher, %q is not a valid hostname", template)
 | |
| 	}
 | |
| 
 | |
| 	re, err := regexp.Compile(template)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("compiling HostSNIRegexp matcher: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	tree.matcher = func(meta ConnData) bool {
 | |
| 		return re.MatchString(meta.serverName)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // isASCII checks if the given string contains only ASCII characters.
 | |
| func isASCII(s string) bool {
 | |
| 	for i := range len(s) {
 | |
| 		if s[i] >= utf8.RuneSelf {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 |