Skip to content

Commit

Permalink
Wildcard host matching (#2)
Browse files Browse the repository at this point in the history
1. Spelling mistake: `Forceable` -> `Forcible`
2. Clean structure
3. Added host wildcard matching using either `lingress.echocat.org/hosts-wildcard` annotation and/or `--hosts.wildcard` parameter.
  • Loading branch information
blaubaer authored Nov 21, 2020
1 parent 0a0b8ce commit d487515
Show file tree
Hide file tree
Showing 23 changed files with 777 additions and 352 deletions.
3 changes: 3 additions & 0 deletions context/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
ResultFallback SimpleResult = 7
ResultFailedWithUnauthorized SimpleResult = 8
ResultFailedWithClientGone SimpleResult = 9
ResultFailedWithIllegalHost SimpleResult = 10
)

var (
Expand All @@ -41,6 +42,7 @@ var (
ResultFailedWithUnauthorized: "unauthorized",
ResultFallback: "fallback",
ResultFailedWithClientGone: "clientGone",
ResultFailedWithIllegalHost: "illegalHost",
}

resultToStatus = map[Result]int{
Expand All @@ -54,6 +56,7 @@ var (
ResultFailedWithUnauthorized: http.StatusUnauthorized,
ResultFallback: http.StatusOK,
ResultFailedWithClientGone: http.StatusGone,
ResultFailedWithIllegalHost: http.StatusUnprocessableEntity,
}
)

Expand Down
2 changes: 1 addition & 1 deletion context/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@ func (instance *Upstream) ApplyToMap(r rules.Rule, prefix string, to *map[string
}
if r != nil {
(*to)[prefix+FieldUpstreamSource] = r.Source().String()
(*to)[prefix+FieldUpstreamMatches] = r.Host() + "/" + strings.Join(r.Path(), "/")
(*to)[prefix+FieldUpstreamMatches] = r.Host().String() + "/" + strings.Join(r.Path(), "/")
}
}
15 changes: 8 additions & 7 deletions proxy/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/echocat/lingress/context"
"github.com/echocat/lingress/rules"
"github.com/echocat/lingress/support"
"github.com/echocat/lingress/value"
"net"
"net/http"
"strconv"
Expand All @@ -16,26 +17,26 @@ func init() {
}

var (
defaultMaxAge = rules.Duration(24 * time.Hour)
defaultMaxAge = value.Duration(24 * time.Hour)
)

type CorsInterceptor struct {
AllowedOriginsHost rules.ForcibleHostPatterns
AllowedMethods rules.ForcibleMethods
AllowedHeaders rules.ForcibleHeaderNames
AllowedCredentials rules.ForcibleBool
MaxAge rules.ForcibleDuration
Enabled rules.ForcibleBool
AllowedCredentials value.ForcibleBool
MaxAge value.ForcibleDuration
Enabled value.ForcibleBool
}

func NewCorsInterceptor() *CorsInterceptor {
return &CorsInterceptor{
AllowedOriginsHost: rules.NewForcibleHostPatterns(rules.HostPatterns{}, false),
AllowedMethods: rules.NewForcibleMethods(rules.Methods{}, false),
AllowedHeaders: rules.NewForcibleHeaders(rules.HeaderNames{}, false),
AllowedCredentials: rules.NewForcibleBool(rules.True, false),
MaxAge: rules.NewForcibleDuration(defaultMaxAge, false),
Enabled: rules.NewForcibleBool(rules.False, false),
AllowedCredentials: value.NewForcibleBool(value.True, false),
MaxAge: value.NewForcibleDuration(defaultMaxAge, false),
Enabled: value.NewForcibleBool(value.False, false),
}
}

Expand Down
11 changes: 9 additions & 2 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/echocat/lingress/rules"
"github.com/echocat/lingress/server"
"github.com/echocat/lingress/support"
"github.com/echocat/lingress/value"
log "github.com/sirupsen/logrus"
"io"
"net"
Expand Down Expand Up @@ -160,15 +161,21 @@ func (instance *Proxy) ServeHTTP(connector server.Connector, resp http.ResponseW
}
}()

ctx.Stage = lctx.StageEvaluateClientRequest
var host value.Fqdn
if err := host.Set(ctx.Client.Host()); err != nil {
instance.markDone(lctx.ResultFailedWithIllegalHost, ctx, err)
return
}

query := rules.Query{
Host: ctx.Client.Host(),
Host: host,
Path: ctx.Client.Request.RequestURI,
}
if u := ctx.Client.Request.URL; u != nil {
query.Path = u.Path
}

ctx.Stage = lctx.StageEvaluateClientRequest
rs, err := instance.RulesRepository.FindBy(query)
if err != nil {
instance.markDone(lctx.ResultFailedWithUnexpectedError, ctx, err)
Expand Down
5 changes: 3 additions & 2 deletions proxy/secure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/echocat/lingress/context"
"github.com/echocat/lingress/rules"
"github.com/echocat/lingress/support"
"github.com/echocat/lingress/value"
"net"
"net/http"
)
Expand All @@ -16,12 +17,12 @@ func init() {
}

type ForceSecureInterceptor struct {
Enabled rules.ForcibleBool
Enabled value.ForcibleBool
}

func NewForceSecureInterceptor() *ForceSecureInterceptor {
return &ForceSecureInterceptor{
Enabled: rules.NewForcibleBool(rules.False, false),
Enabled: value.NewForcibleBool(value.False, false),
}
}

Expand Down
167 changes: 116 additions & 51 deletions rules/byHost.go
Original file line number Diff line number Diff line change
@@ -1,107 +1,172 @@
package rules

import (
"github.com/echocat/lingress/value"
)

type ByHost struct {
values map[string]*ByPath
fallback *ByPath
hostFullMatch map[value.Fqdn]*ByPath
hostPrefixWildcardMatch map[value.Fqdn]*ByPath
allHostsMatching *ByPath

onAdded OnAdded
onRemoved OnRemoved
onAdded OnAdded
onRemoved OnRemoved
findStrategies []func(host value.Fqdn, path []string) (Rules, error)
}

func NewByHost(onAdded OnAdded, onRemoved OnRemoved) *ByHost {
return &ByHost{
values: make(map[string]*ByPath),
fallback: NewByPath(onAdded, onRemoved),
result := &ByHost{
hostFullMatch: make(map[value.Fqdn]*ByPath),
hostPrefixWildcardMatch: make(map[value.Fqdn]*ByPath),
allHostsMatching: NewByPath(onAdded, onRemoved),

onAdded: onAdded,
onRemoved: onRemoved,
}
return result
}

func (instance *ByHost) All(consumer func(Rule) error) error {
for _, value := range instance.values {
for _, value := range instance.hostFullMatch {
if err := value.All(consumer); err != nil {
return err
}
}
if err := instance.fallback.All(consumer); err != nil {
if err := instance.allHostsMatching.All(consumer); err != nil {
return err
}
return nil
}

func (instance *ByHost) Find(host string, path []string) (Rules, error) {
var byHost, fallback Rules
if v, ok := instance.values[host]; ok {
if r, err := v.Find(path); err != nil {
func (instance *ByHost) Find(host value.Fqdn, path []string) (Rules, error) {
var result Rules = rules{}
for _, strategy := range allFindHostByStrategies {
candidate, err := strategy(instance, host, path)
if err != nil {
return nil, err
} else if r != nil && r.Len() > 0 {
byHost = r
}
}
if v := instance.fallback; v != nil {
if r, err := instance.fallback.Find(path); err != nil {
return nil, err
} else if r != nil && r.Len() > 0 {
fallback = r
if candidate == nil {
continue
}
resultAny, candidateAny := result.Any(), candidate.Any()
if candidateAny == nil {
continue
}
if resultAny == nil || len(candidateAny.Path()) > len(resultAny.Path()) {
result = candidate
}
}

if byHost != nil && fallback != nil {
byHostPath := byHost.Any().Path()
fallbackPath := fallback.Any().Path()
if len(fallbackPath) > len(byHostPath) {
return fallback, nil
}
return byHost, nil
} else if byHost != nil {
return byHost, nil
} else if fallback != nil {
return fallback, nil
return result, nil
}

var allFindHostByStrategies = []func(instance *ByHost, host value.Fqdn, path []string) (Rules, error){
findHostByFullMatchStrategy,
findHostByPrefixWildcardMatchStrategy,
findHostByAllHostsMatchStrategy,
}

func findHostByFullMatchStrategy(instance *ByHost, host value.Fqdn, path []string) (Rules, error) {
v, ok := instance.hostFullMatch[host]
if !ok {
return nil, nil
}
r, err := v.Find(path)
if err != nil {
return nil, err
}
if r == nil || r.Len() <= 0 {
return nil, nil
}
return r, nil
}

func findHostByPrefixWildcardMatchStrategy(instance *ByHost, host value.Fqdn, path []string) (Rules, error) {
v, ok := instance.hostPrefixWildcardMatch[host.Parent()]
if !ok {
return nil, nil
}
r, err := v.Find(path)
if err != nil {
return nil, err
}
if r == nil || r.Len() <= 0 {
return nil, nil
}
return r, nil
}

return rules{}, nil
func findHostByAllHostsMatchStrategy(instance *ByHost, _ value.Fqdn, path []string) (Rules, error) {
r, err := instance.allHostsMatching.Find(path)
if err != nil {
return nil, err
}
if r == nil || r.Len() <= 0 {
return nil, nil
}
return r, nil
}

func (instance *ByHost) Put(r Rule) error {
host := r.Host()
hostMaybeWithWildcard := r.Host()

if host == "" {
return instance.fallback.Put(r)
} else if existing, ok := instance.values[host]; ok {
return existing.Put(r)
if hostMaybeWithWildcard == "" {
return instance.allHostsMatching.Put(r)
}

hadWildcard, host, err := hostMaybeWithWildcard.WithoutWildcard()
if err != nil {
return err
}

var target map[value.Fqdn]*ByPath
if hadWildcard {
target = instance.hostPrefixWildcardMatch
} else {
value := NewByPath(instance.onAdded, instance.onRemoved)
instance.values[host] = value
return value.Put(r)
target = instance.hostFullMatch
}

if existing, ok := target[host]; ok {
return existing.Put(r)
}

value := NewByPath(instance.onAdded, instance.onRemoved)
target[host] = value
return value.Put(r)
}

func (instance *ByHost) Remove(predicate Predicate) error {
if err := instance.fallback.Remove(predicate); err != nil {
if err := instance.allHostsMatching.Remove(predicate); err != nil {
return err
}
for host, v := range instance.values {
for host, v := range instance.hostFullMatch {
if err := v.Remove(predicate); err != nil {
return err
}
if !v.HasContent() {
delete(instance.hostFullMatch, host)
}
}
for host, v := range instance.hostPrefixWildcardMatch {
if err := v.Remove(predicate); err != nil {
return err
}
if !v.HasContent() {
delete(instance.values, host)
delete(instance.hostPrefixWildcardMatch, host)
}
}
return nil
}

func (instance *ByHost) HasContent() bool {
return len(instance.values) > 0
}

func (instance *ByHost) Clone() *ByHost {
result := NewByHost(instance.onAdded, instance.onRemoved)

result.fallback = instance.fallback.Clone()
for k, v := range instance.values {
result.values[k] = v.Clone()
result.allHostsMatching = instance.allHostsMatching.Clone()
for k, v := range instance.hostFullMatch {
result.hostFullMatch[k] = v.Clone()
}
for k, v := range instance.hostPrefixWildcardMatch {
result.hostPrefixWildcardMatch[k] = v.Clone()
}

return result
Expand Down
5 changes: 3 additions & 2 deletions rules/headers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rules

import (
"github.com/echocat/lingress/value"
"strings"
)

Expand Down Expand Up @@ -77,13 +78,13 @@ func (instance HeaderNames) IsPresent() bool {
}

type ForcibleHeaderNames struct {
Forcible
value.Forcible
}

func NewForcibleHeaders(init HeaderNames, forced bool) ForcibleHeaderNames {
val := init
return ForcibleHeaderNames{
Forcible: NewForcible(&val, forced),
Forcible: value.NewForcible(&val, forced),
}
}

Expand Down
Loading

0 comments on commit d487515

Please sign in to comment.