mirror of
https://github.com/status-im/consul.git
synced 2025-02-28 05:10:40 +00:00
TLDR with many modules the versions included in each diverged quite a bit. Attempting to use Go Workspaces produces a bunch of errors. This commit: 1. Fixes envoy-library-references.sh to work again 2. Ensures we are pulling in go-control-plane@v0.11.0 everywhere (previously it was at that version in some modules and others were much older) 3. Remove one usage of golang/protobuf that caused us to have a direct dependency on it. 4. Remove deprecated usage of the Endpoint field in the grpc resolver.Target struct. The current version of grpc (v1.55.0) has removed that field and recommended replacement with URL.Opaque and calls to the Endpoint() func when needing to consume the previous field. 4. `go work init <all the paths to go.mod files>` && `go work sync`. This syncrhonized versions of dependencies from the main workspace/root module to all submodules 5. Updated .gitignore to ignore the go.work and go.work.sum files. This seems to be standard practice at the moment. 6. Update doc comments in protoc-gen-consul-rate-limit to be go fmt compatible 7. Upgraded makefile infra to perform linting, testing and go mod tidy on all modules in a flexible manner. 8. Updated linter rules to prevent usage of golang/protobuf 9. Updated a leader peering test to account for an extra colon in a grpc error message.
197 lines
6.2 KiB
Go
197 lines
6.2 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package localratelimit
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
|
envoy_ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
|
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
|
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
|
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/mitchellh/mapstructure"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
|
|
)
|
|
|
|
type ratelimit struct {
|
|
extensioncommon.BasicExtensionAdapter
|
|
|
|
ProxyType string
|
|
|
|
// Token bucket of the rate limit
|
|
MaxTokens *int
|
|
TokensPerFill *int
|
|
FillInterval *int
|
|
|
|
// Percent of requests to be rate limited
|
|
FilterEnabled *uint32
|
|
FilterEnforced *uint32
|
|
}
|
|
|
|
var _ extensioncommon.BasicExtension = (*ratelimit)(nil)
|
|
|
|
// Constructor follows a specific function signature required for the extension registration.
|
|
func Constructor(ext api.EnvoyExtension) (extensioncommon.EnvoyExtender, error) {
|
|
var r ratelimit
|
|
if name := ext.Name; name != api.BuiltinLocalRatelimitExtension {
|
|
return nil, fmt.Errorf("expected extension name 'ratelimit' but got %q", name)
|
|
}
|
|
|
|
if err := r.fromArguments(ext.Arguments); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &extensioncommon.BasicEnvoyExtender{
|
|
Extension: &r,
|
|
}, nil
|
|
}
|
|
|
|
func (r *ratelimit) fromArguments(args map[string]interface{}) error {
|
|
if err := mapstructure.Decode(args, r); err != nil {
|
|
return fmt.Errorf("error decoding extension arguments: %v", err)
|
|
}
|
|
return r.validate()
|
|
}
|
|
|
|
func (r *ratelimit) validate() error {
|
|
var resultErr error
|
|
|
|
// NOTE: Envoy requires FillInterval value must be greater than 0.
|
|
// If unset, it is considered as 0.
|
|
if r.FillInterval == nil {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("FillInterval(in second) is missing"))
|
|
} else if *r.FillInterval <= 0 {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("FillInterval(in second) must be greater than 0, got %d", *r.FillInterval))
|
|
}
|
|
|
|
// NOTE: Envoy requires MaxToken value must be greater than 0.
|
|
// If unset, it is considered as 0.
|
|
if r.MaxTokens == nil {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("MaxTokens is missing"))
|
|
} else if *r.MaxTokens <= 0 {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("MaxTokens must be greater than 0, got %d", r.MaxTokens))
|
|
}
|
|
|
|
// TokensPerFill is allowed to unset. In this case, envoy
|
|
// uses its default value, which is 1.
|
|
if r.TokensPerFill != nil && *r.TokensPerFill <= 0 {
|
|
resultErr = multierror.Append(resultErr, fmt.Errorf("TokensPerFill must be greater than 0, got %d", *r.TokensPerFill))
|
|
}
|
|
|
|
if err := validateProxyType(r.ProxyType); err != nil {
|
|
resultErr = multierror.Append(resultErr, err)
|
|
}
|
|
|
|
return resultErr
|
|
}
|
|
|
|
// CanApply determines if the extension can apply to the given extension configuration.
|
|
func (p *ratelimit) CanApply(config *extensioncommon.RuntimeConfig) bool {
|
|
return string(config.Kind) == p.ProxyType
|
|
}
|
|
|
|
// PatchFilter inserts a http local rate_limit filter at the head of
|
|
// envoy.filters.network.http_connection_manager filters
|
|
func (r ratelimit) PatchFilter(p extensioncommon.FilterPayload) (*envoy_listener_v3.Filter, bool, error) {
|
|
filter := p.Message
|
|
// rate limit is only applied to the inbound listener of the service itself
|
|
// since the limit is aggregated from all downstream connections.
|
|
if !p.IsInbound() {
|
|
return filter, false, nil
|
|
}
|
|
|
|
if filter.Name != "envoy.filters.network.http_connection_manager" {
|
|
return filter, false, nil
|
|
}
|
|
if typedConfig := filter.GetTypedConfig(); typedConfig == nil {
|
|
return filter, false, errors.New("error getting typed config for http filter")
|
|
}
|
|
|
|
config := envoy_resource_v3.GetHTTPConnectionManager(filter)
|
|
if config == nil {
|
|
return filter, false, errors.New("error unmarshalling filter")
|
|
}
|
|
|
|
tokenBucket := envoy_type_v3.TokenBucket{}
|
|
|
|
if r.TokensPerFill != nil {
|
|
tokenBucket.TokensPerFill = &wrapperspb.UInt32Value{
|
|
Value: uint32(*r.TokensPerFill),
|
|
}
|
|
}
|
|
if r.MaxTokens != nil {
|
|
tokenBucket.MaxTokens = uint32(*r.MaxTokens)
|
|
}
|
|
|
|
if r.FillInterval != nil {
|
|
tokenBucket.FillInterval = durationpb.New(time.Duration(*r.FillInterval) * time.Second)
|
|
}
|
|
|
|
var FilterEnabledDefault *envoy_core_v3.RuntimeFractionalPercent
|
|
if r.FilterEnabled != nil {
|
|
FilterEnabledDefault = &envoy_core_v3.RuntimeFractionalPercent{
|
|
DefaultValue: &envoy_type_v3.FractionalPercent{
|
|
Numerator: *r.FilterEnabled,
|
|
Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
|
|
},
|
|
}
|
|
}
|
|
|
|
var FilterEnforcedDefault *envoy_core_v3.RuntimeFractionalPercent
|
|
if r.FilterEnforced != nil {
|
|
FilterEnforcedDefault = &envoy_core_v3.RuntimeFractionalPercent{
|
|
DefaultValue: &envoy_type_v3.FractionalPercent{
|
|
Numerator: *r.FilterEnforced,
|
|
Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
|
|
},
|
|
}
|
|
}
|
|
|
|
ratelimitHttpFilter, err := extensioncommon.MakeEnvoyHTTPFilter(
|
|
"envoy.filters.http.local_ratelimit",
|
|
&envoy_ratelimit.LocalRateLimit{
|
|
TokenBucket: &tokenBucket,
|
|
StatPrefix: "local_ratelimit",
|
|
FilterEnabled: FilterEnabledDefault,
|
|
FilterEnforced: FilterEnforcedDefault,
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return filter, false, err
|
|
}
|
|
|
|
changedFilters := make([]*envoy_http_v3.HttpFilter, 0, len(config.HttpFilters)+1)
|
|
|
|
// The ratelimitHttpFilter is inserted as the first element of the http
|
|
// filter chain.
|
|
changedFilters = append(changedFilters, ratelimitHttpFilter)
|
|
changedFilters = append(changedFilters, config.HttpFilters...)
|
|
config.HttpFilters = changedFilters
|
|
|
|
newFilter, err := extensioncommon.MakeFilter("envoy.filters.network.http_connection_manager", config)
|
|
if err != nil {
|
|
return filter, false, errors.New("error making new filter")
|
|
}
|
|
|
|
return newFilter, true, nil
|
|
}
|
|
|
|
func validateProxyType(t string) error {
|
|
if t != "connect-proxy" {
|
|
return fmt.Errorf("unexpected ProxyType %q", t)
|
|
}
|
|
|
|
return nil
|
|
}
|