From 02e936741d80dc91789e11a61d41ed430eb36d12 Mon Sep 17 00:00:00 2001 From: Kobi Levi <56400138+levikobi@users.noreply.github.com> Date: Thu, 16 May 2024 00:24:24 +0300 Subject: [PATCH 1/6] feat: add supported features to gateway class (#2491) * feat: add supported features to gateway class Signed-off-by: Kobi Levi * remove skipped test features from supported features list Signed-off-by: Kobi Levi --------- Signed-off-by: Kobi Levi --- .../translate/out/default-resources.all.yaml | 21 +++++++ .../out/echo-gateway-api.cluster.yaml | 21 +++++++ .../translate/out/echo-gateway-api.route.json | 22 +++++++ .../translate/out/invalid-envoyproxy.all.yaml | 21 +++++++ .../out/rejected-http-route.route.yaml | 21 +++++++ .../translate/out/valid-envoyproxy.all.yaml | 21 +++++++ internal/cmd/egctl/translate_test.go | 8 +++ internal/gatewayapi/conformance/features.go | 37 ++++++++++++ internal/gatewayapi/status/gatewayclass.go | 26 ++++++++ .../gatewayapi/status/gatewayclass_test.go | 59 +++++++++++++++++++ .../provider/kubernetes/controller_test.go | 5 ++ test/conformance/conformance_test.go | 13 ++-- .../experimental_conformance_test.go | 13 ++-- 13 files changed, 272 insertions(+), 16 deletions(-) create mode 100644 internal/gatewayapi/conformance/features.go diff --git a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml index cacca5bb7c4..de7dfd0e9c5 100644 --- a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml @@ -158,6 +158,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml index 3d88f20f51d..6afabda49f4 100644 --- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml +++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.cluster.yaml @@ -12,6 +12,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json index 41dfd6683e7..472c02fcfdb 100644 --- a/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json +++ b/internal/cmd/egctl/testdata/translate/out/echo-gateway-api.route.json @@ -17,6 +17,28 @@ "reason": "Accepted", "message": "Valid GatewayClass" } + ], + "supportedFeatures": [ + "GRPCRoute", + "GatewayPort8080", + "HTTPRouteBackendProtocolH2C", + "HTTPRouteBackendProtocolWebSocket", + "HTTPRouteBackendTimeout", + "HTTPRouteDestinationPortMatching", + "HTTPRouteHostRewrite", + "HTTPRouteMethodMatching", + "HTTPRouteParentRefPort", + "HTTPRoutePathRedirect", + "HTTPRoutePathRewrite", + "HTTPRoutePortRedirect", + "HTTPRouteQueryParamMatching", + "HTTPRouteRequestMirror", + "HTTPRouteRequestMultipleMirrors", + "HTTPRouteRequestTimeout", + "HTTPRouteResponseHeaderModification", + "HTTPRouteSchemeRedirect", + "ReferenceGrant", + "TLSRoute" ] } }, diff --git a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml index c7ad9cde133..a15d2481e9a 100644 --- a/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/invalid-envoyproxy.all.yaml @@ -38,6 +38,27 @@ gatewayClass: reason: InvalidParameters status: "False" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml index c578d14aef5..f45a99fa577 100644 --- a/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml +++ b/internal/cmd/egctl/testdata/translate/out/rejected-http-route.route.yaml @@ -12,6 +12,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml index ef42d68c93e..feef2037c5b 100644 --- a/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml +++ b/internal/cmd/egctl/testdata/translate/out/valid-envoyproxy.all.yaml @@ -31,6 +31,27 @@ gatewayClass: reason: Accepted status: "True" type: Accepted + supportedFeatures: + - GRPCRoute + - GatewayPort8080 + - HTTPRouteBackendProtocolH2C + - HTTPRouteBackendProtocolWebSocket + - HTTPRouteBackendTimeout + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRouteParentRefPort + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestTimeout + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - ReferenceGrant + - TLSRoute gateways: - metadata: creationTimestamp: null diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go index 96707159a99..c7d51b0e83a 100644 --- a/internal/cmd/egctl/translate_test.go +++ b/internal/cmd/egctl/translate_test.go @@ -22,6 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" ) @@ -349,6 +350,13 @@ func TestTranslate(t *testing.T) { } want := &TranslationResult{} mustUnmarshal(t, requireTestDataOutFile(t, fn), want) + + // Supported features are dynamic, instead of hard-coding them in the output files + // we define them here. + if want.GatewayClass != nil { + want.GatewayClass.Status.SupportedFeatures = status.GatewaySupportedFeatures + } + opts := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") require.Empty(t, cmp.Diff(want, got, opts)) }) diff --git a/internal/gatewayapi/conformance/features.go b/internal/gatewayapi/conformance/features.go new file mode 100644 index 00000000000..93b13751d7c --- /dev/null +++ b/internal/gatewayapi/conformance/features.go @@ -0,0 +1,37 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package conformance + +import ( + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/gateway-api/conformance/tests" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +// SkipTests is a list of tests that are skipped in the conformance suite. +var SkipTests = []suite.ConformanceTest{ + tests.GatewayStaticAddresses, + tests.GatewayHTTPListenerIsolation, // https://github.com/kubernetes-sigs/gateway-api/issues/3049 + tests.HTTPRouteBackendRequestHeaderModifier, // https://github.com/envoyproxy/gateway/issues/3338 +} + +func skipTestsShortNames(skipTests []suite.ConformanceTest) []string { + shortNames := make([]string, len(skipTests)) + for i, test := range skipTests { + shortNames[i] = test.ShortName + } + return shortNames +} + +// EnvoyGatewaySuite is the conformance suite configuration for the Gateway API. +var EnvoyGatewaySuite = suite.ConformanceOptions{ + SupportedFeatures: features.AllFeatures, + ExemptFeatures: sets.New[features.SupportedFeature](). + Insert(features.MeshCoreFeatures.UnsortedList()...). + Insert(features.MeshExtendedFeatures.UnsortedList()...), + SkipTests: skipTestsShortNames(SkipTests), +} diff --git a/internal/gatewayapi/status/gatewayclass.go b/internal/gatewayapi/status/gatewayclass.go index 35813c4bc40..9bcc76f3a0c 100644 --- a/internal/gatewayapi/status/gatewayclass.go +++ b/internal/gatewayapi/status/gatewayclass.go @@ -17,7 +17,11 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) const ( @@ -32,6 +36,7 @@ const ( // for the provided GatewayClass. func SetGatewayClassAccepted(gc *gwapiv1.GatewayClass, accepted bool, reason, msg string) *gwapiv1.GatewayClass { gc.Status.Conditions = MergeConditions(gc.Status.Conditions, computeGatewayClassAcceptedCondition(gc, accepted, reason, msg)) + gc.Status.SupportedFeatures = GatewaySupportedFeatures return gc } @@ -61,3 +66,24 @@ func computeGatewayClassAcceptedCondition(gatewayClass *gwapiv1.GatewayClass, } } } + +// GatewaySupportedFeatures is a list of supported Gateway-API features, +// based on the running conformance tests suite. +var GatewaySupportedFeatures = getSupportedFeatures(conformance.EnvoyGatewaySuite, conformance.SkipTests) + +func getSupportedFeatures(gatewaySuite suite.ConformanceOptions, skippedTests []suite.ConformanceTest) []gwapiv1.SupportedFeature { + supportedFeatures := gatewaySuite.SupportedFeatures.Clone() + supportedFeatures.Delete(gatewaySuite.ExemptFeatures.UnsortedList()...) + + for _, skippedTest := range skippedTests { + for _, feature := range skippedTest.Features { + supportedFeatures.Delete(feature) + } + } + + ret := sets.New[gwapiv1.SupportedFeature]() + for _, feature := range supportedFeatures.UnsortedList() { + ret.Insert(gwapiv1.SupportedFeature(feature)) + } + return sets.List(ret) +} diff --git a/internal/gatewayapi/status/gatewayclass_test.go b/internal/gatewayapi/status/gatewayclass_test.go index 7b845408ecc..0ccce426353 100644 --- a/internal/gatewayapi/status/gatewayclass_test.go +++ b/internal/gatewayapi/status/gatewayclass_test.go @@ -10,7 +10,11 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" ) func TestComputeGatewayClassAcceptedCondition(t *testing.T) { @@ -66,3 +70,58 @@ func TestComputeGatewayClassAcceptedCondition(t *testing.T) { assert.Equal(t, gc.Generation, got.ObservedGeneration) } } + +func TestGetSupportedFeatures(t *testing.T) { + testCases := []struct { + name string + gatewaySuite suite.ConformanceOptions + skippedTests []suite.ConformanceTest + expectedResult []gwapiv1.SupportedFeature + }{ + { + name: "No exempt features", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + ExemptFeatures: sets.New[features.SupportedFeature](), + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway", "HTTPRoute"}, + }, + { + name: "All features exempt", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + ExemptFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute"), + }, + expectedResult: []gwapiv1.SupportedFeature{}, + }, + { + name: "Some features exempt", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute", "GRPCRoute"), + ExemptFeatures: sets.New[features.SupportedFeature]("GRPCRoute"), + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway", "HTTPRoute"}, + }, + { + name: "Some features exempt with skipped tests", + gatewaySuite: suite.ConformanceOptions{ + SupportedFeatures: sets.New[features.SupportedFeature]("Gateway", "HTTPRoute", "GRPCRoute"), + ExemptFeatures: sets.New[features.SupportedFeature]("GRPCRoute"), + }, + skippedTests: []suite.ConformanceTest{ + { + Features: []features.SupportedFeature{"HTTPRoute"}, + }, + }, + expectedResult: []gwapiv1.SupportedFeature{"Gateway"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := getSupportedFeatures(tc.gatewaySuite, tc.skippedTests) + + assert.ElementsMatch(t, tc.expectedResult, result, "The result should match the expected output for the test case.") + }) + } +} diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index 8e3a5f9b56f..860ba09e4b1 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -20,6 +20,7 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway" "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/logging" ) @@ -189,6 +190,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionTrue, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, }, @@ -223,6 +225,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionTrue, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, }, @@ -275,6 +278,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionTrue, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, { @@ -297,6 +301,7 @@ func TestHasManagedClass(t *testing.T) { Status: metav1.ConditionFalse, }, }, + SupportedFeatures: status.GatewaySupportedFeatures, }, }, }, diff --git a/test/conformance/conformance_test.go b/test/conformance/conformance_test.go index 9faa95cf19b..ce7dce052ab 100644 --- a/test/conformance/conformance_test.go +++ b/test/conformance/conformance_test.go @@ -15,20 +15,17 @@ import ( "sigs.k8s.io/gateway-api/conformance" "sigs.k8s.io/gateway-api/conformance/tests" "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" + + internalconf "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) func TestGatewayAPIConformance(t *testing.T) { flag.Parse() opts := conformance.DefaultOptions(t) - opts.SkipTests = []string{ - tests.GatewayStaticAddresses.ShortName, - tests.GatewayHTTPListenerIsolation.ShortName, // https://github.com/kubernetes-sigs/gateway-api/issues/3049 - tests.HTTPRouteBackendRequestHeaderModifier.ShortName, // https://github.com/envoyproxy/gateway/issues/3338 - } - opts.SupportedFeatures = features.AllFeatures - opts.ExemptFeatures = features.MeshCoreFeatures + opts.SkipTests = internalconf.EnvoyGatewaySuite.SkipTests + opts.SupportedFeatures = internalconf.EnvoyGatewaySuite.SupportedFeatures + opts.ExemptFeatures = internalconf.EnvoyGatewaySuite.ExemptFeatures cSuite, err := suite.NewConformanceTestSuite(opts) if err != nil { diff --git a/test/conformance/experimental_conformance_test.go b/test/conformance/experimental_conformance_test.go index 702e919bca4..770abbc1c8a 100644 --- a/test/conformance/experimental_conformance_test.go +++ b/test/conformance/experimental_conformance_test.go @@ -20,21 +20,18 @@ import ( "sigs.k8s.io/gateway-api/conformance/tests" "sigs.k8s.io/gateway-api/conformance/utils/flags" "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" "sigs.k8s.io/yaml" + + internalconf "github.com/envoyproxy/gateway/internal/gatewayapi/conformance" ) func TestExperimentalConformance(t *testing.T) { flag.Parse() opts := conformance.DefaultOptions(t) - opts.SkipTests = []string{ - tests.GatewayStaticAddresses.ShortName, - tests.GatewayHTTPListenerIsolation.ShortName, // https://github.com/kubernetes-sigs/gateway-api/issues/3049 - tests.HTTPRouteBackendRequestHeaderModifier.ShortName, // https://github.com/envoyproxy/gateway/issues/3338 - } - opts.SupportedFeatures = features.AllFeatures - opts.ExemptFeatures = features.MeshCoreFeatures + opts.SkipTests = internalconf.EnvoyGatewaySuite.SkipTests + opts.SupportedFeatures = internalconf.EnvoyGatewaySuite.SupportedFeatures + opts.ExemptFeatures = internalconf.EnvoyGatewaySuite.ExemptFeatures opts.ConformanceProfiles = sets.New( suite.GatewayHTTPConformanceProfileName, suite.GatewayTLSConformanceProfileName, From 16f1b43aa8f65e8a9ead7b5ec1a5890a8cbc7f53 Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Wed, 15 May 2024 16:52:47 -0500 Subject: [PATCH 2/6] API: Backend (#3063) * start backend Signed-off-by: Guy Daich * update design Signed-off-by: Guy Daich * add section name Signed-off-by: Guy Daich * update doc Signed-off-by: Guy Daich * fix cel Signed-off-by: Guy Daich * set MaxItems=1 Signed-off-by: Guy Daich * fix links Signed-off-by: Guy Daich * remove support for named policy attachment Signed-off-by: Guy Daich * rename address proto to proto Signed-off-by: Guy Daich * fix review comments, change ApplicationProto scope and enum Signed-off-by: Guy Daich * add CEL test, optionals, list, init and hide from docs Signed-off-by: Guy Daich * remove IPv6 Signed-off-by: Guy Daich * add port validation Signed-off-by: Guy Daich * rm protocol, rename, update design Signed-off-by: Guy Daich * code review fixes Signed-off-by: Guy Daich * fix design and app protos json Signed-off-by: Guy Daich * fix test Signed-off-by: Guy Daich * rename backendAddresses to endpoints, ip to ipv4 Signed-off-by: Guy Daich * fix doc string to use address Signed-off-by: Guy Daich * improve all doc strings Signed-off-by: Guy Daich * fix ip => ipv4 Signed-off-by: Guy Daich * rename backendendpoints => endpoits Signed-off-by: Guy Daich * renamed fqdn.address => fqdn.hostname Signed-off-by: Guy Daich * add conditions, doc fixes Signed-off-by: Guy Daich --------- Signed-off-by: Guy Daich --- api/v1alpha1/backend_types.go | 193 +++++++++++++ api/v1alpha1/envoygateway_types.go | 3 + api/v1alpha1/zz_generated.deepcopy.go | 245 ++++++++++++++-- .../gateway.envoyproxy.io_backends.yaml | 224 +++++++++++++++ .../en/contributions/design/backend.md | 162 +++++++++++ site/content/en/latest/api/extension_types.md | 138 +++++++++ test/cel-validation/backend_test.go | 266 ++++++++++++++++++ 7 files changed, 1200 insertions(+), 31 deletions(-) create mode 100644 api/v1alpha1/backend_types.go create mode 100644 charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml create mode 100644 site/content/en/contributions/design/backend.md create mode 100644 test/cel-validation/backend_test.go diff --git a/api/v1alpha1/backend_types.go b/api/v1alpha1/backend_types.go new file mode 100644 index 00000000000..a35cd62b6e6 --- /dev/null +++ b/api/v1alpha1/backend_types.go @@ -0,0 +1,193 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // KindBackend is the name of the Backend kind. + KindBackend = "Backend" +) + +// AppProtocolType defines various backend applications protocols supported by Envoy Gateway +// +// +kubebuilder:validation:Enum=gateway.envoyproxy.io/h2c;gateway.envoyproxy.io/ws;gateway.envoyproxy.io/wss +// +notImplementedHide +type AppProtocolType string + +const ( + // AppProtocolTypeH2C defines the HTTP/2 application protocol. + AppProtocolTypeH2C AppProtocolType = "gateway.envoyproxy.io/h2c" + // AppProtocolTypeWS defines the WebSocket over HTTP protocol. + AppProtocolTypeWS AppProtocolType = "gateway.envoyproxy.io/ws" + // AppProtocolTypeWSS defines the WebSocket over HTTPS protocol. + AppProtocolTypeWSS AppProtocolType = "gateway.envoyproxy.io/wss" +) + +// Backend allows the user to configure the endpoints of a backend and +// the behavior of the connection from Envoy Proxy to the backend. +// +// +kubebuilder:object:root=true +// +kubebuilder:resource:categories=envoy-gateway,shortName=be +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].reason` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` +// +notImplementedHide +type Backend struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of Backend. + Spec BackendSpec `json:"spec"` + + // Status defines the current status of Backend. + Status BackendStatus `json:"status,omitempty"` +} + +// BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IPv4 address or unix domain socket +// corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address +// +// +kubebuilder:validation:XValidation:rule="(has(self.fqdn) || has(self.ipv4) || has(self.unix))",message="one of fqdn, ipv4 or unix must be specified" +// +kubebuilder:validation:XValidation:rule="((has(self.fqdn) && !(has(self.ipv4) || has(self.unix))) || (has(self.ipv4) && !(has(self.fqdn) || has(self.unix))) || (has(self.unix) && !(has(self.ipv4) || has(self.fqdn))))",message="only one of fqdn, ipv4 or unix can be specified" +// +notImplementedHide +type BackendEndpoint struct { + // FQDN defines a FQDN endpoint + // + // +optional + FQDN *FQDNEndpoint `json:"fqdn,omitempty"` + + // IPv4 defines an IPv4 endpoint + // + // +optional + IPv4 *IPv4Endpoint `json:"ipv4,omitempty"` + + // Unix defines the unix domain socket endpoint + // + // +optional + Unix *UnixSocket `json:"unix,omitempty"` +} + +// IPv4Endpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress +// +// +notImplementedHide +type IPv4Endpoint struct { + // Address defines the IPv4 address of the backend endpoint. + // + // +kubebuilder:validation:MinLength=7 + // +kubebuilder:validation:MaxLength=15 + // +kubebuilder:validation:Pattern=`^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` + Address string `json:"address"` + + // Port defines the port of the backend endpoint. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` +} + +// FQDNEndpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress +// +// +notImplementedHide +type FQDNEndpoint struct { + // Hostname defines the FQDN hostname of the backend endpoint. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + Hostname string `json:"hostname"` + + // Port defines the port of the backend endpoint. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` +} + +// UnixSocket describes TCP/UDP unix domain socket address, corresponding to Envoy's Pipe +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-pipe +// +// +notImplementedHide +type UnixSocket struct { + // Path defines the unix domain socket path of the backend endpoint. + Path string `json:"path"` +} + +// BackendSpec describes the desired state of BackendSpec. +// +// +notImplementedHide +type BackendSpec struct { + // Endpoints defines the endpoints to be used when connecting to the backend. + // + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=4 + // +kubebuilder:validation:XValidation:rule="self.all(f, has(f.fqdn)) || !self.exists(f, has(f.fqdn))",message="fqdn addresses cannot be mixed with other address types" + Endpoints []BackendEndpoint `json:"endpoints,omitempty"` + + // AppProtocols defines the application protocols to be supported when connecting to the backend. + // + // +optional + AppProtocols []AppProtocolType `json:"appProtocols,omitempty"` +} + +// BackendConditionType is a type of condition for a backend. This type should be +// used with a Backend resource Status.Conditions field. +type BackendConditionType string + +// BackendConditionReason is a reason for a backend condition. +type BackendConditionReason string + +const ( + // BackendConditionAccepted indicates whether the backend has been accepted or + // rejected by a targeted resource, and why. + // + // Possible reasons for this condition to be True are: + // + // * "Accepted" + // + // Possible reasons for this condition to be False are: + // + // * "Invalid" + // + BackendConditionAccepted BackendConditionType = "Accepted" + + // BackendReasonAccepted is used with the "Accepted" condition when the backend + // has been accepted by the targeted resource. + BackendReasonAccepted BackendConditionReason = "Accepted" + + // BackendReasonInvalid is used with the "Accepted" condition when the backend + // is syntactically or semantically invalid. + BackendReasonInvalid BackendConditionReason = "Invalid" +) + +// BackendStatus defines the state of Backend +// +notImplementedHide +type BackendStatus struct { + // Conditions describe the current conditions of the Backend. + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// BackendList contains a list of Backend resources. +// +// +kubebuilder:object:root=true +// +notImplementedHide +type BackendList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Backend `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Backend{}, &BackendList{}) +} diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go index b1de1937c61..29b816c5fcb 100644 --- a/api/v1alpha1/envoygateway_types.go +++ b/api/v1alpha1/envoygateway_types.go @@ -166,6 +166,9 @@ type ExtensionAPISettings struct { // EnableEnvoyPatchPolicy enables Envoy Gateway to // reconcile and implement the EnvoyPatchPolicy resources. EnableEnvoyPatchPolicy bool `json:"enableEnvoyPatchPolicy"` + // EnableBackend enables Envoy Gateway to + // reconcile and implement the Backend resources. + EnableBackend bool `json:"enableBackend"` } // EnvoyGatewayProvider defines the desired configuration of a provider. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c584ae2d62a..f3fd72c38d1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -14,9 +14,9 @@ import ( "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/gateway-api/apis/v1" + apisv1 "sigs.k8s.io/gateway-api/apis/v1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -86,12 +86,12 @@ func (in *ActiveHealthCheck) DeepCopyInto(out *ActiveHealthCheck) { *out = *in if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.Interval != nil { in, out := &in.Interval, &out.Interval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.UnhealthyThreshold != nil { @@ -183,12 +183,12 @@ func (in *BackOffPolicy) DeepCopyInto(out *BackOffPolicy) { *out = *in if in.BaseInterval != nil { in, out := &in.BaseInterval, &out.BaseInterval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.MaxInterval != nil { in, out := &in.MaxInterval, &out.MaxInterval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } } @@ -203,6 +203,95 @@ func (in *BackOffPolicy) DeepCopy() *BackOffPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Backend) DeepCopyInto(out *Backend) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend. +func (in *Backend) DeepCopy() *Backend { + if in == nil { + return nil + } + out := new(Backend) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Backend) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendEndpoint) DeepCopyInto(out *BackendEndpoint) { + *out = *in + if in.FQDN != nil { + in, out := &in.FQDN, &out.FQDN + *out = new(FQDNEndpoint) + **out = **in + } + if in.IPv4 != nil { + in, out := &in.IPv4, &out.IPv4 + *out = new(IPv4Endpoint) + **out = **in + } + if in.Unix != nil { + in, out := &in.Unix, &out.Unix + *out = new(UnixSocket) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendEndpoint. +func (in *BackendEndpoint) DeepCopy() *BackendEndpoint { + if in == nil { + return nil + } + out := new(BackendEndpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendList) DeepCopyInto(out *BackendList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Backend, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendList. +func (in *BackendList) DeepCopy() *BackendList { + if in == nil { + return nil + } + out := new(BackendList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackendList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackendRef) DeepCopyInto(out *BackendRef) { *out = *in @@ -219,12 +308,61 @@ func (in *BackendRef) DeepCopy() *BackendRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendSpec) DeepCopyInto(out *BackendSpec) { + *out = *in + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]BackendEndpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AppProtocols != nil { + in, out := &in.AppProtocols, &out.AppProtocols + *out = make([]AppProtocolType, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendSpec. +func (in *BackendSpec) DeepCopy() *BackendSpec { + if in == nil { + return nil + } + out := new(BackendSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendStatus) DeepCopyInto(out *BackendStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendStatus. +func (in *BackendStatus) DeepCopy() *BackendStatus { + if in == nil { + return nil + } + out := new(BackendStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackendTLSConfig) DeepCopyInto(out *BackendTLSConfig) { *out = *in if in.ClientCertificateRef != nil { in, out := &in.ClientCertificateRef, &out.ClientCertificateRef - *out = new(v1.SecretObjectReference) + *out = new(apisv1.SecretObjectReference) (*in).DeepCopyInto(*out) } in.TLSSettings.DeepCopyInto(&out.TLSSettings) @@ -417,7 +555,7 @@ func (in *CORS) DeepCopyInto(out *CORS) { } if in.MaxAge != nil { in, out := &in.MaxAge, &out.MaxAge - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.AllowCredentials != nil { @@ -693,7 +831,7 @@ func (in *ClientValidationContext) DeepCopyInto(out *ClientValidationContext) { *out = *in if in.CACertificateRefs != nil { in, out := &in.CACertificateRefs, &out.CACertificateRefs - *out = make([]v1.SecretObjectReference, len(*in)) + *out = make([]apisv1.SecretObjectReference, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -760,7 +898,7 @@ func (in *ConnectionLimit) DeepCopyInto(out *ConnectionLimit) { *out = *in if in.CloseDelay != nil { in, out := &in.CloseDelay, &out.CloseDelay - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -1692,7 +1830,7 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) { } if in.MessageTimeout != nil { in, out := &in.MessageTimeout, &out.MessageTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.FailOpen != nil { @@ -1843,6 +1981,21 @@ func (in *ExtensionTLS) DeepCopy() *ExtensionTLS { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FQDNEndpoint) DeepCopyInto(out *FQDNEndpoint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FQDNEndpoint. +func (in *FQDNEndpoint) DeepCopy() *FQDNEndpoint { + if in == nil { + return nil + } + out := new(FQDNEndpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FaultInjection) DeepCopyInto(out *FaultInjection) { *out = *in @@ -1903,7 +2056,7 @@ func (in *FaultInjectionDelay) DeepCopyInto(out *FaultInjectionDelay) { *out = *in if in.FixedDelay != nil { in, out := &in.FixedDelay, &out.FixedDelay - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.Percentage != nil { @@ -2176,12 +2329,12 @@ func (in *HTTPClientTimeout) DeepCopyInto(out *HTTPClientTimeout) { *out = *in if in.RequestReceivedTimeout != nil { in, out := &in.RequestReceivedTimeout, &out.RequestReceivedTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.IdleTimeout != nil { in, out := &in.IdleTimeout, &out.IdleTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -2227,12 +2380,12 @@ func (in *HTTPTimeout) DeepCopyInto(out *HTTPTimeout) { *out = *in if in.ConnectionIdleTimeout != nil { in, out := &in.ConnectionIdleTimeout, &out.ConnectionIdleTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.MaxConnectionDuration != nil { in, out := &in.MaxConnectionDuration, &out.MaxConnectionDuration - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -2352,6 +2505,21 @@ func (in *HealthCheck) DeepCopy() *HealthCheck { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPv4Endpoint) DeepCopyInto(out *IPv4Endpoint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPv4Endpoint. +func (in *IPv4Endpoint) DeepCopy() *IPv4Endpoint { + if in == nil { + return nil + } + out := new(IPv4Endpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageWasmCodeSource) DeepCopyInto(out *ImageWasmCodeSource) { *out = *in @@ -2846,7 +3014,7 @@ func (in *KubernetesWatchMode) DeepCopyInto(out *KubernetesWatchMode) { } if in.NamespaceSelector != nil { in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(metav1.LabelSelector) + *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } } @@ -2866,17 +3034,17 @@ func (in *LeaderElection) DeepCopyInto(out *LeaderElection) { *out = *in if in.LeaseDuration != nil { in, out := &in.LeaseDuration, &out.LeaseDuration - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.RenewDeadline != nil { in, out := &in.RenewDeadline, &out.RenewDeadline - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.RetryPeriod != nil { in, out := &in.RetryPeriod, &out.RetryPeriod - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.Disable != nil { @@ -3064,7 +3232,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { } if in.Interval != nil { in, out := &in.Interval, &out.Interval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.ConsecutiveLocalOriginFailures != nil { @@ -3084,7 +3252,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { } if in.BaseEjectionTime != nil { in, out := &in.BaseEjectionTime, &out.BaseEjectionTime - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.MaxEjectionPercent != nil { @@ -3134,7 +3302,7 @@ func (in *PerRetryPolicy) DeepCopyInto(out *PerRetryPolicy) { *out = *in if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.BackOff != nil { @@ -3518,7 +3686,7 @@ func (in *RateLimit) DeepCopyInto(out *RateLimit) { in.Backend.DeepCopyInto(&out.Backend) if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.Telemetry != nil { @@ -3778,7 +3946,7 @@ func (in *RedisTLSSettings) DeepCopyInto(out *RedisTLSSettings) { *out = *in if in.CertificateRef != nil { in, out := &in.CertificateRef, &out.CertificateRef - *out = new(v1.SecretObjectReference) + *out = new(apisv1.SecretObjectReference) (*in).DeepCopyInto(*out) } } @@ -4009,12 +4177,12 @@ func (in *ShutdownConfig) DeepCopyInto(out *ShutdownConfig) { *out = *in if in.DrainTimeout != nil { in, out := &in.DrainTimeout, &out.DrainTimeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.MinDrainDuration != nil { in, out := &in.MinDrainDuration, &out.MinDrainDuration - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } } @@ -4054,7 +4222,7 @@ func (in *SlowStart) DeepCopyInto(out *SlowStart) { *out = *in if in.Window != nil { in, out := &in.Window, &out.Window - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } } @@ -4144,12 +4312,12 @@ func (in *TCPKeepalive) DeepCopyInto(out *TCPKeepalive) { } if in.IdleTime != nil { in, out := &in.IdleTime, &out.IdleTime - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.Interval != nil { in, out := &in.Interval, &out.Interval - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -4169,7 +4337,7 @@ func (in *TCPTimeout) DeepCopyInto(out *TCPTimeout) { *out = *in if in.ConnectTimeout != nil { in, out := &in.ConnectTimeout, &out.ConnectTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -4281,6 +4449,21 @@ func (in *TracingProvider) DeepCopy() *TracingProvider { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UnixSocket) DeepCopyInto(out *UnixSocket) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnixSocket. +func (in *UnixSocket) DeepCopy() *UnixSocket { + if in == nil { + return nil + } + out := new(UnixSocket) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Wasm) DeepCopyInto(out *Wasm) { *out = *in diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml new file mode 100644 index 00000000000..cb3538d9874 --- /dev/null +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml @@ -0,0 +1,224 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: backends.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: Backend + listKind: BackendList + plural: backends + shortNames: + - be + singular: backend + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Backend allows the user to configure the endpoints of a backend and + the behavior of the connection from Envoy Proxy to the backend. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Backend. + properties: + appProtocols: + description: AppProtocols defines the application protocols to be + supported when connecting to the backend. + items: + description: AppProtocolType defines various backend applications + protocols supported by Envoy Gateway + enum: + - gateway.envoyproxy.io/h2c + - gateway.envoyproxy.io/ws + - gateway.envoyproxy.io/wss + type: string + type: array + endpoints: + description: Endpoints defines the endpoints to be used when connecting + to the backend. + items: + description: |- + BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IPv4 address or unix domain socket + corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address + properties: + fqdn: + description: FQDN defines a FQDN endpoint + properties: + hostname: + description: Hostname defines the FQDN hostname of the backend + endpoint. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - hostname + - port + type: object + ipv4: + description: IPv4 defines an IPv4 endpoint + properties: + address: + description: Address defines the IPv4 address of the backend + endpoint. + maxLength: 15 + minLength: 7 + pattern: ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - address + - port + type: object + unix: + description: Unix defines the unix domain socket endpoint + properties: + path: + description: Path defines the unix domain socket path of + the backend endpoint. + type: string + required: + - path + type: object + type: object + x-kubernetes-validations: + - message: one of fqdn, ipv4 or unix must be specified + rule: (has(self.fqdn) || has(self.ipv4) || has(self.unix)) + - message: only one of fqdn, ipv4 or unix can be specified + rule: ((has(self.fqdn) && !(has(self.ipv4) || has(self.unix))) + || (has(self.ipv4) && !(has(self.fqdn) || has(self.unix))) || + (has(self.unix) && !(has(self.ipv4) || has(self.fqdn)))) + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-validations: + - message: fqdn addresses cannot be mixed with other address types + rule: self.all(f, has(f.fqdn)) || !self.exists(f, has(f.fqdn)) + type: object + status: + description: Status defines the current status of Backend. + properties: + conditions: + description: Conditions describe the current conditions of the Backend. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/site/content/en/contributions/design/backend.md b/site/content/en/contributions/design/backend.md new file mode 100644 index 00000000000..32596bffd32 --- /dev/null +++ b/site/content/en/contributions/design/backend.md @@ -0,0 +1,162 @@ +--- +title: "Backend" +--- + +## Overview + +This design document introduces the `Backend` API allowing system administrators to represent backends without the use +of a K8s `Service` resource. + +Common use cases for non-Service backends in the K8s and Envoy ecosystem include: +- Cluster-external endpoints, which are currently second-class citizens in Gateway-API + (supported using [Services and FQDN endpoints][]). +- Host-local endpoints, such as sidecars or daemons that listen on [unix domain sockets][] or envoy [internal listeners][], + that cannot be represented by a K8s service at all. + +Several projects currently support backends that are not registered in the infrastructure-specific service registry. +- K8s Ingress: [Resource Backends][] +- Istio: [Service Entry][] +- Gloo Edge: [Upstream][] +- Consul: [External Services][] + +## Goals +* Add an API definition to hold settings for configuring Unix Domain Socket, FQDN and IP. +* Determine which resources may reference the new backend resource. +* Determine which existing Gateway-API and Envoy Gateway policies may attach to the new backend resource. + +## Non Goals +* Support specific backend types, such as S3 Bucket, Redis, AMQP, InfluxDB, etc. + +## Implementation + +The `Backend` resource is an implementation-specific Gateway-API [BackendObjectReference Extension][]. + +### Example +Here is an example highlighting how a user can configure a route that forwards traffic to both a K8s Service and a Backend +that has both unix domain socket and ipv4 endpoints. A [BackendTLSPolicy][] is attached to the backend resource, enabling TLS. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + ports: + - name: http + port: 3000 + targetPort: 3000 + selector: + app: backend +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-mixed-ip-uds +spec: + appProtocols: + - gateway.envoyproxy.io/h2c + endpoints: + - unix: + path: /var/run/backend.sock + - ipv4: + address: 10.244.0.28 + port: 3000 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-mixed-ip-uds + weight: 1 + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: BackendTLSPolicy +metadata: + name: policy-btls +spec: + targetRef: + group: gateway.envoyproxy.io + kind: Backend + name: backend-mixed-ip-uds + tls: + caCertRefs: + - name: backend-tls-checks-certificate + group: '' + kind: ConfigMap + hostname: example.com +``` + +## Design Decisions +* All instances of `BackendObjectReference` in Envoy Gateway MAY support referencing the `Backend` kind. +* For security reasons, Envoy Gateway MUST reject references to a `Backend` in xRoute resources. For example, UDS and + localhost references will not be supported for xRoutes. +* All attributes of the Envoy Gateway extended `BackendRef` resource MUST be implemented for the `Backend` resource. +* A `Backend` resource referenced by `BackendObjectReference` will be translated to Envoy Gateway's IR DestinationSetting. + As such, all `BackendAdresses` are treated as equivalent endpoints with identical weights, TLS settings, etc. +* Gateway-API and Envoy Gateway policies that attach to Services ([BackendTLSPolicy][], [BackendLBPolicy][]) + MUST support attachment to the `Backend` resource in Envoy Gateway. +* Policy attachment to a named section of the `Backend` resource is not supported at this time. Currently, + `BackendObjectReference` can only select ports, and not generic section names. Hence, a named section of `Backend` + cannot be referenced by routes, and so attachment of policies to named sections will create translation ambiguity. + Users that wish to attach policies to some of the `BackendAddresses` in a `Backend` resource can use multiple `Backend` + resources and pluralized `BackendRefs` instead. +* The `Backend` API SHOULD support other Gateway-API backend features, such as [Backend Protocol Selection][]. + Translation of explicit upstream application protocol setting SHOULD be consistent with the existing implementation for + `Service` resources. +* The `Backend` upstream transport protocol (TCP, UDP) is inferred from the xRoute kind: TCP is inferred for all routes + except for `UDPRoute` which is resolved to UDP. +* This API resource MUST be part of same namespace as the targetRef resource. The `Backend` API MUST be subject to + the same cross-namespace reference restriction as referenced `Service` resources. +* The `Backend` resource translation MUST NOT modify Infrastructure. Any change to infrastructure that is required to + achieve connectivity to a backend (mounting a socket, adding a sidecar container, modifying a network policy, ...) + MUST be implemented with an appropriate infrastructure patch in the [EnvoyProxy][] API. +* To limit the overall maintenance effort related to supporting of non-Service backends, the `Backend` API SHOULD + support multiple generic address types (UDS, FQDN, IPv4, IPv6), and MUST NOT support vendor-specific backend types. +* Both `Backend` and `Service` resources may appear in the same `BackendRefs` list. +* The Optional `Port` field SHOULD NOT be evaluated when referencing a `Backend`. +* Referenced `Backend` resources MUST be translated to envoy endpoints, similar to the current `Service` translation. +* Certain combinations of `Backend` and `Service` are incompatible. For example, a Unix Domain Socket and a FQDN service + require different cluster service discovery types (Static/EDS and Strict-DNS accordingly). +* If a Backend that is referenced by a route cannot be translated, the `Route` resource will have an `Accepted=False` + condition with a `UnsupportedValue` reason. +* This API needs to be explicitly enabled using the [EnvoyGateway][] API + +## Alternatives +* The project can indefinitely wait for these configuration parameters to be part of the [Gateway API][]. +* Users can leverage the existing [Envoy Patch Policy][] or [Envoy Extension Manager][] to inject custom envoy clusters + and route configuration. However, these features require a high level of envoy expertise, investment and maintenance. + +[BackendObjectReference Extension]: https://gateway-api.sigs.k8s.io/guides/migrating-from-ingress/?h=extensi#approach-to-extensibility +[internal listeners]: https://www.envoyproxy.io/docs/envoy/latest/configuration/other_features/internal_listener +[unix domain sockets]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#envoy-v3-api-msg-config-core-v3-pipe +[Resource Backends]: https://kubernetes.io/docs/concepts/services-networking/ingress/#resource-backend +[Services and FQDN endpoints]: ./../../latest/tasks/traffic/routing-outside-kubernetes.md +[Service Entry]: https://istio.io/latest/docs/reference/config/networking/service-entry/ +[Upstream]: https://docs.solo.io/gloo-edge/1.7.23/reference/api/github.com/solo-io/gloo/projects/gloo/api/v1/upstream.proto.sk/ +[External Services]: https://developer.hashicorp.com/consul/tutorials/developer-mesh/terminating-gateways-connect-external-services +[BackendTLSPolicy]: https://gateway-api.sigs.k8s.io/geps/gep-1897/ +[BackendLBPolicy]: https://gateway-api.sigs.k8s.io/geps/gep-1619/ +[Backend Protocol Selection]: https://gateway-api.sigs.k8s.io/geps/gep-1911/ +[EnvoyProxy]:../../latest/api/extension_types#envoyproxy +[EnvoyGateway]: ../../latest/api/extension_types#envoygateway +[Gateway API]: https://gateway-api.sigs.k8s.io/ +[Envoy Patch Policy]: ../../latest/api/extension_types#envoypatchpolicy +[Envoy Extension Manager]: ./extending-envoy-gateway diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index cde865d9ab2..716ad0bc78d 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -14,6 +14,8 @@ API group. ### Resource Types +- [Backend](#backend) +- [BackendList](#backendlist) - [BackendTrafficPolicy](#backendtrafficpolicy) - [BackendTrafficPolicyList](#backendtrafficpolicylist) - [ClientTrafficPolicy](#clienttrafficpolicy) @@ -171,6 +173,22 @@ _Appears in:_ | `TCP` | ActiveHealthCheckerTypeTCP defines the TCP type of health checking.
| +#### AppProtocolType + +_Underlying type:_ _string_ + +AppProtocolType defines various backend applications protocols supported by Envoy Gateway + +_Appears in:_ +- [BackendSpec](#backendspec) + +| Value | Description | +| ----- | ----------- | +| `gateway.envoyproxy.io/h2c` | AppProtocolTypeH2C defines the HTTP/2 application protocol.
| +| `gateway.envoyproxy.io/ws` | AppProtocolTypeWS defines the WebSocket over HTTP protocol.
| +| `gateway.envoyproxy.io/wss` | AppProtocolTypeWSS defines the WebSocket over HTTPS protocol.
| + + #### Authorization @@ -201,6 +219,61 @@ _Appears in:_ | `maxInterval` | _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#duration-v1-meta)_ | false | MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set.
The default is 10 times the base_interval | +#### Backend + + + +Backend allows the user to configure the endpoints of a backend and +the behavior of the connection from Envoy Proxy to the backend. + +_Appears in:_ +- [BackendList](#backendlist) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `apiVersion` | _string_ | |`gateway.envoyproxy.io/v1alpha1` +| `kind` | _string_ | |`Backend` +| `metadata` | _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | true | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` | _[BackendSpec](#backendspec)_ | true | Spec defines the desired state of Backend. | + + + + + + +#### BackendEndpoint + + + +BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IPv4 address or unix domain socket +corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address + +_Appears in:_ +- [BackendSpec](#backendspec) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `fqdn` | _[FQDNEndpoint](#fqdnendpoint)_ | false | FQDN defines a FQDN endpoint | +| `ipv4` | _[IPv4Endpoint](#ipv4endpoint)_ | false | IPv4 defines an IPv4 endpoint | +| `unix` | _[UnixSocket](#unixsocket)_ | false | Unix defines the unix domain socket endpoint | + + +#### BackendList + + + +BackendList contains a list of Backend resources. + + + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `apiVersion` | _string_ | |`gateway.envoyproxy.io/v1alpha1` +| `kind` | _string_ | |`BackendList` +| `metadata` | _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#listmeta-v1-meta)_ | true | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` | _[Backend](#backend) array_ | true | | + + #### BackendRef @@ -223,6 +296,23 @@ _Appears in:_ | `port` | _[PortNumber](#portnumber)_ | false | Port specifies the destination port number to use for this resource.
Port is required when the referent is a Kubernetes Service. In this
case, the port number is the service port number, not the target port.
For other resources, destination port might be derived from the referent
resource or this field. | +#### BackendSpec + + + +BackendSpec describes the desired state of BackendSpec. + +_Appears in:_ +- [Backend](#backend) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `endpoints` | _[BackendEndpoint](#backendendpoint) array_ | true | Endpoints defines the endpoints to be used when connecting to the backend. | +| `appProtocols` | _[AppProtocolType](#appprotocoltype) array_ | false | AppProtocols defines the application protocols to be supported when connecting to the backend. | + + + + #### BackendTLSConfig @@ -1280,6 +1370,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `enableEnvoyPatchPolicy` | _boolean_ | true | EnableEnvoyPatchPolicy enables Envoy Gateway to
reconcile and implement the EnvoyPatchPolicy resources. | +| `enableBackend` | _boolean_ | true | EnableBackend enables Envoy Gateway to
reconcile and implement the Backend resources. | #### ExtensionHooks @@ -1344,6 +1435,22 @@ _Appears in:_ | `certificateRef` | _[SecretObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.SecretObjectReference)_ | true | CertificateRef contains a references to objects (Kubernetes objects or otherwise) that
contains a TLS certificate and private keys. These certificates are used to
establish a TLS handshake to the extension server.

CertificateRef can only reference a Kubernetes Secret at this time. | +#### FQDNEndpoint + + + +FQDNEndpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress + +_Appears in:_ +- [BackendEndpoint](#backendendpoint) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `hostname` | _string_ | true | Hostname defines the FQDN hostname of the backend endpoint. | +| `port` | _integer_ | true | Port defines the port of the backend endpoint. | + + #### FaultInjection @@ -1707,6 +1814,22 @@ _Appears in:_ | `passive` | _[PassiveHealthCheck](#passivehealthcheck)_ | false | Passive passive check configuration | +#### IPv4Endpoint + + + +IPv4Endpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress + +_Appears in:_ +- [BackendEndpoint](#backendendpoint) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `address` | _string_ | true | Address defines the IPv4 address of the backend endpoint. | +| `port` | _integer_ | true | Port defines the port of the backend endpoint. | + + #### ImageWasmCodeSource @@ -3283,6 +3406,21 @@ _Appears in:_ | `unavailable` | The gRPC status code in the response headers is “unavailable”.
| +#### UnixSocket + + + +UnixSocket describes TCP/UDP unix domain socket address, corresponding to Envoy's Pipe +https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-pipe + +_Appears in:_ +- [BackendEndpoint](#backendendpoint) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `path` | _string_ | true | Path defines the unix domain socket path of the backend endpoint. | + + #### Wasm diff --git a/test/cel-validation/backend_test.go b/test/cel-validation/backend_test.go new file mode 100644 index 00000000000..ab8efd7e629 --- /dev/null +++ b/test/cel-validation/backend_test.go @@ -0,0 +1,266 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +//go:build celvalidation +// +build celvalidation + +package celvalidation + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" +) + +func TestBackend(t *testing.T) { + ctx := context.Background() + baseBackend := egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backend", + Namespace: metav1.NamespaceDefault, + }, + Spec: egv1a1.BackendSpec{}, + } + + cases := []struct { + desc string + mutate func(backend *egv1a1.Backend) + mutateStatus func(backend *egv1a1.Backend) + wantErrors []string + }{ + { + desc: "Valid static", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + Unix: &egv1a1.UnixSocket{ + Path: "/path/to/service.sock", + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "1.1.1.1", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "Valid DNS", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example2.com", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "unsupported application protocol type", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{"HTTP7"}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{"spec.appProtocols[0]: Unsupported value: \"HTTP7\": supported values: \"gateway.envoyproxy.io/h2c\", \"gateway.envoyproxy.io/ws\", \"gateway.envoyproxy.io/wss\""}, + }, + { + desc: "No address", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{{}}, + } + }, + wantErrors: []string{"spec.endpoints[0]: Invalid value: \"object\": one of fqdn, ipv4 or unix must be specified"}, + }, + { + desc: "Multiple addresses", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + Unix: &egv1a1.UnixSocket{ + Path: "/path/to/service.sock", + }, + }, + }, + } + }, + wantErrors: []string{"spec.endpoints[0]: Invalid value: \"object\": only one of fqdn, ipv4 or unix can be specified"}, + }, + { + desc: "Mixed types", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "1.1.1.1", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{"spec.endpoints: Invalid value: \"array\": FQDN addresses cannot be mixed with other address types"}, + }, + { + desc: "Invalid hostname", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "host name", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "host_name", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "hostname:443", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "host.*.name", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.endpoints[0].fqdn.hostname: Invalid value: \"host name\": spec.endpoints[0].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + "spec.endpoints[1].fqdn.hostname: Invalid value: \"host_name\": spec.endpoints[1].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + "spec.endpoints[2].fqdn.hostname: Invalid value: \"hostname:443\": spec.endpoints[2].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + "spec.endpoints[3].fqdn.hostname: Invalid value: \"host.*.name\": spec.endpoints[3].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + }, + }, + { + desc: "Invalid IPv4", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "300.0.0.0", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "0.0.0.0:443", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "0.0.0.0/12", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "a.b.c.e", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.endpoints[0].ipv4.address: Invalid value: \"300.0.0.0\": spec.endpoints[0].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + "spec.endpoints[1].ipv4.address: Invalid value: \"0.0.0.0:443\": spec.endpoints[1].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + "spec.endpoints[2].ipv4.address: Invalid value: \"0.0.0.0/12\": spec.endpoints[2].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + "spec.endpoints[3].ipv4.address: Invalid value: \"a.b.c.e\": spec.endpoints[3].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + }, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + backend := baseBackend.DeepCopy() + backend.Name = fmt.Sprintf("backend-%v", time.Now().UnixNano()) + + if tc.mutate != nil { + tc.mutate(backend) + } + err := c.Create(ctx, backend) + + if tc.mutateStatus != nil { + tc.mutateStatus(backend) + err = c.Status().Update(ctx, backend) + } + + if (len(tc.wantErrors) != 0) != (err != nil) { + t.Fatalf("Unexpected response while creating Backend; got err=\n%v\n;want error=%v", err, tc.wantErrors) + } + + var missingErrorStrings []string + for _, wantError := range tc.wantErrors { + if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) { + missingErrorStrings = append(missingErrorStrings, wantError) + } + } + if len(missingErrorStrings) != 0 { + t.Errorf("Unexpected response while creating Backend; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings) + } + }) + } +} From 6912dc0bae944873d88a943c186b6b57787146bb Mon Sep 17 00:00:00 2001 From: Dingkang Li Date: Thu, 16 May 2024 06:17:51 +0800 Subject: [PATCH 3/6] feat(api): add idleTimeout to ClientTrafficPolicy for TCP listener (#3345) * Add tcp idle timeout api Signed-off-by: Dingkang Li * State tcp client timeout usage Signed-off-by: Dingkang Li --------- Signed-off-by: Dingkang Li --- api/v1alpha1/timeout_types.go | 15 +++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 25 +++++++++++++++++++ ...y.envoyproxy.io_clienttrafficpolicies.yaml | 11 ++++++++ site/content/en/latest/api/extension_types.md | 15 +++++++++++ 4 files changed, 66 insertions(+) diff --git a/api/v1alpha1/timeout_types.go b/api/v1alpha1/timeout_types.go index ccf50724305..36c0c320ed2 100644 --- a/api/v1alpha1/timeout_types.go +++ b/api/v1alpha1/timeout_types.go @@ -43,12 +43,27 @@ type HTTPTimeout struct { } type ClientTimeout struct { + // Timeout settings for TCP. + // + // +optional + TCP *TCPClientTimeout `json:"tcp,omitempty"` + // Timeout settings for HTTP. // // +optional HTTP *HTTPClientTimeout `json:"http,omitempty"` } +// TCPClientTimeout only provides timeout configuration on the listener whose protocol is TCP or TLS. +type TCPClientTimeout struct { + // IdleTimeout for a TCP connection. Idle time is defined as a period in which there are no + // bytes sent or received on either the upstream or downstream connection. + // Default: 1 hour. + // + // +optional + IdleTimeout *gwapiv1.Duration `json:"idleTimeout,omitempty"` +} + type HTTPClientTimeout struct { // RequestReceivedTimeout is the duration envoy waits for the complete request reception. This timer starts upon request // initiation and stops when either the last byte of the request is sent upstream or when the response begins. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f3fd72c38d1..f29855aab7e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -679,6 +679,11 @@ func (in *ClientTLSSettings) DeepCopy() *ClientTLSSettings { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientTimeout) DeepCopyInto(out *ClientTimeout) { *out = *in + if in.TCP != nil { + in, out := &in.TCP, &out.TCP + *out = new(TCPClientTimeout) + (*in).DeepCopyInto(*out) + } if in.HTTP != nil { in, out := &in.HTTP, &out.HTTP *out = new(HTTPClientTimeout) @@ -4302,6 +4307,26 @@ func (in *TCPActiveHealthChecker) DeepCopy() *TCPActiveHealthChecker { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TCPClientTimeout) DeepCopyInto(out *TCPClientTimeout) { + *out = *in + if in.IdleTimeout != nil { + in, out := &in.IdleTimeout, &out.IdleTimeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPClientTimeout. +func (in *TCPClientTimeout) DeepCopy() *TCPClientTimeout { + if in == nil { + return nil + } + out := new(TCPClientTimeout) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPKeepalive) DeepCopyInto(out *TCPKeepalive) { *out = *in diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index b45522e3e39..a9d3fb95fcf 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -348,6 +348,17 @@ spec: pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ type: string type: object + tcp: + description: Timeout settings for TCP. + properties: + idleTimeout: + description: |- + IdleTimeout for a TCP connection. Idle time is defined as a period in which there are no + bytes sent or received on either the upstream or downstream connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object type: object tls: description: TLS settings configure TLS termination settings with diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 716ad0bc78d..6ac4fe6b123 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -518,6 +518,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | +| `tcp` | _[TCPClientTimeout](#tcpclienttimeout)_ | false | Timeout settings for TCP. | | `http` | _[HTTPClientTimeout](#httpclienttimeout)_ | false | Timeout settings for HTTP. | @@ -3263,6 +3264,20 @@ _Appears in:_ | `receive` | _[ActiveHealthCheckPayload](#activehealthcheckpayload)_ | false | Receive defines the expected response payload. | +#### TCPClientTimeout + + + +TCPClientTimeout only provides timeout configuration on the listener whose protocol is TCP or TLS. + +_Appears in:_ +- [ClientTimeout](#clienttimeout) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `idleTimeout` | _[Duration](#duration)_ | false | IdleTimeout for a TCP connection. Idle time is defined as a period in which there are no
bytes sent or received on either the upstream or downstream connection.
Default: 1 hour. | + + #### TCPKeepalive From 452063b5bf44baac48878149dfa0469413a31444 Mon Sep 17 00:00:00 2001 From: Huabing Zhao Date: Wed, 15 May 2024 17:39:08 -0700 Subject: [PATCH 4/6] fix: build is failing (#3402) fix build Signed-off-by: huabing zhao --- api/v1alpha1/zz_generated.deepcopy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f29855aab7e..fd607c16af7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -4312,7 +4312,7 @@ func (in *TCPClientTimeout) DeepCopyInto(out *TCPClientTimeout) { *out = *in if in.IdleTimeout != nil { in, out := &in.IdleTimeout, &out.IdleTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } From 76a60574c9b38d0bc3064ff3cd1662b28788b365 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 11:13:03 +0800 Subject: [PATCH 5/6] build(deps): bump aquasecurity/trivy-action from 0.19.0 to 0.20.0 (#3380) Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.19.0 to 0.20.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/d710430a6722f083d3b36b8339ff66b32f22ee55...b2933f565dbc598b29947660e66259e3c7bc8561) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: zirain --- .github/workflows/trivy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 1529aaf24d6..f9218b9287a 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -25,7 +25,7 @@ jobs: IMAGE=envoy-proxy/gateway-dev TAG=${{ github.sha }} make image - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@d710430a6722f083d3b36b8339ff66b32f22ee55 # v0.19.0 + uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # v0.20.0 with: image-ref: envoy-proxy/gateway-dev:${{ github.sha }} exit-code: '1' From c5ec046be14c4bb9049a7d3633900d2b8207d40f Mon Sep 17 00:00:00 2001 From: Wilson Wu Date: Thu, 16 May 2024 11:13:24 +0800 Subject: [PATCH 6/6] docs(zh): translate Developer Guide and related docs into Chinese (#3384) * Translate Developer Guide and related docs into Chinese Signed-off-by: Wilson Wu * Update site/content/zh/contributions/DEVELOP.md Co-authored-by: sh2 Signed-off-by: Wilson Wu * Translate missed security policy design Signed-off-by: Wilson Wu * Update site/content/zh/contributions/design/security-policy.md Co-authored-by: sh2 Signed-off-by: Wilson Wu --------- Signed-off-by: Wilson Wu Co-authored-by: sh2 --- site/content/zh/contributions/DEVELOP.md | 156 ++++++++++ .../contributions/design/security-policy.md | 114 ++++++++ site/content/zh/latest/tasks/quickstart.md | 2 +- .../tasks/security/jwt-authentication.md | 164 +++++++++++ .../zh/latest/tasks/traffic/grpc-routing.md | 271 ++++++++++++++++++ 5 files changed, 706 insertions(+), 1 deletion(-) create mode 100644 site/content/zh/contributions/DEVELOP.md create mode 100644 site/content/zh/contributions/design/security-policy.md create mode 100644 site/content/zh/latest/tasks/security/jwt-authentication.md create mode 100644 site/content/zh/latest/tasks/traffic/grpc-routing.md diff --git a/site/content/zh/contributions/DEVELOP.md b/site/content/zh/contributions/DEVELOP.md new file mode 100644 index 00000000000..b1b270dbe97 --- /dev/null +++ b/site/content/zh/contributions/DEVELOP.md @@ -0,0 +1,156 @@ +--- +title: "开发者指南" +description: "本节介绍如何开发 Envoy Gateway。" +weight: 2 +--- + +Envoy Gateway 使用基于 [make][] 的构建系统进行构建。我们的 CI 使用基于 [Github Actions][] 的[工作流][]建设。 + +## 先决条件 {#prerequisites} + +### go {#go} + +* 版本:1.20 +* 安装指南:https://go.dev/doc/install + +### make {#make} + +* 推荐版本:4.0 或更高 +* 安装指南:https://www.gnu.org/software/make + +### docker {#docker} + +* 当您想要构建 Docker 镜像或在 Docker 内运行 `make` 时可选。 +* 推荐版本:20.10.16 +* 安装指南:https://docs.docker.com/engine/install + +### python3 {#python3} + +* 需要一个 `python3` 程序 +* 必须有一个正常运行的 `venv` 模块;这是标准库的一部分,但某些发行版使用 stub 将其替换(例如 Debian 和 Ubuntu), + 并要求您单独安装 `python3-venv` 包。 + +## 快速开始 {#quickstart} + +* 运行 `make help` 以查看构建、测试和运行 Envoy Gateway 的所有可用目标。 + +### 构建 {#building} + +* 运行 `make build` 来构建所有二进制文件。 +* 运行 `make build BINS="envoy-gateway"` 来构建 Envoy Gateway 二进制文件。 +* 运行 `make build BINS="egctl"` 来构建 egctl 二进制文件。 + +**注意:**二进制文件在 `bin/$OS/$ARCH` 目录中生成,例如,`bin/linux/amd64/`。 + +### 测试 {#testing} + +* 运行 `make test` 来运行 golang 测试。 + +* 运行 `make testdata` 生成标准的 YAML 测试数据文件。 + +### 运行 Linter {#running-linters} + +* 运行 `make lint` 以确保您的代码通过所有 Linter 检查。 + +**注意:**`golangci-lint` 配置位于[此处](https://github.com/envoyproxy/gateway/blob/main/tools/linter/golangci-lint/.golangci.yml)。 + +### 构建并推送镜像 {#building-and-pushing-the-image} + +* 运行 `IMAGE=docker.io/you/gateway-dev make image` 来构建 Docker 镜像。 +* 运行 `IMAGE=docker.io/you/gateway-dev make push-multiarch` 来构建并推送多架构 Docker 镜像。 + +**注意:**将 `IMAGE` 替换为您的仓库的镜像名称。 + +### 部署 Envoy Gateway 进行测试/开发 {#deploying-envoy-gateway-for-testdev} + +* 运行 `make create-cluster` 创建一个 [Kind][] 集群。 + +#### 选项 1:使用最新的 [gateway-dev][] 镜像 {#option-1-use-the-latest-gateway-dev-image} + +* 运行 `TAG=latest make kube-deploy` 以使用最新镜像在 Kind 集群中部署 Envoy Gateway。 + 替换 `latest` 以使用不同的镜像标签。 + +#### 选项 2:使用自定义镜像 {#option-2-use-a-custom-image} + +* 运行 `make kube-install-image` 从当前分支的最新构建镜像并将其加载到 Kind 集群中。 +* 运行 `IMAGE_PULL_POLICY=IfNotPresent make kube-deploy` 以使用自定义镜像将 Envoy Gateway 安装到 Kind 集群中。 + +### 在 Kubernetes 中部署 Envoy Gateway {#deploying-envoy-gateway-in-kubernetes} + +* 运行 `TAG=latest make kube-deploy` 以使用最新镜像将 Envoy Gateway 部署到 Kubernetes 集群(链接到当前 kube 上下文)。 + 在命令前面加上 `IMAGE` 或替换 `TAG` 以使用不同的 Envoy Gateway 镜像或标签。 +* 运行 `make kube-undeploy` 从集群中卸载 Envoy Gateway。 + +**注意:**Envoy Gateway 针对 Kubernetes v1.24.0 进行了测试。 + +### 演示设置 {#demo-setup} + +* 运行 `make kube-demo` 来部署演示后端服务、GatewayClass、Gateway 和 HTTPRoute 资源 + (类似于[快速入门][]文档中概述的步骤)并测试配置。 +* 运行 `make kube-demo-undeploy` 删除 `make kube-demo` 命令创建的资源。 + +### 运行 Gateway API 一致性测试 {#run-gateway-api-conformance-tests} + +通过以下命令将 Envoy Gateway 部署到 Kubernetes 集群并运行 Gateway API 一致性测试。 +请参阅 Gateway API [一致性主页][]以了解有关测试的更多信息。如果 Envoy Gateway 已安装, +请运行 `TAG=latest make run-conformance` 来运行一致性测试。 + +#### 在 Linux 主机上 {#on-a-linux-host} + +* 运行 `TAG=latest make conformance` 来创建 Kind 集群, + 使用最新的 [gateway-dev][] 镜像安装 Envoy Gateway,并运行 Gateway API 一致性测试。 + +#### 在 Mac 主机上 {#on-a-mac-host} + +由于 Mac 不支持将 Docker 网络[直接暴露][]到 Mac 主机,因此请使用以下解决方法之一来运行一致性测试: + +* 部署您自己的 Kubernetes 集群或使用具有 [Kubernetes 支持][] 的 Docker Desktop, + 然后运行 `TAG=latest make kube-deploy run-conformance`。这将使用最新的 [gateway-dev][] 镜像将 Envoy Gateway + 安装到使用当前 kubectl 上下文的 Kubernetes 集群,并运行一致性测试。使用 `make kube-undeploy` 卸载 Envoy Gateway。 +* 安装并运行 [Docker Mac Net Connect][mac_connect],然后运行 `TAG=latest make conformance`。 + +**注意:**在命令前加上 `IMAGE` 或替换 `TAG` 以使用不同的 Envoy Gateway 镜像或标签。如果未指定 `TAG`,则使用当前分支的短 SHA。 + +### 调试 Envoy 配置 {#debugging-the-envoy-config} + +查看 Envoy Gateway 正在使用的 Envoy 配置的一种简单方法是将端口转发到与 Gateway +对应的 Envoy 部署上的管理界面端口(当前为 `19000`),以便可以在本地访问它。 + +获取 Envoy 部署的名称。以下示例适用于 `default` 命名空间中的网关 `eg`: + +```shell +export ENVOY_DEPLOYMENT=$(kubectl get deploy -n envoy-gateway-system --selector=gateway.envoyproxy.io/owning-gateway-namespace=default,gateway.envoyproxy.io/owning-gateway-name=eg -o jsonpath='{.items[0].metadata.name}') +``` + +通过端口转发管理接口端口: + +```shell +kubectl port-forward deploy/${ENVOY_DEPLOYMENT} -n envoy-gateway-system 19000:19000 +``` + +现在,您可以通过导航到 `127.0.0.1:19000/config_dump` 来查看正在运行的 Envoy 配置。 + +[Envoy 管理接口][]上还有许多其他端点在调试时可能会有所帮助。 + +### JWT 测试 {#jwt-testing} + +[JSON Web Token(JWT)][jwt]和 [JSON Web Key Set(JWKS)][jwks]示例用于[请求身份验证][]任务。 +JWT 由 [JWT Debugger][] 使用 `RS256` 算法创建。JWT 验证签名中的公钥已复制到 [JWK Creator][] 以生成 JWK。 +JWK Creator 配置了匹配的设置,即 `Signing` 公钥使用和 `RS256` 算法。生成的 JWK 包装在 JWKS 结构中并托管在仓库中。 + +[快速入门]: https://github.com/envoyproxy/gateway/blob/main/docs/latest/user/quickstart.md +[make]: https://www.gnu.org/software/make/ +[Github Actions]: https://docs.github.com/en/actions +[工作流]: https://github.com/envoyproxy/gateway/tree/main/.github/workflows +[Kind]: https://kind.sigs.k8s.io/ +[一致性主页]: https://gateway-api.sigs.k8s.io/concepts/conformance/ +[直接暴露]: https://kind.sigs.k8s.io/docs/user/loadbalancer/ +[Kubernetes 支持]: https://docs.docker.com/desktop/kubernetes/ +[gateway-dev]: https://hub.docker.com/r/envoyproxy/gateway-dev/tags +[mac_connect]: https://github.com/chipmk/docker-mac-net-connect +[Envoy 管理接口]: https://www.envoyproxy.io/docs/envoy/latest/operations/admin#operations-admin-interface +[jwt]: https://tools.ietf.org/html/rfc7519 +[jwks]: https://tools.ietf.org/html/rfc7517 +[请求身份验证]: ../latest/tasks/security/jwt-authentication +[JWT Debugger]: https://jwt.io/ +[JWK Creator]: https://russelldavies.github.io/jwk-creator/ diff --git a/site/content/zh/contributions/design/security-policy.md b/site/content/zh/contributions/design/security-policy.md new file mode 100644 index 00000000000..50bacc040a5 --- /dev/null +++ b/site/content/zh/contributions/design/security-policy.md @@ -0,0 +1,114 @@ +--- +title: "SecurityPolicy" +--- + +## 概述 {#overview} + +本设计文档引入了 `SecurityPolicy` API,允许系统管理员为进入网关的流量配置身份验证和鉴权策略。 + +## 目标 {#goals} + +* 添加 API 定义以保存用于配置进入网关的流量的身份验证和鉴权规则的设置。 + +## 非目标 {#non-goals} + +* 定义该 API 中的 API 配置字段。 + +## 实现 {#implementation} + +`SecurityPolicy` 是一个[策略附件][]类型的 API,可用于扩展 [Gateway API][] 来定义身份验证和鉴权规则。 + +### 示例 {#example} + +以下示例重点介绍了用户如何配置此 API。 + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: default +spec: + gatewayClassName: eg + listeners: + - name: https + protocol: HTTPS + port: 443 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: jwt-authn-policy + namespace: default +spec: + jwt: + providers: + - name: example + remoteJWKS: + uri: https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/jwt/jwks.json + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: eg + namespace: default +``` + +## 功能及 API 字段 {#features-api-fields} + +以下是此 API 中包含的功能列表: +* JWT 基础鉴权 +* OIDC 鉴权 +* 外部认证 +* Basic Auth +* API Key Auth +* CORS(跨域) + +## 设计决策 {#design-decisions} + +* 此 API 仅支持单个 `targetRef`,并且可以绑定到 `Gateway` 资源或 `HTTPRoute` 或 `GRPCRoute`。 +* 此 API 资源**必须**与 targetRef 资源属于同一命名空间 +* 只能有**一个**策略资源附加到特定的 targetRef,例如 `Gateway` 内的 `Listener`(部分) +* 如果策略针对某个资源但无法附加到该资源,则应使用 `Conflicted=True` 条件将该信息反映在“策略状态”字段中。 +* 如果多个策略针对同一资源,则最旧的资源(基于创建时间戳)将附加到网关侦听器,其他资源则不会。 +* 如果策略 A 具有包含 `sectionName` 的 `targetRef`,即它以 `Gateway` 内的特定侦听器为目标, + 并且策略 B 具有以同一整个 Gateway 为目标的 `targetRef`,则 + * 策略 A 将应用/附加到 `targetRef.SectionName` 中定义的特定监听器 + * 策略 B 将应用于 Gateway 内的其余侦听器。策略 B 将具有附加状态条件 `Overridden=True`。 +* 针对拥有具体范围的策略胜过针对缺少具体范围的策略。即,针对 xRoute(`HTTPRoute` 或 `GRPCRoute`)的策略会覆盖针对侦听器的策略, + 该侦听器是该路由的 parentRef,而侦听器又会覆盖针对侦听器/部分所属 Gateway 的策略。 + +## 替代方案 {#alternatives} + +* 项目可以无限期地等待这些配置参数成为 [Gateway API][] 的一部分。 + +[策略附件]: https://gateway-api.sigs.k8s.io/references/policy-attachment +[Gateway API]: https://gateway-api.sigs.k8s.io/ diff --git a/site/content/zh/latest/tasks/quickstart.md b/site/content/zh/latest/tasks/quickstart.md index aa94962e71a..8e32fdf5aa3 100644 --- a/site/content/zh/latest/tasks/quickstart.md +++ b/site/content/zh/latest/tasks/quickstart.md @@ -1,5 +1,5 @@ --- -title: "快速开始" +title: "快速入门" weight: 1 description: 只需几个简单的步骤即可开始使用 Envoy Gateway。 --- diff --git a/site/content/zh/latest/tasks/security/jwt-authentication.md b/site/content/zh/latest/tasks/security/jwt-authentication.md new file mode 100644 index 00000000000..a06399bc33f --- /dev/null +++ b/site/content/zh/latest/tasks/security/jwt-authentication.md @@ -0,0 +1,164 @@ +--- +title: "JWT 身份验证" +--- + +此任务提供有关配置 [JSON Web Token(JWT)][jwt]身份验证的说明。 +JWT 身份验证在将请求路由到后端服务之前检查传入请求是否具有有效的 JWT。 +目前,Envoy Gateway 仅支持通过 HTTP 标头验证 JWT,例如 `Authorization: Bearer `。 + +Envoy Gateway 引入了一个名为 [SecurityPolicy][SecurityPolicy] 的新 CRD,允许用户配置 JWT 身份验证。 +该实例化资源可以链接到 [Gateway][Gateway]、[HTTPRoute][HTTPRoute] 或 [GRPCRoute][GRPCRoute] 资源。 + +## 先决条件 {#prerequisites} + +按照[快速入门](../quickstart)中的步骤安装 Envoy Gateway 和示例清单。 +对于 GRPC - 请按照 [GRPC 路由](../traffic/grpc-routing)示例中的步骤操作。 +在继续之前,您应该能够使用 HTTP 或 GRPC 查询示例程序后端。 + +## 配置 {#configuration} + +通过创建 [SecurityPolicy][SecurityPolicy] 并将其附加到示例 HTTPRoute 或 GRPCRoute,允许使用具有有效 JWT 的请求。 + +### HTTPRoute {#httproute} + +```shell +kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/jwt/jwt.yaml +``` + +已创建两个 HTTPRoute,一个用于 `/foo`,另一个用于 `/bar`。 +已创建 SecurityPolicy 并以 HTTPRoute foo 为目标来验证对 `/foo` 的请求。 +HTTPRoute bar 不是 SecurityPolicy 的目标,并且将允许未经身份验证的请求发送到 `/bar`。 + +验证 HTTPRoute 配置和状态: + +```shell +kubectl get httproute/foo -o yaml +kubectl get httproute/bar -o yaml +``` + +SecurityPolicy 配置为 JWT 身份验证,并使用单个 [JSON Web Key Set(JWKS)][jwks]提供程序来对 JWT 进行身份验证。 + +验证 SecurityPolicy 配置: + +```shell +kubectl get securitypolicy/jwt-example -o yaml +``` + +### GRPCRoute {#grpcroute} + +```shell +kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/jwt/grpc-jwt.yaml +``` + +已创建 SecurityPolicy 并针对 GRPCRoute yages 来验证 `yages` 服务的所有请求。 + +验证 GRPCRoute 配置和状态: + +```shell +kubectl get grpcroute/yages -o yaml +``` + +SecurityPolicy 配置为 JWT 身份验证,并使用单个 [JSON Web Key Set(JWKS)][jwks]提供程序来对 JWT 进行身份验证。 + +验证 SecurityPolicy 配置: + +```shell +kubectl get securitypolicy/jwt-example -o yaml +``` + +## 测试 {#testing} + +确保设置了[快速入门](../../quickstart) 中的 `GATEWAY_HOST` 环境变量。如果没有,请按照快速入门说明设置变量。 + +```shell +echo $GATEWAY_HOST +``` + +### HTTPRoute {#httproute-1} + +验证在没有 JWT 的情况下对 `/foo` 的请求是否被拒绝: + +```shell +curl -sS -o /dev/null -H "Host: www.example.com" -w "%{http_code}\n" http://$GATEWAY_HOST/foo +``` + +应返回一个 `401` HTTP 响应码。 + +获取用于测试请求身份验证的 JWT: + +```shell +TOKEN=$(curl https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/jwt/test.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode - +``` + +**注意:**上述命令解码并返回令牌的有效内容。您可以将 `f2` 替换为 `f1` 来查看令牌的标头。 + +验证是否允许使用有效 JWT 向 `/foo` 发出请求: + +```shell +curl -sS -o /dev/null -H "Host: www.example.com" -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n" http://$GATEWAY_HOST/foo +``` + +应返回一个 `200` HTTP 响应码。 + +验证是否允许在**没有** JWT 的情况下向 `/bar` 发出请求: + +```shell +curl -sS -o /dev/null -H "Host: www.example.com" -w "%{http_code}\n" http://$GATEWAY_HOST/bar +``` + +### GRPCRoute {#grpcroute-1} + +验证是否在没有 JWT 的情况下拒绝对 `yages` 服务的请求: + +```shell +grpcurl -plaintext -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping +``` + +您应该看到以下响应: + +```shell +Error invoking method "yages.Echo/Ping": rpc error: code = Unauthenticated desc = failed to query for service descriptor "yages.Echo": Jwt is missing +``` + +获取用于测试请求身份验证的 JWT: + +```shell +TOKEN=$(curl https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/jwt/test.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode - +``` + +**注意:**上述命令解码并返回令牌的有效内容。您可以将 `f2` 替换为 `f1` 来查看令牌的标头。 + +验证是否允许使用有效 JWT 向 `yages` 服务发出请求: + +```shell +grpcurl -plaintext -H "authorization: Bearer $TOKEN" -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping +``` + +您应该看到以下响应: + +```shell +{ + "text": "pong" +} +``` + +## 清理 {#clean-up} + +按照[快速入门](../../quickstart) 中的步骤卸载 Envoy Gateway 和示例清单。 + +删除 SecurityPolicy: + +```shell +kubectl delete securitypolicy/jwt-example +``` + +## 后续步骤 {#next-steps} + +查看[开发者指南](../../../contributions/develop)参与该项目。 + +[SecurityPolicy]: ../../../contributions/design/security-policy +[jwt]: https://tools.ietf.org/html/rfc7519 +[jwks]: https://tools.ietf.org/html/rfc7517 +[Gateway]: https://gateway-api.sigs.k8s.io/api-types/gateway +[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute +[GRPCRoute]: https://gateway-api.sigs.k8s.io/api-types/grpcroute diff --git a/site/content/zh/latest/tasks/traffic/grpc-routing.md b/site/content/zh/latest/tasks/traffic/grpc-routing.md new file mode 100644 index 00000000000..6acffe4e32a --- /dev/null +++ b/site/content/zh/latest/tasks/traffic/grpc-routing.md @@ -0,0 +1,271 @@ +--- +title: "GRPC 路由" +--- + +[GRPCRoute][] 资源允许用户通过匹配 HTTP/2 流量并将其转发到后端 gRPC 服务器来配置 gRPC 路由。 +要了解有关 gRPC 路由的更多信息,请参阅[Gateway API 文档][]。 + +## 先决条件 {#prerequisites} + +按照[快速入门](../quickstart)中的步骤安装 Envoy Gateway 和示例清单。 +在继续之前,您应该能够使用 HTTP 查询示例程序后端。 + +## 安装 {#installation} + +安装 gRPC 路由示例资源: + +```shell +kubectl apply -f https://raw.githubusercontent.com/envoyproxy/gateway/latest/examples/kubernetes/grpc-routing.yaml +``` + +该清单安装 [GatewayClass][]、[Gateway][]、Deployment、Service 和 GRPCRoute 资源。 +GatewayClass 是集群范围的资源,表示可以被实例化的一类 Gateway。 + +**注意:**Envoy Gateway 默认被配置为使用 `controllerName: gateway.envoyproxy.io/gatewayclass-controller` 管理 GatewayClass。 + +## 验证 {#verification} + +检查 GatewayClass 的状态: + +```shell +kubectl get gc --selector=example=grpc-routing +``` + +状态应反映为 `Accepted=True`,表示 Envoy Gateway 正在管理 GatewayClass。 + +Gateway 代表基础设施的配置。创建 Gateway 时,[Envoy 代理][]基础设施由 Envoy Gateway 预配或配置。 +`gatewayClassName` 定义此 Gateway 使用的 GatewayClass 的名称。检查 Gateway 状态: + +```shell +kubectl get gateways --selector=example=grpc-routing +``` + +状态应反映为 `Ready=True`,表示 Envoy 代理基础设施已被配置。 +该状态还提供 Gateway 的地址。该地址稍后用于测试与代理后端服务的连接。 + +检查 GRPCRoute 的状态: + +```shell +kubectl get grpcroutes --selector=example=grpc-routing -o yaml +``` + +GRPCRoute 的状态应显示 `Accepted=True` 和引用示例 Gateway 的 `parentRef`。 +`example-route` 匹配 `grpc-example.com` 的任何流量并将其转发到 `yages` 服务。 + +## 测试配置 {#testing-the-configuration} + +在测试到 `yages` 后端的 GRPC 路由之前,请获取 Gateway 的地址。 + +```shell +export GATEWAY_HOST=$(kubectl get gateway/example-gateway -o jsonpath='{.status.addresses[0].value}') +``` + +使用 [grpcurl][] 命令测试到 `yages` 后端的 GRPC 路由。 + +```shell +grpcurl -plaintext -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping +``` + +您应该看到以下响应: + +```shell +{ + "text": "pong" +} +``` + +Envoy Gateway 还支持此配置的 [gRPC-Web][] 请求。下面的 `curl` 命令可用于通过 HTTP/2 发送 grpc-Web 请求。 +您应该收到与上一个命令相同的响应。 + +正文 `AAAAAAA=` 中的数据是 Ping RPC 接受的空消息(数据长度为 0)的 Base64 编码表示。 + +```shell +curl --http2-prior-knowledge -s ${GATEWAY_HOST}:80/yages.Echo/Ping -H 'Host: grpc-example.com' -H 'Content-Type: application/grpc-web-text' -H 'Accept: application/grpc-web-text' -XPOST -d'AAAAAAA=' | base64 -d +``` + +## GRPCRoute 匹配 {#grpcroute-match} + +`matches` 字段可用于根据 GRPC 的服务和/或方法名称将路由限制到一组特定的请求。 +它支持两种匹配类型:`Exact`(精准)和 `RegularExpression`(正则)。 + +### 精准 {#exact} + +`Exact`(精准)匹配是默认匹配类型。 + +以下示例显示如何根据 `grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo` 的服务和方法名称来匹配请求, +以及如何在我们的部署中匹配方法名称为 `Ping` 且与 `yages.Echo/Ping` 匹配的所有服务。 + +{{< tabpane text=true >}} +{{% tab header="通过标准输入应用" %}} + +```shell +cat <}} + +验证 GRPCRoute 状态: + +```shell +kubectl get grpcroutes --selector=example=grpc-routing -o yaml +``` + +使用 [grpcurl][] 命令测试到 `yages` 后端的 GRPC 路由。 + +```shell +grpcurl -plaintext -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping +``` + +### 正则 {#regularexpression} + +以下示例演示如何根据服务和方法名称将请求与匹配类型 `RegularExpression` 进行匹配。 +它与模式 `/.*.Echo/Pin.+` 匹配所有服务和方法,该模式与我们部署中的 `yages.Echo/Ping` 匹配。 + +{{< tabpane text=true >}} +{{% tab header="通过标准输入应用" %}} + +```shell +cat <}} + +检查 GRPCRoute 状态: + +```shell +kubectl get grpcroutes --selector=example=grpc-routing -o yaml +``` + +使用 [grpcurl][] 命令测试到 `yages` 后端的 GRPC 路由。 + +```shell +grpcurl -plaintext -authority=grpc-example.com ${GATEWAY_HOST}:80 yages.Echo/Ping +``` + +[GRPCRoute]: https://gateway-api.sigs.k8s.io/api-types/grpcroute/ +[Gateway API 文档]: https://gateway-api.sigs.k8s.io/ +[GatewayClass]: https://gateway-api.sigs.k8s.io/api-types/gatewayclass/ +[Gateway]: https://gateway-api.sigs.k8s.io/api-types/gateway/ +[Envoy 代理]: https://www.envoyproxy.io/ +[grpcurl]: https://github.com/fullstorydev/grpcurl +[gRPC-Web]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2