Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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