diff --git a/pkg/api/ratelimit.go b/pkg/api/ratelimit.go index d4b6640..950814f 100644 --- a/pkg/api/ratelimit.go +++ b/pkg/api/ratelimit.go @@ -17,4 +17,5 @@ type RateLimitDescriptor struct { SourceCluster bool `json:"sourceCluster" yaml:"sourceCluster"` RemoteAddress bool `json:"remoteAddress" yaml:"remoteAddress"` RequestHeader string `json:"requestHeader" yaml:"requestHeader"` + MTLSSubject bool `json:"mTLSSubject" yaml:"mTLSSubject"` } diff --git a/pkg/api/rule.go b/pkg/api/rule.go index 04b7593..a055e22 100644 --- a/pkg/api/rule.go +++ b/pkg/api/rule.go @@ -61,5 +61,6 @@ type RuleActionsDirectResponse struct { } type Listener struct { - MTLS string `json:"mTLS" yaml:"mTLS"` + MTLS string `json:"mTLS" yaml:"mTLS"` + DisableOnDefault bool `json:"disableOnDefault" yaml:"disableOnDefault"` } diff --git a/pkg/envoy/jwtprovider.go b/pkg/envoy/jwtprovider.go index 101d528..138c9d6 100644 --- a/pkg/envoy/jwtprovider.go +++ b/pkg/envoy/jwtprovider.go @@ -125,8 +125,7 @@ func (j *JwtProvider) getJwtRule(conditions Conditions, clusterName string, jwtP Match: &route.RouteMatch{ PathSpecifier: &route.RouteMatch_SafeRegex{ SafeRegex: &matcher.RegexMatcher{ - EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}}, - Regex: conditions.Regex, + Regex: conditions.Regex, }, }, Headers: hostnameHeaders, @@ -141,8 +140,7 @@ func (j *JwtProvider) getJwtRule(conditions Conditions, clusterName string, jwtP Match: &route.RouteMatch{ PathSpecifier: &route.RouteMatch_SafeRegex{ SafeRegex: &matcher.RegexMatcher{ - EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}}, - Regex: conditions.Regex, + Regex: conditions.Regex, }, }, Headers: append(hostnameHeaders, methodHeader), diff --git a/pkg/envoy/listener.go b/pkg/envoy/listener.go index 45be0a7..1f3b8d7 100644 --- a/pkg/envoy/listener.go +++ b/pkg/envoy/listener.go @@ -40,7 +40,7 @@ type Listener struct { httpFilter []*hcm.HttpFilter tracing *hcm.HttpConnectionManager_Tracing accessLoggerConfig []*alf.AccessLog - rateLimits []*route.RateLimit + rateLimits map[string][]*route.RateLimit rateLimitsMapping map[string]uint mTLSListenerDefaultsMapping map[string]listenerDefaultsMapping } @@ -60,7 +60,7 @@ func newListener() *Listener { }, } listener.accessLoggerConfig = []*alf.AccessLog{} - listener.rateLimits = []*route.RateLimit{} + listener.rateLimits = make(map[string][]*route.RateLimit) listener.rateLimitsMapping = make(map[string]uint) listener.mTLSListenerDefaultsMapping = make(map[string]listenerDefaultsMapping) @@ -213,8 +213,7 @@ func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, target if regexRewrite.Regex != "" { envoyRegexRewrite = &matcher.RegexMatchAndSubstitute{ Pattern: &matcher.RegexMatcher{ - Regex: regexRewrite.Regex, - EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}}, + Regex: regexRewrite.Regex, }, Substitution: regexRewrite.Substitution, } @@ -303,8 +302,7 @@ func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, target Match: &route.RouteMatch{ PathSpecifier: &route.RouteMatch_SafeRegex{ SafeRegex: &matcher.RegexMatcher{ - EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}}, - Regex: targetPrefix, + Regex: targetPrefix, }, }, }, @@ -316,8 +314,7 @@ func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, target Match: &route.RouteMatch{ PathSpecifier: &route.RouteMatch_SafeRegex{ SafeRegex: &matcher.RegexMatcher{ - EngineType: &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{}}, - Regex: targetPrefix, + Regex: targetPrefix, }, }, Headers: []*route.HeaderMatcher{header}, @@ -351,7 +348,9 @@ func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, target } // set ratelimits if isDefaultListener(listenerName) || l.HasMTLSDefault(listenerName, "envoy.filters.http.ratelimit") { - newVirtualhost.RateLimits = l.rateLimits + if _, ok := l.rateLimits[listenerName]; ok { + newVirtualhost.RateLimits = l.rateLimits[listenerName] + } } return newVirtualhost } @@ -837,17 +836,19 @@ func (l *Listener) updateDefaultAuthzSetting(listenerParams ListenerParams, auth func (l *Listener) updateDefaultRateLimit(rateLimitParams RateLimitParams) { r := newRateLimit() - if getListenerHTTPFilterIndex("envoy.filters.http.ratelimit", l.httpFilter) == -1 { - rateLimitConfigEncoded, err := r.getRateLimitConfigEncoded(rateLimitParams) - if err != nil { - logger.Errorf("Couldn't update default rateLimit filter: %s", err) - return - } - if rateLimitConfigEncoded == nil { - return - } + if !rateLimitParams.Listener.DisableOnDefault { + if getListenerHTTPFilterIndex("envoy.filters.http.ratelimit", l.httpFilter) == -1 { + rateLimitConfigEncoded, err := r.getRateLimitConfigEncoded(rateLimitParams) + if err != nil { + logger.Errorf("Couldn't update default rateLimit filter: %s", err) + return + } + if rateLimitConfigEncoded == nil { + return + } - updateHTTPFilterWithConfig(&l.httpFilter, "envoy.filters.http.ratelimit", rateLimitConfigEncoded) + updateHTTPFilterWithConfig(&l.httpFilter, "envoy.filters.http.ratelimit", rateLimitConfigEncoded) + } } rateLimitVirtualHostConfig, err := r.getRateLimitVirtualHostConfig(rateLimitParams) @@ -855,16 +856,29 @@ func (l *Listener) updateDefaultRateLimit(rateLimitParams RateLimitParams) { logger.Errorf("Couldn't update ratelimit: %s", err) return } - if val, ok := l.rateLimitsMapping[rateLimitParams.Name]; ok { - l.rateLimits[val] = rateLimitVirtualHostConfig - } else { - l.rateLimits = append(l.rateLimits, rateLimitVirtualHostConfig) - l.rateLimitsMapping[rateLimitParams.Name] = uint(len(l.rateLimits) - 1) + if !rateLimitParams.Listener.DisableOnDefault { + l.updateDefaultRateLimitByListener("l_http", rateLimitParams, rateLimitVirtualHostConfig) + } + if rateLimitParams.Listener.MTLS != "" { + l.updateDefaultRateLimitByListener("l_mtls_"+rateLimitParams.Listener.MTLS, rateLimitParams, rateLimitVirtualHostConfig) } + // set mTLS listeners defaults l.setMTLSDefault(rateLimitParams.Listener.MTLS, "envoy.filters.http.ratelimit") } +func (l *Listener) updateDefaultRateLimitByListener(listenerName string, rateLimitParams RateLimitParams, rateLimitVirtualHostConfig *route.RateLimit) { + if l.rateLimits[listenerName] == nil { + l.rateLimits[listenerName] = []*route.RateLimit{} + } + if val, ok := l.rateLimitsMapping[listenerName+"#"+rateLimitParams.Name]; ok { + l.rateLimits[listenerName][val] = rateLimitVirtualHostConfig + } else { + l.rateLimits[listenerName] = append(l.rateLimits[listenerName], rateLimitVirtualHostConfig) + } + l.rateLimitsMapping[listenerName+"#"+rateLimitParams.Name] = uint(len(l.rateLimits[listenerName]) - 1) +} + func (l *Listener) updateDefaultLuaFilter(luaFilterParams LuaFilterParams) { lf := newLuaFilter() luaFilterConfigEncoded, err := lf.getLuaFilterConfigEncoded(luaFilterParams) diff --git a/pkg/envoy/mtls.go b/pkg/envoy/mtls.go index 481bbf9..4e6bef5 100644 --- a/pkg/envoy/mtls.go +++ b/pkg/envoy/mtls.go @@ -74,11 +74,14 @@ func (l *MTLS) updateMTLSListener(cache *WorkQueueCache, params ListenerParams, } ll.FilterChains[0].Filters = append(rbacFilter, ll.FilterChains[0].Filters...) } - matchSubjectAltNames := make([]*matcher.StringMatcher, len(mTLSParams.AllowedSubjectAltNames)) + matchSubjectAltNames := make([]*tls.SubjectAltNameMatcher, len(mTLSParams.AllowedSubjectAltNames)) for k, name := range mTLSParams.AllowedSubjectAltNames { - matchSubjectAltNames[k] = &matcher.StringMatcher{ - MatchPattern: &matcher.StringMatcher_Exact{ - Exact: name, + matchSubjectAltNames[k] = &tls.SubjectAltNameMatcher{ + SanType: tls.SubjectAltNameMatcher_DNS, + Matcher: &matcher.StringMatcher{ + MatchPattern: &matcher.StringMatcher_Exact{ + Exact: name, + }, }, } } @@ -110,7 +113,7 @@ func (l *MTLS) updateMTLSListener(cache *WorkQueueCache, params ListenerParams, }, ValidationContextType: &tls.CommonTlsContext_ValidationContext{ ValidationContext: &tls.CertificateValidationContext{ - MatchSubjectAltNames: matchSubjectAltNames, + MatchTypedSubjectAltNames: matchSubjectAltNames, TrustedCa: &core.DataSource{ Specifier: &core.DataSource_InlineString{ InlineString: mTLSParams.CACertificate, diff --git a/pkg/envoy/ratelimit.go b/pkg/envoy/ratelimit.go index 72fb42a..d4c3a2d 100644 --- a/pkg/envoy/ratelimit.go +++ b/pkg/envoy/ratelimit.go @@ -8,23 +8,26 @@ import ( rlc "github.com/envoyproxy/go-control-plane/envoy/config/ratelimit/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" rl "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ratelimit/v3" + ssl "github.com/envoyproxy/go-control-plane/envoy/extensions/matching/common_inputs/ssl/v3" any "github.com/golang/protobuf/ptypes/any" "google.golang.org/protobuf/types/known/anypb" ) type RateLimit struct { - enabled bool + enabled map[string]bool } func newRateLimit() *RateLimit { - return &RateLimit{} + return &RateLimit{ + enabled: make(map[string]bool), + } } -func (r *RateLimit) updateListenersWithRateLimit(cache *WorkQueueCache, params RateLimitParams, rateLimits []*route.RateLimit) error { +func (r *RateLimit) updateListenersWithRateLimit(cache *WorkQueueCache, params RateLimitParams, rateLimits map[string][]*route.RateLimit) error { // update listener for listenerKey := range cache.listeners { ll := cache.listeners[listenerKey].(*api.Listener) - if isDefaultListener(ll.GetName()) || "l_mtls_"+params.Listener.MTLS == ll.GetName() { // only update listener if it is default listener / mTLS listener is selected + if (isDefaultListener(ll.GetName()) && !params.Listener.DisableOnDefault) || "l_mtls_"+params.Listener.MTLS == ll.GetName() { // only update listener if it is default listener (and disableOnDefault is not true) / mTLS listener is selected for filterchainID := range ll.FilterChains { for filterID := range ll.FilterChains[filterchainID].Filters { // get manager @@ -39,9 +42,10 @@ func (r *RateLimit) updateListenersWithRateLimit(cache *WorkQueueCache, params R return err } - if !r.enabled { + if _, enabled := r.enabled[ll.GetName()]; !enabled { // update http filter updateHTTPFilterWithConfig(&manager.HttpFilters, "envoy.filters.http.ratelimit", rateLimitConfigEncoded) + r.enabled[ll.GetName()] = true } // update virtualhosts @@ -51,7 +55,9 @@ func (r *RateLimit) updateListenersWithRateLimit(cache *WorkQueueCache, params R } for k := range routeSpecifier.RouteConfig.VirtualHosts { - routeSpecifier.RouteConfig.VirtualHosts[k].RateLimits = rateLimits + if _, ok := rateLimits[ll.GetName()]; ok { + routeSpecifier.RouteConfig.VirtualHosts[k].RateLimits = rateLimits[ll.GetName()] + } } manager.RouteSpecifier = routeSpecifier @@ -69,8 +75,6 @@ func (r *RateLimit) updateListenersWithRateLimit(cache *WorkQueueCache, params R } } - r.enabled = true - return nil } @@ -140,6 +144,20 @@ func (r *RateLimit) getRateLimitVirtualHostConfig(params RateLimitParams) (*rout }, }) } + if params.Listener.MTLS != "" && descriptor.MTLSSubject { + extensionConfig, err := anypb.New(&ssl.SubjectInput{}) + if err != nil { + return nil, err + } + actions = append(actions, &route.RateLimit_Action{ + ActionSpecifier: &route.RateLimit_Action_Extension{ + Extension: &core.TypedExtensionConfig{ + Name: "mtls_subject", + TypedConfig: extensionConfig, + }, + }, + }) + } actions = append(actions, &route.RateLimit_Action{ ActionSpecifier: &route.RateLimit_Action_GenericKey_{ GenericKey: &route.RateLimit_Action_GenericKey{ diff --git a/pkg/envoy/testdata/test-ratelimit-mtls-disableondefault.yaml b/pkg/envoy/testdata/test-ratelimit-mtls-disableondefault.yaml new file mode 100644 index 0000000..8fc1408 --- /dev/null +++ b/pkg/envoy/testdata/test-ratelimit-mtls-disableondefault.yaml @@ -0,0 +1,12 @@ +api: proxy.in4it.io/v1 +kind: rateLimit +metadata: + name: ratelimit-disable-on-default +spec: + listener: + mTLS: test-mtls + disableOnDefault: true + descriptors: + - mTLSSubject: true + requestPerUnit: 1 + Unit: hour diff --git a/pkg/envoy/types.go b/pkg/envoy/types.go index 1eba560..7c7714e 100644 --- a/pkg/envoy/types.go +++ b/pkg/envoy/types.go @@ -69,6 +69,7 @@ type ListenerParamsListener struct { MTLS string Port int64 StripAnyHostPort bool + DisableOnDefault bool } type ChallengeParams struct { @@ -187,6 +188,7 @@ type RateLimitDescriptor struct { SourceCluster bool RemoteAddress bool RequestHeader string + MTLSSubject bool } type MTLSParams struct { diff --git a/pkg/envoy/xds.go b/pkg/envoy/xds.go index 9a2eb18..76350fc 100644 --- a/pkg/envoy/xds.go +++ b/pkg/envoy/xds.go @@ -425,6 +425,7 @@ func (x *XDS) importRateLimit(rateLimit pkgApi.RateLimit) ([]WorkQueueItem, erro SourceCluster: descriptor.SourceCluster, RemoteAddress: descriptor.RemoteAddress, RequestHeader: descriptor.RequestHeader, + MTLSSubject: descriptor.MTLSSubject, }) } return []WorkQueueItem{ @@ -434,7 +435,8 @@ func (x *XDS) importRateLimit(rateLimit pkgApi.RateLimit) ([]WorkQueueItem, erro Name: rateLimit.Metadata.Name, Descriptors: descriptors, Listener: ListenerParamsListener{ - MTLS: rateLimit.Spec.Listener.MTLS, + MTLS: rateLimit.Spec.Listener.MTLS, + DisableOnDefault: rateLimit.Spec.Listener.DisableOnDefault, }, }, }, @@ -857,7 +859,7 @@ func (x *XDS) launchCreateCert(name string, domains []string) WorkQueueItem { return workQueueItem } -//ReceiveNotification receives notification items and will process them +// ReceiveNotification receives notification items and will process them func (x *XDS) ReceiveNotification(notifications []*notification.NotificationRequest_NotificationItem) error { var ( workQueueItems []WorkQueueItem diff --git a/pkg/envoy/xds_test.go b/pkg/envoy/xds_test.go index 5b3c51e..4f4dae9 100644 --- a/pkg/envoy/xds_test.go +++ b/pkg/envoy/xds_test.go @@ -1168,6 +1168,108 @@ func TestRateLimitObjectWithMTLS3(t *testing.T) { } } } +func TestRateLimitObjectWithMTLS4(t *testing.T) { + logger.SetLogLevel(loggo.DEBUG) + s, err := initStorage() + if err != nil { + t.Errorf("Couldn't initialize storage: %s", err) + return + } + x := NewXDS(s, "", "") + ObjectFileNames := []string{"test1.yaml", "test-mtls.yaml", "test-ratelimit-mtls-disableondefault.yaml"} + for _, filename := range ObjectFileNames { + newItems, err := x.putObject(filename) + if err != nil { + t.Errorf("PutObject failed: %s", err) + return + } + _, err = x.workQueue.Submit(newItems) + if err != nil { + t.Errorf("WorkQueue error: %s", err) + return + } + } + httpDefaultFound := false + for listenerKey := range x.workQueue.cache.listeners { + ll := x.workQueue.cache.listeners[listenerKey].(*api.Listener) + if ll.GetName() == "l_http" { + httpDefaultFound = true + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("getListenerHTTPConnectionManager error: %s", err) + return + } + if getListenerHTTPFilterIndex("envoy.filters.http.ratelimit", manager.HttpFilters) != -1 { + t.Errorf("envoy.filters.http.ratelimit found in httprouter filter - should be found") + return + } + } else { + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("getListenerHTTPConnectionManager error: %s", err) + return + } + if getListenerHTTPFilterIndex("envoy.filters.http.ratelimit", manager.HttpFilters) == -1 { + t.Errorf("envoy.filters.http.ratelimit not found in httprouter filter - should be found") + return + } + } + } + if !httpDefaultFound { + t.Fatalf("default listener not found") + } +} +func TestRateLimitObjectWithMTLS5(t *testing.T) { + logger.SetLogLevel(loggo.DEBUG) + s, err := initStorage() + if err != nil { + t.Errorf("Couldn't initialize storage: %s", err) + return + } + x := NewXDS(s, "", "") + ObjectFileNames := []string{"test1.yaml", "test-mtls.yaml", "test-ratelimit.yaml", "test-ratelimit-mtls-disableondefault.yaml"} + for _, filename := range ObjectFileNames { + newItems, err := x.putObject(filename) + if err != nil { + t.Errorf("PutObject failed: %s", err) + return + } + _, err = x.workQueue.Submit(newItems) + if err != nil { + t.Errorf("WorkQueue error: %s", err) + return + } + } + httpDefaultFound := false + for listenerKey := range x.workQueue.cache.listeners { + ll := x.workQueue.cache.listeners[listenerKey].(*api.Listener) + if ll.GetName() == "l_http" { + httpDefaultFound = true + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("getListenerHTTPConnectionManager error: %s", err) + return + } + if getListenerHTTPFilterIndex("envoy.filters.http.ratelimit", manager.HttpFilters) == -1 { + t.Errorf("envoy.filters.http.ratelimit not found in httprouter filter - should be found") + return + } + } else { + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("getListenerHTTPConnectionManager error: %s", err) + return + } + if getListenerHTTPFilterIndex("envoy.filters.http.ratelimit", manager.HttpFilters) == -1 { + t.Errorf("envoy.filters.http.ratelimit not found in mtls httprouter filter - should be found") + return + } + } + } + if !httpDefaultFound { + t.Fatalf("default listener not found") + } +} func TestAuthzObjectWithMTLS2(t *testing.T) { logger.SetLogLevel(loggo.DEBUG) s, err := initStorage()