Skip to content

Commit

Permalink
Merge 2735bbe into backport/add-extension-version-checks/abnormally-b…
Browse files Browse the repository at this point in the history
…right-redbird
  • Loading branch information
hc-github-team-consul-core authored Jun 8, 2023
2 parents a89f806 + 2735bbe commit 8b2b73d
Show file tree
Hide file tree
Showing 22 changed files with 1,344 additions and 114 deletions.
21 changes: 20 additions & 1 deletion agent/envoyextensions/registered_extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package envoyextensions
import (
"fmt"

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-version"

awslambda "github.com/hashicorp/consul/agent/envoyextensions/builtin/aws-lambda"
extauthz "github.com/hashicorp/consul/agent/envoyextensions/builtin/ext-authz"
"github.com/hashicorp/consul/agent/envoyextensions/builtin/http/localratelimit"
Expand All @@ -14,7 +17,6 @@ import (
"github.com/hashicorp/consul/agent/envoyextensions/builtin/wasm"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
"github.com/hashicorp/go-multierror"
)

type extensionConstructor func(api.EnvoyExtension) (extensioncommon.EnvoyExtender, error)
Expand Down Expand Up @@ -50,6 +52,23 @@ func ValidateExtensions(extensions []api.EnvoyExtension) error {
output = multierror.Append(output, fmt.Errorf("invalid EnvoyExtensions[%d]: Name is required", i))
continue
}

if v := ext.EnvoyVersion; v != "" {
_, err := version.NewConstraint(v)
if err != nil {
output = multierror.Append(output, fmt.Errorf("invalid EnvoyExtensions[%d].EnvoyVersion: %w", i, err))
continue
}
}

if v := ext.ConsulVersion; v != "" {
_, err := version.NewConstraint(v)
if err != nil {
output = multierror.Append(output, fmt.Errorf("invalid EnvoyExtensions[%d].ConsulVersion: %w", i, err))
continue
}
}

_, err := ConstructExtension(ext)
if err != nil {
output = multierror.Append(output, fmt.Errorf("invalid EnvoyExtensions[%d][%s]: %w", i, ext.Name, err))
Expand Down
24 changes: 24 additions & 0 deletions agent/envoyextensions/registered_extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,30 @@ func TestValidateExtensions(t *testing.T) {
"missing Script value",
},
},
"invalid consul version constraint": {
input: []api.EnvoyExtension{{
Name: "builtin/aws/lambda",
Arguments: map[string]interface{}{
"ARN": "arn:aws:lambda:us-east-1:111111111111:function:lambda-1234",
},
ConsulVersion: "bad",
}},
expectErrs: []string{
"invalid EnvoyExtensions[0].ConsulVersion: Malformed constraint: bad",
},
},
"invalid envoy version constraint": {
input: []api.EnvoyExtension{{
Name: "builtin/aws/lambda",
Arguments: map[string]interface{}{
"ARN": "arn:aws:lambda:us-east-1:111111111111:function:lambda-1234",
},
EnvoyVersion: "bad",
}},
expectErrs: []string{
"invalid EnvoyExtensions[0].EnvoyVersion: Malformed constraint: bad",
},
},
}

for name, tc := range tests {
Expand Down
16 changes: 10 additions & 6 deletions agent/structs/envoy_extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (

// EnvoyExtension has configuration for an extension that patches Envoy resources.
type EnvoyExtension struct {
Name string
Required bool
Arguments map[string]interface{} `bexpr:"-"`
Name string
Required bool
Arguments map[string]interface{} `bexpr:"-"`
ConsulVersion string
EnvoyVersion string
}

type EnvoyExtensions []EnvoyExtension
Expand All @@ -20,9 +22,11 @@ func (es EnvoyExtensions) ToAPI() []api.EnvoyExtension {
extensions := make([]api.EnvoyExtension, len(es))
for i, e := range es {
extensions[i] = api.EnvoyExtension{
Name: e.Name,
Required: e.Required,
Arguments: e.Arguments,
Name: e.Name,
Required: e.Required,
Arguments: e.Arguments,
EnvoyVersion: e.EnvoyVersion,
ConsulVersion: e.ConsulVersion,
}
}
return extensions
Expand Down
10 changes: 10 additions & 0 deletions agent/structs/structs_filtering_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ var expectedFieldConfigEnvoyExtensions bexpr.FieldConfigurations = bexpr.FieldCo
CoerceFn: bexpr.CoerceBool,
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual},
},
"ConsulVersion": &bexpr.FieldConfiguration{
StructFieldName: "ConsulVersion",
CoerceFn: bexpr.CoerceString,
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual, bexpr.MatchIn, bexpr.MatchNotIn, bexpr.MatchMatches, bexpr.MatchNotMatches},
},
"EnvoyVersion": &bexpr.FieldConfiguration{
StructFieldName: "EnvoyVersion",
CoerceFn: bexpr.CoerceString,
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual, bexpr.MatchIn, bexpr.MatchNotIn, bexpr.MatchMatches, bexpr.MatchNotMatches},
},
}
var expectedFieldConfigUpstreams bexpr.FieldConfigurations = bexpr.FieldConfigurations{
"DestinationType": &bexpr.FieldConfiguration{
Expand Down
158 changes: 107 additions & 51 deletions agent/xds/delta.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
envoy_config_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_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"github.com/hashicorp/go-hclog"
goversion "github.com/hashicorp/go-version"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand All @@ -29,6 +31,7 @@ import (
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/xds/extensionruntime"
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
"github.com/hashicorp/consul/envoyextensions/xdscommon"
"github.com/hashicorp/consul/logging"
"github.com/hashicorp/consul/version"
Expand Down Expand Up @@ -255,7 +258,7 @@ func (s *Server) processDelta(stream ADSDeltaStream, reqCh <-chan *envoy_discove
s.ResourceMapMutateFn(newResourceMap)
}

if err = s.applyEnvoyExtensions(newResourceMap, cfgSnap); err != nil {
if err = s.applyEnvoyExtensions(newResourceMap, cfgSnap, node); err != nil {
// err is already the result of calling status.Errorf
return err
}
Expand Down Expand Up @@ -400,70 +403,123 @@ func (s *Server) processDelta(stream ADSDeltaStream, reqCh <-chan *envoy_discove
}
}

func (s *Server) applyEnvoyExtensions(resources *xdscommon.IndexedResources, cfgSnap *proxycfg.ConfigSnapshot) error {
func (s *Server) applyEnvoyExtensions(resources *xdscommon.IndexedResources, cfgSnap *proxycfg.ConfigSnapshot, node *envoy_config_core_v3.Node) error {
var err error
envoyVersion := xdscommon.DetermineEnvoyVersionFromNode(node)
consulVersion, err := goversion.NewVersion(version.Version)

if err != nil {
return status.Errorf(codes.InvalidArgument, "failed to parse Consul version")
}

serviceConfigs := extensionruntime.GetRuntimeConfigurations(cfgSnap)
for _, cfgs := range serviceConfigs {
for _, cfg := range cfgs {
logFn := s.Logger.Warn
if cfg.EnvoyExtension.Required {
logFn = s.Logger.Error
}
errorParams := []interface{}{
"extension", cfg.EnvoyExtension.Name,
"service", cfg.ServiceName.Name,
"namespace", cfg.ServiceName.Namespace,
"partition", cfg.ServiceName.Partition,
}

getMetricLabels := func(err error) []metrics.Label {
return []metrics.Label{
{Name: "extension", Value: cfg.EnvoyExtension.Name},
{Name: "version", Value: "builtin/" + version.Version},
{Name: "service", Value: cfgSnap.Service},
{Name: "partition", Value: cfgSnap.ProxyID.PartitionOrDefault()},
{Name: "namespace", Value: cfgSnap.ProxyID.NamespaceOrDefault()},
{Name: "error", Value: strconv.FormatBool(err != nil)},
}
}
err = applyEnvoyExtension(s.Logger, cfgSnap, resources, cfg, envoyVersion, consulVersion)

now := time.Now()
extender, err := envoyextensions.ConstructExtension(cfg.EnvoyExtension)
metrics.MeasureSinceWithLabels([]string{"envoy_extension", "validate_arguments"}, now, getMetricLabels(err))
if err != nil {
logFn("failed to construct extension", errorParams...)
return err
}
}
}

if cfg.EnvoyExtension.Required {
return status.Errorf(codes.Unavailable, "failed to construct extension %q for service %q", cfg.EnvoyExtension.Name, cfg.ServiceName.Name)
}
return nil
}

continue
}
func applyEnvoyExtension(logger hclog.Logger, cfgSnap *proxycfg.ConfigSnapshot, resources *xdscommon.IndexedResources, runtimeConfig extensioncommon.RuntimeConfig, envoyVersion, consulVersion *goversion.Version) error {
logFn := logger.Warn
if runtimeConfig.EnvoyExtension.Required {
logFn = logger.Error
}

now = time.Now()
err = extender.Validate(&cfg)
metrics.MeasureSinceWithLabels([]string{"envoy_extension", "validate"}, now, getMetricLabels(err))
if err != nil {
errorParams = append(errorParams, "error", err)
logFn("failed to validate extension arguments", errorParams...)
if cfg.EnvoyExtension.Required {
return status.Errorf(codes.Unavailable, "failed to validate arguments for extension %q for service %q", cfg.EnvoyExtension.Name, cfg.ServiceName.Name)
}
svc := runtimeConfig.ServiceName

continue
}
errorParams := []interface{}{
"extension", runtimeConfig.EnvoyExtension.Name,
"service", svc.Name,
"namespace", svc.Namespace,
"partition", svc.Partition,
}

now = time.Now()
resources, err = extender.Extend(resources, &cfg)
metrics.MeasureSinceWithLabels([]string{"envoy_extension", "extend"}, now, getMetricLabels(err))
if err == nil {
continue
getMetricLabels := func(err error) []metrics.Label {
return []metrics.Label{
{Name: "extension", Value: runtimeConfig.EnvoyExtension.Name},
{Name: "version", Value: "builtin/" + version.Version},
{Name: "service", Value: cfgSnap.Service},
{Name: "partition", Value: cfgSnap.ProxyID.PartitionOrDefault()},
{Name: "namespace", Value: cfgSnap.ProxyID.NamespaceOrDefault()},
{Name: "error", Value: strconv.FormatBool(err != nil)},
}
}

ext := runtimeConfig.EnvoyExtension

if v := ext.EnvoyVersion; v != "" {
c, err := goversion.NewConstraint(v)
if err != nil {
logFn("failed to parse Envoy extension version constraint", errorParams...)

if ext.Required {
return status.Errorf(codes.InvalidArgument, "failed to parse Envoy version constraint for extension %q for service %q", ext.Name, svc.Name)
}
return nil
}

if !c.Check(envoyVersion) {
logger.Info("skipping envoy extension due to Envoy version constraint violation", errorParams...)
return nil
}
}

if v := ext.ConsulVersion; v != "" {
c, err := goversion.NewConstraint(v)
if err != nil {
logFn("failed to parse Consul extension version constraint", errorParams...)

logFn("failed to apply envoy extension", errorParams...)
if cfg.EnvoyExtension.Required {
return status.Errorf(codes.Unavailable, "failed to patch xDS resources in the %q extension: %v", cfg.EnvoyExtension.Name, err)
if ext.Required {
return status.Errorf(codes.InvalidArgument, "failed to parse Consul version constraint for extension %q for service %q", ext.Name, svc.Name)
}
return nil
}

if !c.Check(consulVersion) {
logger.Info("skipping envoy extension due to Consul version constraint violation", errorParams...)
return nil
}
}

now := time.Now()
extender, err := envoyextensions.ConstructExtension(ext)
metrics.MeasureSinceWithLabels([]string{"envoy_extension", "validate_arguments"}, now, getMetricLabels(err))
if err != nil {
logFn("failed to construct extension", errorParams...)

if ext.Required {
return status.Errorf(codes.InvalidArgument, "failed to construct extension %q for service %q", ext.Name, svc.Name)
}

return nil
}

now = time.Now()
err = extender.Validate(&runtimeConfig)
metrics.MeasureSinceWithLabels([]string{"envoy_extension", "validate"}, now, getMetricLabels(err))
if err != nil {
errorParams = append(errorParams, "error", err)
logFn("failed to validate extension arguments", errorParams...)
if ext.Required {
return status.Errorf(codes.InvalidArgument, "failed to validate arguments for extension %q for service %q", ext.Name, svc.Name)
}

return nil
}

now = time.Now()
_, err = extender.Extend(resources, &runtimeConfig)
metrics.MeasureSinceWithLabels([]string{"envoy_extension", "extend"}, now, getMetricLabels(err))
logFn("failed to apply envoy extension", errorParams...)
if err != nil && ext.Required {
return status.Errorf(codes.InvalidArgument, "failed to patch xDS resources in the %q extension: %v", ext.Name, err)
}

return nil
Expand Down
Loading

0 comments on commit 8b2b73d

Please sign in to comment.