Skip to content

Commit

Permalink
validation for bootstrap within EnvoyProxy res (#1109)
Browse files Browse the repository at this point in the history
* validation for bootstrap within EnvoyProxy res

Relates to #31

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

* use embed

Signed-off-by: Arko Dasgupta <arko@tetrate.io>
  • Loading branch information
arkodg authored Mar 9, 2023
1 parent c903255 commit f76ebe0
Show file tree
Hide file tree
Showing 7 changed files with 558 additions and 8 deletions.
84 changes: 78 additions & 6 deletions api/config/v1alpha1/validation/envoyproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ package validation
import (
"errors"
"fmt"
"reflect"

bootstrapv3 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
"google.golang.org/protobuf/encoding/protojson"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/yaml"

egcfgv1a1 "github.com/envoyproxy/gateway/api/config/v1alpha1"
"github.com/envoyproxy/gateway/internal/xds/bootstrap"
_ "github.com/envoyproxy/gateway/internal/xds/extensions" // register the generated types to support protojson unmarshalling
)

// ValidateEnvoyProxy validates the provided EnvoyProxy.
Expand All @@ -31,14 +38,79 @@ func ValidateEnvoyProxy(ep *egcfgv1a1.EnvoyProxy) error {
func validateEnvoyProxySpec(spec *egcfgv1a1.EnvoyProxySpec) error {
var errs []error

switch {
case spec == nil:
if spec == nil {
errs = append(errs, errors.New("spec is nil"))
case spec.Provider == nil:
return utilerrors.NewAggregate(errs)
case spec.Provider.Type != egcfgv1a1.ProviderTypeKubernetes:
}
if spec != nil && spec.Provider != nil && spec.Provider.Type != egcfgv1a1.ProviderTypeKubernetes {
errs = append(errs, fmt.Errorf("unsupported provider type %v", spec.Provider.Type))
}

if spec.Bootstrap != nil {
if err := validateBootstrap(spec.Bootstrap); err != nil {
errs = append(errs, err)
}
}
return utilerrors.NewAggregate(errs)
}

func validateBootstrap(bstrap *string) error {
userBootstrap := &bootstrapv3.Bootstrap{}
jsonData, err := yaml.YAMLToJSON([]byte(*bstrap))
if err != nil {
return fmt.Errorf("unable to convert user bootstrap to json: %w", err)
}

if err := protojson.Unmarshal(jsonData, userBootstrap); err != nil {
return fmt.Errorf("unable to unmarshal user bootstrap: %w", err)
}

// Call Validate method
if err := userBootstrap.Validate(); err != nil {
return fmt.Errorf("validation failed for user bootstrap: %w", err)
}
defaultBootstrap := &bootstrapv3.Bootstrap{}
defaultBootstrapStr, err := bootstrap.GetRenderedBootstrapConfig()
if err != nil {
return err
}

jsonData, err = yaml.YAMLToJSON([]byte(defaultBootstrapStr))
if err != nil {
return fmt.Errorf("unable to convert default bootstrap to json: %w", err)
}

if err := protojson.Unmarshal(jsonData, defaultBootstrap); err != nil {
return fmt.Errorf("unable to unmarshal default bootstrap: %w", err)
}

// Ensure dynamic resources config is same
// nolint // Circumvents this error "Error: copylocks: call of reflect.DeepEqual copies lock value:"
if userBootstrap.DynamicResources == nil || !reflect.DeepEqual(*userBootstrap.DynamicResources, *defaultBootstrap.DynamicResources) {
return fmt.Errorf("dynamic_resources cannot be modified")
}
// Ensure layered runtime resources config is same
// nolint // Circumvents this error "Error: copylocks: call of reflect.DeepEqual copies lock value:"
if userBootstrap.LayeredRuntime == nil || !reflect.DeepEqual(*userBootstrap.LayeredRuntime, *defaultBootstrap.LayeredRuntime) {
return fmt.Errorf("layered_runtime cannot be modified")
}
// Ensure that the xds_cluster config is same
var userXdsCluster, defaultXdsCluster *clusterv3.Cluster
for _, cluster := range userBootstrap.StaticResources.Clusters {
if cluster.Name == "xds_cluster" {
userXdsCluster = cluster
break
}
}
for _, cluster := range defaultBootstrap.StaticResources.Clusters {
if cluster.Name == "xds_cluster" {
defaultXdsCluster = cluster
break
}
}

// nolint // Circumvents this error "Error: copylocks: call of reflect.DeepEqual copies lock value:"
if userXdsCluster == nil || !reflect.DeepEqual(*userXdsCluster.LoadAssignment, *defaultXdsCluster.LoadAssignment) {
return fmt.Errorf("xds_cluster's loadAssigntment cannot be modified")
}

return nil
}
70 changes: 68 additions & 2 deletions api/config/v1alpha1/validation/envoyproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package validation
package validation_test

import (
// Register embed
_ "embed"
"testing"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

egcfgv1a1 "github.com/envoyproxy/gateway/api/config/v1alpha1"
"github.com/envoyproxy/gateway/api/config/v1alpha1/validation"
)

var (
//go:embed testdata/valid-user-bootstrap.yaml
validUserBootstrap string
//go:embed testdata/missing-admin-address-user-bootstrap.yaml
missingAdminAddressUserBootstrap string
//go:embed testdata/different-dynamic-resources-user-bootstrap.yaml
differentDynamicResourcesUserBootstrap string
//go:embed testdata/different-xds-cluster-address-bootstrap.yaml
differentXdsClusterAddressBootstrap string
)

func TestValidateEnvoyProxy(t *testing.T) {
Expand Down Expand Up @@ -53,12 +67,64 @@ func TestValidateEnvoyProxy(t *testing.T) {
},
expected: false,
},
{
name: "valid user bootstrap",
obj: &egcfgv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egcfgv1a1.EnvoyProxySpec{
Bootstrap: &validUserBootstrap,
},
},
expected: true,
},
{
name: "user bootstrap with missing admin address",
obj: &egcfgv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egcfgv1a1.EnvoyProxySpec{
Bootstrap: &missingAdminAddressUserBootstrap,
},
},
expected: false,
},
{
name: "user bootstrap with different dynamic resources",
obj: &egcfgv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egcfgv1a1.EnvoyProxySpec{
Bootstrap: &differentDynamicResourcesUserBootstrap,
},
},
expected: false,
},
{
name: "user bootstrap with different xds_cluster endpoint",
obj: &egcfgv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "test",
},
Spec: egcfgv1a1.EnvoyProxySpec{
Bootstrap: &differentXdsClusterAddressBootstrap,
},
},
expected: false,
},
}

for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
err := ValidateEnvoyProxy(tc.obj)
err := validation.ValidateEnvoyProxy(tc.obj)
if tc.expected {
require.NoError(t, err)
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
admin:
accessLog:
- name: envoy.access_loggers.file
typedConfig:
'@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/null
address:
socketAddress:
address: 127.0.0.1
portValue: 19000
dynamicResources:
adsConfig:
apiType: GRPC
grpcServices:
- envoyGrpc:
clusterName: xds_cluster
setNodeOnFirstMessageOnly: true
transportApiVersion: V3
ldsConfig:
ads: {}
cdsConfig:
ads: {}
layeredRuntime:
layers:
- name: runtime-0
rtdsLayer:
name: runtime-0
rtdsConfig:
ads: {}
staticResources:
clusters:
- connectTimeout: 10s
loadAssignment:
clusterName: xds_cluster
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: envoy-gateway
portValue: 18000
name: xds_cluster
transportSocket:
name: envoy.transport_sockets.tls
typedConfig:
'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
commonTlsContext:
tlsCertificateSdsSecretConfigs:
- name: xds_certificate
sdsConfig:
pathConfigSource:
path: /sds/xds-certificate.json
resourceApiVersion: V3
tlsParams:
tlsMaximumProtocolVersion: TLSv1_3
validationContextSdsSecretConfig:
name: xds_trusted_ca
sdsConfig:
pathConfigSource:
path: /sds/xds-trusted-ca.json
resourceApiVersion: V3
type: STRICT_DNS
typedExtensionProtocolOptions:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
'@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicitHttpConfig:
http2ProtocolOptions: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
admin:
accessLog:
- name: envoy.access_loggers.file
typedConfig:
'@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/null
address:
socketAddress:
address: 127.0.0.1
portValue: 19000
dynamicResources:
adsConfig:
apiType: DELTA_GRPC
grpcServices:
- envoyGrpc:
clusterName: xds_cluster
setNodeOnFirstMessageOnly: true
transportApiVersion: V3
ldsConfig:
ads: {}
cdsConfig:
ads: {}
layeredRuntime:
layers:
- name: runtime-0
rtdsLayer:
name: runtime-0
rtdsConfig:
ads: {}
staticResources:
clusters:
- connectTimeout: 10s
loadAssignment:
clusterName: xds_cluster
endpoints:
- lbEndpoints:
- endpoint:
address:
socketAddress:
address: fake-envoy-gateway
portValue: 18000
name: xds_cluster
transportSocket:
name: envoy.transport_sockets.tls
typedConfig:
'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
commonTlsContext:
tlsCertificateSdsSecretConfigs:
- name: xds_certificate
sdsConfig:
pathConfigSource:
path: /sds/xds-certificate.json
resourceApiVersion: V3
tlsParams:
tlsMaximumProtocolVersion: TLSv1_3
validationContextSdsSecretConfig:
name: xds_trusted_ca
sdsConfig:
pathConfigSource:
path: /sds/xds-trusted-ca.json
resourceApiVersion: V3
type: STRICT_DNS
typedExtensionProtocolOptions:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
'@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicitHttpConfig:
http2ProtocolOptions: {}
Loading

0 comments on commit f76ebe0

Please sign in to comment.