Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validation for bootstrap within EnvoyProxy res #1109

Merged
merged 5 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
82 changes: 75 additions & 7 deletions api/config/v1alpha1/validation/envoyproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ package validation
import (
"errors"
"fmt"

arkodg marked this conversation as resolved.
Show resolved Hide resolved
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"
"reflect"
"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 +37,76 @@ 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
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
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
}
}

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