Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.

Commit f3966a3

Browse files
authored
rate-limiting: add HTTP local rate limiting capability (#4846)
- Adds capability to locally (per sidecar) rate limit HTTP traffic - Adds HTTP rate limit e2e test - Clarifies allowed HTTP status codes for rate limited responses Part of #2018 Signed-off-by: Shashank Ram <[email protected]>
1 parent 82f48f7 commit f3966a3

File tree

7 files changed

+428
-20
lines changed

7 files changed

+428
-20
lines changed

pkg/apis/policy/v1alpha1/upstreamtrafficsetting.go

+2
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ type HTTPLocalRateLimitSpec struct {
182182
// ResponseStatusCode defines the HTTP status code to use for responses
183183
// to rate limited requests. Code must be in the 400-599 (inclusive)
184184
// error range. If not specified, a default of 429 (Too Many Requests) is used.
185+
// See https://www.envoyproxy.io/docs/envoy/latest/api-v3/type/v3/http_status.proto#enum-type-v3-statuscode
186+
// for the list of HTTP status codes supported by Envoy.
185187
// +optional
186188
ResponseStatusCode uint32 `json:"responseStatusCode,omitempty"`
187189

pkg/envoy/rds/route/rbac.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import (
77
"github.com/pkg/errors"
88
"google.golang.org/protobuf/types/known/anypb"
99

10-
"github.com/openservicemesh/osm/pkg/envoy"
1110
"github.com/openservicemesh/osm/pkg/envoy/rbac"
12-
1311
"github.com/openservicemesh/osm/pkg/identity"
1412
"github.com/openservicemesh/osm/pkg/trafficpolicy"
1513
)
@@ -21,7 +19,7 @@ const (
2119
// buildInboundRBACFilterForRule builds an HTTP RBAC per route filter based on the given traffic policy rule.
2220
// The principals in the RBAC policy are derived from the allowed service accounts specified in the given rule.
2321
// The permissions in the RBAC policy are implicitly set to ANY (all permissions).
24-
func buildInboundRBACFilterForRule(rule *trafficpolicy.Rule, trustDomain string) (map[string]*any.Any, error) {
22+
func buildInboundRBACFilterForRule(rule *trafficpolicy.Rule, trustDomain string) (*any.Any, error) {
2523
if rule.AllowedServiceIdentities == nil {
2624
return nil, errors.Errorf("traffipolicy.Rule.AllowedServiceIdentities not set")
2725
}
@@ -54,5 +52,5 @@ func buildInboundRBACFilterForRule(rule *trafficpolicy.Rule, trustDomain string)
5452
return nil, err
5553
}
5654

57-
return map[string]*any.Any{envoy.HTTPRBACFilterName: marshalled}, nil
55+
return marshalled, nil
5856
}

pkg/envoy/rds/route/rbac_test.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
xds_http_rbac "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
1010
tassert "github.com/stretchr/testify/assert"
1111

12-
"github.com/openservicemesh/osm/pkg/envoy"
1312
"github.com/openservicemesh/osm/pkg/envoy/rbac"
1413
"github.com/openservicemesh/osm/pkg/identity"
1514
"github.com/openservicemesh/osm/pkg/tests"
@@ -99,9 +98,8 @@ func TestBuildInboundRBACFilterForRule(t *testing.T) {
9998
return
10099
}
101100

102-
marshalled := rbacFilter[envoy.HTTPRBACFilterName]
103101
httpRBACPerRoute := &xds_http_rbac.RBACPerRoute{}
104-
err = marshalled.UnmarshalTo(httpRBACPerRoute)
102+
err = rbacFilter.UnmarshalTo(httpRBACPerRoute)
105103
assert.Nil(err)
106104

107105
rbacRules := httpRBACPerRoute.Rbac.Rules

pkg/envoy/rds/route/route_config.go

+130-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@ package route
33
import (
44
"fmt"
55
"sort"
6+
"time"
67

78
mapset "github.com/deckarep/golang-set"
89
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
10+
xds_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
911
xds_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
12+
xds_local_ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
1013
xds_matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
14+
xds_type "github.com/envoyproxy/go-control-plane/envoy/type/v3"
15+
"github.com/golang/protobuf/ptypes/any"
1116
"github.com/golang/protobuf/ptypes/duration"
1217
"github.com/golang/protobuf/ptypes/wrappers"
18+
"github.com/pkg/errors"
19+
"google.golang.org/protobuf/types/known/anypb"
1320
"google.golang.org/protobuf/types/known/durationpb"
1421
"google.golang.org/protobuf/types/known/wrapperspb"
1522

16-
"github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
23+
policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
1724

1825
"github.com/openservicemesh/osm/pkg/configurator"
1926
"github.com/openservicemesh/osm/pkg/constants"
@@ -56,6 +63,8 @@ const (
5663

5764
// authorityHeaderKey is the key corresponding to the HTTP Host/Authority header programmed as a header matcher in an Envoy route
5865
authorityHeaderKey = ":authority"
66+
67+
httpLocalRateLimiterStatsPrefix = "http_local_rate_limiter"
5968
)
6069

6170
// BuildInboundMeshRouteConfiguration constructs the Envoy constructs ([]*xds_route.RouteConfiguration) for implementing inbound and outbound routes
@@ -70,6 +79,7 @@ func BuildInboundMeshRouteConfiguration(portSpecificRouteConfigs map[int][]*traf
7079
for _, config := range configs {
7180
virtualHost := buildVirtualHostStub(inboundVirtualHost, config.Name, config.Hostnames)
7281
virtualHost.Routes = buildInboundRoutes(config.Rules, trustDomain)
82+
applyInboundVirtualHostConfig(virtualHost, config)
7383
routeConfig.VirtualHosts = append(routeConfig.VirtualHosts, virtualHost)
7484
}
7585
if featureFlags := cfg.GetFeatureFlags(); featureFlags.EnableWASMStats {
@@ -88,6 +98,100 @@ func BuildInboundMeshRouteConfiguration(portSpecificRouteConfigs map[int][]*traf
8898
return routeConfigs
8999
}
90100

101+
// applyInboundVirtualHostConfig updates the VirtualHost configuration based on the given policy
102+
func applyInboundVirtualHostConfig(vhost *xds_route.VirtualHost, policy *trafficpolicy.InboundTrafficPolicy) {
103+
if vhost == nil || policy == nil {
104+
return
105+
}
106+
107+
config := make(map[string]*any.Any)
108+
109+
// Apply VirtualHost level rate limiting config
110+
if policy.RateLimit != nil && policy.RateLimit.Local != nil && policy.RateLimit.Local.HTTP != nil {
111+
if filter, err := getLocalRateLimitFilterConfig(policy.RateLimit.Local.HTTP); err != nil {
112+
log.Error().Err(err).Msgf("Error applying local rate limiting config for vhost %s, ignoring it", vhost.Name)
113+
} else {
114+
config[envoy.HTTPLocalRateLimitFilterName] = filter
115+
}
116+
}
117+
// Add other typed filter configs below when necessary
118+
119+
vhost.TypedPerFilterConfig = config
120+
}
121+
122+
// getLocalRateLimitFilterConfig returns the marshalled HTTP local rate limiting config for the given policy
123+
func getLocalRateLimitFilterConfig(config *policyv1alpha1.HTTPLocalRateLimitSpec) (*any.Any, error) {
124+
if config == nil {
125+
return nil, nil
126+
}
127+
128+
var fillInterval time.Duration
129+
switch config.Unit {
130+
case "second":
131+
fillInterval = time.Second
132+
case "minute":
133+
fillInterval = time.Minute
134+
case "hour":
135+
fillInterval = time.Hour
136+
default:
137+
return nil, errors.Errorf("invalid unit %q for HTTP request rate limiting", config.Unit)
138+
}
139+
140+
rl := &xds_local_ratelimit.LocalRateLimit{
141+
StatPrefix: httpLocalRateLimiterStatsPrefix,
142+
TokenBucket: &xds_type.TokenBucket{
143+
MaxTokens: config.Requests + config.Burst,
144+
TokensPerFill: wrapperspb.UInt32(config.Requests),
145+
FillInterval: durationpb.New(fillInterval),
146+
},
147+
ResponseHeadersToAdd: getRateLimitHeaderValueOptions(config.ResponseHeadersToAdd),
148+
FilterEnabled: &xds_core.RuntimeFractionalPercent{
149+
DefaultValue: &xds_type.FractionalPercent{
150+
Numerator: 100,
151+
Denominator: xds_type.FractionalPercent_HUNDRED,
152+
},
153+
},
154+
FilterEnforced: &xds_core.RuntimeFractionalPercent{
155+
DefaultValue: &xds_type.FractionalPercent{
156+
Numerator: 100,
157+
Denominator: xds_type.FractionalPercent_HUNDRED,
158+
},
159+
},
160+
}
161+
162+
// Set the response status code if not specified. Envoy defaults to 429 (Too Many Requests).
163+
if config.ResponseStatusCode > 0 {
164+
rl.Status = &xds_type.HttpStatus{Code: xds_type.StatusCode(config.ResponseStatusCode)}
165+
}
166+
167+
marshalled, err := anypb.New(rl)
168+
if err != nil {
169+
return nil, err
170+
}
171+
172+
return marshalled, nil
173+
}
174+
175+
// getRateLimitHeaderValueOptions returns a list of HeaderValueOption objects corresponding
176+
// to the given list of rate limiting HTTPHeaderValue objects
177+
func getRateLimitHeaderValueOptions(headerValues []policyv1alpha1.HTTPHeaderValue) []*xds_core.HeaderValueOption {
178+
var hvOptions []*xds_core.HeaderValueOption
179+
180+
for _, hv := range headerValues {
181+
hvOptions = append(hvOptions, &xds_core.HeaderValueOption{
182+
Header: &xds_core.HeaderValue{
183+
Key: hv.Name,
184+
Value: hv.Value,
185+
},
186+
Append: &wrappers.BoolValue{
187+
Value: false,
188+
},
189+
})
190+
}
191+
192+
return hvOptions
193+
}
194+
91195
// BuildIngressConfiguration constructs the Envoy constructs ([]*xds_route.RouteConfiguration) for implementing ingress routes
92196
func BuildIngressConfiguration(ingress []*trafficpolicy.InboundTrafficPolicy, trustDomain string) *xds_route.RouteConfiguration {
93197
if len(ingress) == 0 {
@@ -176,7 +280,7 @@ func buildInboundRoutes(rules []*trafficpolicy.Rule, trustDomain string) []*xds_
176280

177281
// Create an RBAC policy derived from 'trafficpolicy.Rule'
178282
// Each route is associated with an RBAC policy
179-
rbacPolicyForRoute, err := buildInboundRBACFilterForRule(rule, trustDomain)
283+
rbacConfig, err := buildInboundRBACFilterForRule(rule, trustDomain)
180284
if err != nil {
181285
log.Error().Err(err).Str(errcode.Kind, errcode.GetErrCodeWithMetric(errcode.ErrBuildingRBACPolicyForRoute)).
182286
Msgf("Error building RBAC policy for rule [%v], skipping route addition", rule)
@@ -186,13 +290,35 @@ func buildInboundRoutes(rules []*trafficpolicy.Rule, trustDomain string) []*xds_
186290
// Each HTTP method corresponds to a separate route
187291
for _, method := range allowedMethods {
188292
route := buildRoute(rule.Route, method)
189-
route.TypedPerFilterConfig = rbacPolicyForRoute
293+
applyInboundRouteConfig(route, rbacConfig, rule.Route.RateLimit)
190294
routes = append(routes, route)
191295
}
192296
}
193297
return routes
194298
}
195299

300+
func applyInboundRouteConfig(route *xds_route.Route, rbacConfig *any.Any, rateLimit *policyv1alpha1.HTTPPerRouteRateLimitSpec) {
301+
if route == nil {
302+
return
303+
}
304+
305+
perFilterConfig := make(map[string]*any.Any)
306+
307+
// Apply rate limiting config
308+
perFilterConfig[envoy.HTTPRBACFilterName] = rbacConfig
309+
310+
// Apply rate limiting config
311+
if rateLimit != nil && rateLimit.Local != nil {
312+
if filter, err := getLocalRateLimitFilterConfig(rateLimit.Local); err != nil {
313+
log.Error().Err(err).Msgf("Error applying local rate limiting config for route path %s, ignoring it", route.GetMatch().GetPath())
314+
} else {
315+
perFilterConfig[envoy.HTTPLocalRateLimitFilterName] = filter
316+
}
317+
}
318+
319+
route.TypedPerFilterConfig = perFilterConfig
320+
}
321+
196322
func buildOutboundRoutes(outRoutes []*trafficpolicy.RouteWeightedClusters) []*xds_route.Route {
197323
var routes []*xds_route.Route
198324
for _, outRoute := range outRoutes {
@@ -289,7 +415,7 @@ func buildWeightedCluster(weightedClusters mapset.Set) *xds_route.WeightedCluste
289415

290416
// TODO: Add validation webhook for retry policy
291417
// Remove checks when validation webhook is implemented
292-
func buildRetryPolicy(retry *v1alpha1.RetryPolicySpec) *xds_route.RetryPolicy {
418+
func buildRetryPolicy(retry *policyv1alpha1.RetryPolicySpec) *xds_route.RetryPolicy {
293419
if retry == nil {
294420
return nil
295421
}

0 commit comments

Comments
 (0)