Update routing syntax
Co-authored-by: Tom Moulard <tom.moulard@traefik.io>
This commit is contained in:
parent
b93141992e
commit
4d86668af3
27 changed files with 2484 additions and 2085 deletions
|
@ -1,31 +1,18 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v2/pkg/ip"
|
||||
"github.com/traefik/traefik/v2/pkg/rules"
|
||||
"github.com/traefik/traefik/v2/pkg/tcp"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
"github.com/vulcand/predicate"
|
||||
)
|
||||
|
||||
var tcpFuncs = map[string]func(*matchersTree, ...string) error{
|
||||
"HostSNI": hostSNI,
|
||||
"HostSNIRegexp": hostSNIRegexp,
|
||||
"ClientIP": clientIP,
|
||||
"ALPN": alpn,
|
||||
}
|
||||
|
||||
// ParseHostSNI extracts the HostSNIs declared in a rule.
|
||||
// This is a first naive implementation used in TCP routing.
|
||||
func ParseHostSNI(rule string) ([]string, error) {
|
||||
|
@ -261,233 +248,7 @@ func (m *matchersTree) match(meta ConnData) bool {
|
|||
return m.left.match(meta) && m.right.match(meta)
|
||||
default:
|
||||
// This should never happen as it should have been detected during parsing.
|
||||
log.Warn().Msgf("Invalid rule operator %s", m.operator)
|
||||
log.Warn().Str("operator", m.operator).Msg("Invalid rule operator")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func clientIP(tree *matchersTree, clientIPs ...string) error {
|
||||
checker, err := ip.NewChecker(clientIPs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
if meta.remoteIP == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// alpn checks if any of the connection ALPN protocols matches one of the matcher protocols.
|
||||
func alpn(tree *matchersTree, protos ...string) error {
|
||||
if len(protos) == 0 {
|
||||
return errors.New("empty value for \"ALPN\" matcher is not allowed")
|
||||
}
|
||||
|
||||
for _, proto := range protos {
|
||||
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 _, proto := range meta.alpnProtos {
|
||||
for _, filter := range protos {
|
||||
if proto == filter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var almostFQDN = regexp.MustCompile(`^[[:alnum:]\.-]+$`)
|
||||
|
||||
// hostSNI checks if the SNI Host of the connection match the matcher host.
|
||||
func hostSNI(tree *matchersTree, hosts ...string) error {
|
||||
if len(hosts) == 0 {
|
||||
return errors.New("empty value for \"HostSNI\" matcher is not allowed")
|
||||
}
|
||||
|
||||
for i, host := range hosts {
|
||||
// Special case to allow global wildcard
|
||||
if host == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !almostFQDN.MatchString(host) {
|
||||
return fmt.Errorf("invalid value for \"HostSNI\" matcher, %q is not a valid hostname", host)
|
||||
}
|
||||
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
|
||||
// it allows matching with an empty serverName.
|
||||
// Which is why we make sure to take that case into account before
|
||||
// checking meta.serverName.
|
||||
if hosts[0] == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if meta.serverName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if host == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
if host == meta.serverName {
|
||||
return true
|
||||
}
|
||||
|
||||
// trim trailing period in case of FQDN
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
if host == meta.serverName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp.
|
||||
func hostSNIRegexp(tree *matchersTree, templates ...string) error {
|
||||
if len(templates) == 0 {
|
||||
return fmt.Errorf("empty value for \"HostSNIRegexp\" matcher is not allowed")
|
||||
}
|
||||
|
||||
var regexps []*regexp.Regexp
|
||||
|
||||
for _, template := range templates {
|
||||
preparedPattern, err := preparePattern(template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pattern value for \"HostSNIRegexp\" matcher, %q is not a valid pattern: %w", template, err)
|
||||
}
|
||||
|
||||
regexp, err := regexp.Compile(preparedPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regexps = append(regexps, regexp)
|
||||
}
|
||||
|
||||
tree.matcher = func(meta ConnData) bool {
|
||||
for _, regexp := range regexps {
|
||||
if regexp.MatchString(meta.serverName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: expose more of containous/mux fork to get rid of the following copied code (https://github.com/containous/mux/blob/8ffa4f6d063c/regexp.go).
|
||||
|
||||
// preparePattern builds a regexp pattern from the initial user defined expression.
|
||||
// This function reuses the code dedicated to host matching of the newRouteRegexp func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func preparePattern(template string) (string, error) {
|
||||
// Check if it is well-formed.
|
||||
idxs, errBraces := braceIndices(template)
|
||||
if errBraces != nil {
|
||||
return "", errBraces
|
||||
}
|
||||
|
||||
defaultPattern := "[^.]+"
|
||||
pattern := bytes.NewBufferString("")
|
||||
|
||||
// Host SNI matching is case-insensitive
|
||||
fmt.Fprint(pattern, "(?i)")
|
||||
|
||||
pattern.WriteByte('^')
|
||||
var end int
|
||||
var err error
|
||||
for i := 0; i < len(idxs); i += 2 {
|
||||
// Set all values we are interested in.
|
||||
raw := template[end:idxs[i]]
|
||||
end = idxs[i+1]
|
||||
parts := strings.SplitN(template[idxs[i]+1:end-1], ":", 2)
|
||||
name := parts[0]
|
||||
patt := defaultPattern
|
||||
if len(parts) == 2 {
|
||||
patt = parts[1]
|
||||
}
|
||||
// Name or pattern can't be empty.
|
||||
if name == "" || patt == "" {
|
||||
return "", fmt.Errorf("mux: missing name or pattern in %q",
|
||||
template[idxs[i]:end])
|
||||
}
|
||||
// Build the regexp pattern.
|
||||
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||
|
||||
// Append variable name and compiled pattern.
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the remaining.
|
||||
raw := template[end:]
|
||||
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||
pattern.WriteByte('$')
|
||||
|
||||
return pattern.String(), nil
|
||||
}
|
||||
|
||||
// varGroupName builds a capturing group name for the indexed variable.
|
||||
// This function is a copy of varGroupName func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func varGroupName(idx int) string {
|
||||
return "v" + strconv.Itoa(idx)
|
||||
}
|
||||
|
||||
// braceIndices returns the first level curly brace indices from a string.
|
||||
// This function is a copy of braceIndices func from the gorilla/mux library.
|
||||
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
|
||||
func braceIndices(s string) ([]int, error) {
|
||||
var level, idx int
|
||||
var idxs []int
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '{':
|
||||
if level++; level == 1 {
|
||||
idx = i
|
||||
}
|
||||
case '}':
|
||||
if level--; level == 0 {
|
||||
idxs = append(idxs, idx, i+1)
|
||||
} else if level < 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if level != 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
return idxs, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue