From 3cc000dc4287bdb524a2347b2f80fe9b865d4534 Mon Sep 17 00:00:00 2001 From: sheidkamp Date: Tue, 18 Feb 2025 15:38:01 -0500 Subject: [PATCH] prune tests --- test/e2e/README.md | 115 -- test/e2e/access_log_test.go | 364 ------- test/e2e/aggregate_listener_test.go | 1083 ------------------- test/e2e/aws_ec2_test.go | 262 ----- test/e2e/aws_test.go | 906 ---------------- test/e2e/buffer_test.go | 312 ------ test/e2e/connection_limit_test.go | 151 --- test/e2e/consul_test.go | 441 -------- test/e2e/cors_test.go | 363 ------- test/e2e/csrf_test.go | 404 ------- test/e2e/custom_auth_test.go | 354 ------- test/e2e/dns_test.go | 131 --- test/e2e/dynamic_forward_proxy_test.go | 161 --- test/e2e/fault_injection_test.go | 105 -- test/e2e/gateway_test.go | 1347 ------------------------ test/e2e/grpc_discovery_test.go | 196 ---- test/e2e/grpc_json_test.go | 301 ------ test/e2e/grpc_plugin_test.go | 204 ---- test/e2e/grpcweb_test.go | 184 ---- test/e2e/gzip_test.go | 113 -- test/e2e/happypath_test.go | 501 --------- test/e2e/header_validation_test.go | 102 -- test/e2e/headers_test.go | 750 ------------- test/e2e/health_checks_test.go | 384 ------- test/e2e/http_tunneling_test.go | 505 --------- test/e2e/hybrid_test.go | 677 ------------ test/e2e/loadbalancer_plugin_test.go | 154 --- test/e2e/local_ratelimit_test.go | 347 ------ test/e2e/proxy_protocol_test.go | 227 ---- test/e2e/ratelimit_test.go | 437 -------- test/e2e/route_transformation_test.go | 505 --------- test/e2e/staged_transformation_test.go | 1083 ------------------- test/e2e/tcp_stats_test.go | 225 ---- test/e2e/tls_ocsp_test.go | 220 ---- test/e2e/vault_aws_test.go | 233 ---- test/e2e/vault_test.go | 123 --- test/e2e/zipkin_test.go | 507 --------- 37 files changed, 14477 deletions(-) delete mode 100644 test/e2e/README.md delete mode 100644 test/e2e/access_log_test.go delete mode 100644 test/e2e/aggregate_listener_test.go delete mode 100644 test/e2e/aws_ec2_test.go delete mode 100644 test/e2e/aws_test.go delete mode 100644 test/e2e/buffer_test.go delete mode 100644 test/e2e/connection_limit_test.go delete mode 100644 test/e2e/consul_test.go delete mode 100644 test/e2e/cors_test.go delete mode 100644 test/e2e/csrf_test.go delete mode 100644 test/e2e/custom_auth_test.go delete mode 100644 test/e2e/dns_test.go delete mode 100644 test/e2e/dynamic_forward_proxy_test.go delete mode 100644 test/e2e/fault_injection_test.go delete mode 100644 test/e2e/gateway_test.go delete mode 100644 test/e2e/grpc_discovery_test.go delete mode 100644 test/e2e/grpc_json_test.go delete mode 100644 test/e2e/grpc_plugin_test.go delete mode 100644 test/e2e/grpcweb_test.go delete mode 100644 test/e2e/gzip_test.go delete mode 100644 test/e2e/happypath_test.go delete mode 100644 test/e2e/header_validation_test.go delete mode 100644 test/e2e/headers_test.go delete mode 100644 test/e2e/health_checks_test.go delete mode 100644 test/e2e/http_tunneling_test.go delete mode 100644 test/e2e/hybrid_test.go delete mode 100644 test/e2e/loadbalancer_plugin_test.go delete mode 100644 test/e2e/local_ratelimit_test.go delete mode 100644 test/e2e/proxy_protocol_test.go delete mode 100644 test/e2e/ratelimit_test.go delete mode 100644 test/e2e/route_transformation_test.go delete mode 100644 test/e2e/staged_transformation_test.go delete mode 100644 test/e2e/tcp_stats_test.go delete mode 100644 test/e2e/tls_ocsp_test.go delete mode 100644 test/e2e/vault_aws_test.go delete mode 100644 test/e2e/vault_test.go delete mode 100644 test/e2e/zipkin_test.go diff --git a/test/e2e/README.md b/test/e2e/README.md deleted file mode 100644 index 1f485c3fcf5..00000000000 --- a/test/e2e/README.md +++ /dev/null @@ -1,115 +0,0 @@ -# In Memory End-to-End tests -See the [developer e2e testing guide](/devel/testing/e2e-tests.md) for more information about the philosophy of these tests. - -*Note: All commands should be run from the root directory of the Gloo repository* - -- [Local Development](#local-development) - - [Setup](#setup) - - [Use the CI Install Script](#use-the-ci-install-script) - - [Verify Your Setup](#verify-your-setup) - - [Common Setup Errors](#common-setup-errors) - - [Run Tests](#run-tests) - - [Using Recently Published Image (Most Common)](#using-recently-published-image-most-common) - - [Using Previously Published Image](#using-previously-published-image) - - [Using Locally Built Image](#using-locally-built-image) - - [Running Tests in Parallel](#running-tests-in-parallel) - - [Debugging Tests](#debugging-tests) - - [Use WAIT_ON_FAIL](#use-wait_on_fail) - - [Use INVALID_TEST_REQS](#use-invalid_test_reqs) - - -## Local Development -### Setup -For these tests to run, we require that our gateway-proxy component be previously built as a docker image. - -If you have not made local changes to the component, you can rely on a previously published image and no setup is required. - -However, if you have made changes to the component, refer to the [Envoyinit README](https://github.com/solo-io/gloo/blob/main/projects/envoyinit) for build instructions. - -### Run Tests -The `test` make target runs ginkgo with a set of useful flags. See [run-tests](/devel/testing/run-tests.md) for more details about common techniques and common environment variables used when running tests. The following environment variables can also be configured for this target: - -| Name | Default | Description | -|-------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ENVOY_IMAGE_TAG | "" | The tag of the gloo-envoy-wrapper-docker image built during setup | -| SERVICE_LOG_LEVEL | "" | The log levels used for services. See "Controlling Log Verbosity of Services" below. | - -#### Controlling Log Verbosity of Services -Multiple services (Gloo, Envoy, Discovery) are executed in parallel to run these tests. By default, these services log at the `info` level. To change the log level of a service, set the `SERVICE_LOG_LEVEL` environment variable to a comma separated list of `service:level` pairs. - -Options for services are: -- gateway-proxy -- gloo -- uds -- fds - -Options for log levels are: -- debug -- info -- warn -- error - -For example, to set the log level of the Gloo service to `debug` and the Envoy service to `error`, you would set: - -```bash -SERVICE_LOG_LEVEL=gloo:debug,gateway-proxy:error TEST_PKG=./test/e2e/... make test -``` - -*If the same service has multiple log levels specified, we will log a warning and the last one defined will be used.* - -#### Controlling Log Verbosity of Ginkgo Runner -Ginkgo has 4 verbosity settings, whose details can be found in the [Ginkgo docs](https://onsi.github.io/ginkgo/#controlling-verbosity) - -To control these settings, you must pass the flags using the `GINKGO_USER_FLAGS` environment variable. - -For example, to set the Ginkgo runner to `very verbose` mode, you would set: -```bash -GINKGO_USER_FLAGS=-vv TEST_PKG=./test/e2e/... make test -``` - -#### Using Recently Published Image (Most Common) -This is the most common pattern. If you did not make changes to the `gateway-proxy` component, and do not specify an `ENVOY_IMAGE_TAG` our tests will identify the most recently published image (for your LTS branch) and use that version. - -```bash -TEST_PKG=./test/e2e/... make test -``` - -#### Using Previously Published Image -If you want to specify a particular version that was previously published, you can also do that by specifying the `ENVOY_IMAGE_TAG`. - -```bash -ENVOY_IMAGE_TAG=1.13.0 TEST_PKG=./test/e2e/... make test -``` - -#### Using Locally Built Image -If you have made changes to the component, you will have had to rebuild the image locally (see [setup tests](#setup)). After you rebuild the image, you need to supply the tag of that image when running the tests: - -```bash -ENVOY_IMAGE_TAG=0.0.1-local TEST_PKG=./test/e2e/... make test -``` - -#### Running Tests in Parallel -It is possible to run tests in parallel locally, however it is not recommended, because debugging failures becomes more difficult. If you do want to run tests in parallel, you can do so by passing in the relevant `GINKGO_USER_FLAGS` values: -```bash -GINKGO_USER_FLAGS=-procs=4 TEST_PKG=./test/e2e/... make test -``` - -*Note: When using Docker to run Envoy, we have seen occasional failures: `Error response from daemon: dial unix docker.raw.sock: connect: connection refused`* - - -### Debugging Tests -See [debugging tests](/devel/testing/run-tests.md) for more details about common techniques for debugging tests. -#### Use WAIT_ON_FAIL -When Ginkgo encounters a [test failure](https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-handles-failure) it will attempt to clean up relevant resources, which includes stopping the running instance of Envoy, preventing the developer from inspecting the state of the Envoy instance for useful clues. - -To avoid this clean up, run the test(s) with `WAIT_ON_FAIL=1`. When the test fails, it will halt execution, allowing you to inspect the state of the Envoy instance. - -Once halted, use `docker ps` to determine the admin port for the Envoy instance, and follow the recommendations for [debugging Envoy](https://github.com/solo-io/gloo/tree/main/projects/envoyinit#debug), specifically the parts around interacting with the Administration interface. - -#### Use INVALID_TEST_REQS -Certain tests require environmental conditions to be true for them to succeed. For example, there are tests that only run on Linux machines. - -By setting `INVALID_TEST_REQS=skip`, you can run all tests locally, and any tests which will not run in your local environment will be skipped. The default behavior is that they fail. - -### Notes -### AWS Tests -To run AWS tests locally, see our [AWS testing guide](/devel/testing/aws-tests.md). diff --git a/test/e2e/access_log_test.go b/test/e2e/access_log_test.go deleted file mode 100644 index b2c163b73ce..00000000000 --- a/test/e2e/access_log_test.go +++ /dev/null @@ -1,364 +0,0 @@ -package e2e_test - -import ( - "context" - "net/http" - "time" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - envoy_data_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - "github.com/solo-io/gloo/test/e2e" - - envoyals "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" - structpb "github.com/golang/protobuf/ptypes/struct" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/solo-io/gloo/projects/accesslogger/pkg/loggingservice" - "github.com/solo-io/gloo/projects/accesslogger/pkg/runner" - gwdefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - - gloo_envoy_v3 "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/core/v3" - - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/als" - alsplugin "github.com/solo-io/gloo/projects/gloo/pkg/plugins/als" - "github.com/solo-io/gloo/projects/gloo/pkg/translator" -) - -var _ = Describe("Access Log", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - // This mutation must happen after the testContext.BeforeEach() becuase that - // is where our VirtualService is constructed. - vs := testContext.ResourcesToCreate().VirtualServices[0] - routeOptions := &gloov1.RouteOptions{ - EnvoyMetadata: map[string]*structpb.Struct{ - "foo-namespace": { - Fields: map[string]*structpb.Value{ - "bar-metadata": { - Kind: &structpb.Value_StringValue{ - StringValue: "greetings", - }, - }}, - }, - }} - vs.GetVirtualHost().GetRoutes()[0].Options = routeOptions - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Grpc", func() { - - var ( - msgChan <-chan *envoy_data_accesslog_v3.HTTPAccessLogEntry - ) - - BeforeEach(func() { - msgChan = runAccessLog(testContext.Ctx(), testContext.EnvoyInstance().AccessLogPort) - - gw := gwdefaults.DefaultGateway(writeNamespace) - gw.Options = &gloov1.ListenerOptions{ - AccessLoggingService: &als.AccessLoggingService{ - AccessLog: []*als.AccessLog{ - { - OutputDestination: &als.AccessLog_GrpcService{ - GrpcService: &als.GrpcService{ - LogName: "test-log", - ServiceRef: &als.GrpcService_StaticClusterName{ - StaticClusterName: alsplugin.ClusterName, - }, - }, - }, - }, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("can stream access logs", func() { - requestBuilder := testContext.GetHttpRequestBuilder() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - - var entry *envoy_data_accesslog_v3.HTTPAccessLogEntry - g.Eventually(msgChan, 2*time.Second).Should(Receive(&entry)) - g.Expect(entry.CommonProperties.UpstreamCluster).To(Equal(translator.UpstreamToClusterName(testContext.TestUpstream().Upstream.Metadata.Ref()))) - }, time.Second*21, time.Second*2).Should(Succeed()) - }) - - }) - - Context("File", func() { - var gw *v1.Gateway - Context("String Format", func() { - BeforeEach(func() { - gw = gwdefaults.DefaultGateway(writeNamespace) - gw.Options = &gloov1.ListenerOptions{ - AccessLoggingService: &als.AccessLoggingService{ - AccessLog: []*als.AccessLog{ - { - OutputDestination: &als.AccessLog_FileSink{ - FileSink: &als.FileSink{ - Path: "/dev/stdout", - OutputFormat: &als.FileSink_StringFormat{ - StringFormat: "", - }, - }, - }, - }, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - It("can create string access logs", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("1"). - WithQuery("foo=bar"). - WithPostMethod() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - - logs, err := testContext.EnvoyInstance().Logs() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(logs).To(ContainSubstring(`"POST /1?foo=bar HTTP/1.1" 200`)) - }, time.Second*30, time.Second/2).Should(Succeed()) - }) - Context("Formatter extensions", func() { - BeforeEach(func() { - gw.GetOptions().GetAccessLoggingService().GetAccessLog()[0].GetFileSink().OutputFormat = &als.FileSink_StringFormat{ - StringFormat: "req: %REQ(:PATH)%\n" + - "req_without_query: %REQ_WITHOUT_QUERY(:PATH)%\n" + - "metadata: %METADATA(ROUTE:foo-namespace)%\n", - } - }) - It("can create formatted string access logs", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("1"). - WithQuery("sensitive=data&needs=removed"). - WithPostMethod() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - - logs, err := testContext.EnvoyInstance().Logs() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(logs).To(ContainSubstring(`req: /1?sensitive=data&needs=removed`)) - g.Expect(logs).To(ContainSubstring(`req_without_query: /1`)) - g.Expect(logs).To(ContainSubstring(`metadata: {"bar-metadata":"greetings"}`)) - }, time.Second*30, time.Second/2).Should(Succeed()) - }) - - }) - }) - - Context("Json Format", func() { - - BeforeEach(func() { - gw := gwdefaults.DefaultGateway(writeNamespace) - gw.Options = &gloov1.ListenerOptions{ - AccessLoggingService: &als.AccessLoggingService{ - AccessLog: []*als.AccessLog{ - { - OutputDestination: &als.AccessLog_FileSink{ - FileSink: &als.FileSink{ - Path: "/dev/stdout", - OutputFormat: &als.FileSink_JsonFormat{ - JsonFormat: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "protocol": { - Kind: &structpb.Value_StringValue{ - StringValue: "%PROTOCOL%", - }, - }, - "method": { - Kind: &structpb.Value_StringValue{ - StringValue: "%REQ(:METHOD)%", - }, - }, - "path": { - Kind: &structpb.Value_StringValue{ - StringValue: "%REQ(:PATH)%", - }, - }, - "path_without_query": { - Kind: &structpb.Value_StringValue{ - StringValue: "%REQ_WITHOUT_QUERY(:PATH)%", - }, - }, - "route_md": { - Kind: &structpb.Value_StringValue{ - StringValue: "%METADATA(ROUTE:foo-namespace)%", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - It("can create json access logs", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("1?foo=bar"). - WithPostMethod() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - - logs, err := testContext.EnvoyInstance().Logs() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(logs).To(ContainSubstring(`"method":"POST"`)) - g.Expect(logs).To(ContainSubstring(`"path":"/1?foo=bar"`)) - g.Expect(logs).To(ContainSubstring(`"path_without_query":"/1"`)) - g.Expect(logs).To(ContainSubstring(`"protocol":"HTTP/1.1"`)) - g.Expect(logs).To(ContainSubstring(`"route_md":{"bar-metadata":"greetings"}`)) - }, time.Second*30, time.Second/2).Should(Succeed()) - }) - }) - }) - - Context("Test Filters", func() { - // The output format doesn't (or at least shouldn't) matter for the filter tests, except in how we examine the access logs - // We'll use the string output because it's easiest to match against - BeforeEach(func() { - gw := gwdefaults.DefaultGateway(writeNamespace) - filter := &als.AccessLogFilter{ - FilterSpecifier: &als.AccessLogFilter_StatusCodeFilter{ - StatusCodeFilter: &als.StatusCodeFilter{ - Comparison: &als.ComparisonFilter{ - Op: als.ComparisonFilter_EQ, - Value: &gloo_envoy_v3.RuntimeUInt32{ - DefaultValue: 404, - RuntimeKey: "404", - }, - }, - }, - }, - } - - gw.Options = &gloov1.ListenerOptions{ - AccessLoggingService: &als.AccessLoggingService{ - AccessLog: []*als.AccessLog{ - { - OutputDestination: &als.AccessLog_FileSink{ - FileSink: &als.FileSink{ - Path: "/dev/stdout", - OutputFormat: &als.FileSink_StringFormat{ - StringFormat: "", - }, - }, - }, - Filter: filter, - }, - }, - }, - } - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("Can filter by status code", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("1"). - WithPostMethod() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - - logs, err := testContext.EnvoyInstance().Logs() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(logs).To(Not(ContainSubstring(`"POST /1 HTTP/1.1" 200`))) - }, time.Second*30, time.Second/2).Should(Succeed()) - - badHostRequestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("BAD/HOST"). - WithPostMethod(). - WithHost("") // We can get a 404 by not setting the Host header. - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(badHostRequestBuilder.Build())).Should(matchers.HaveStatusCode(http.StatusNotFound)) - - logs, err := testContext.EnvoyInstance().Logs() - g.Expect(err).To(Not(HaveOccurred())) - g.Expect(logs).To(Not(ContainSubstring(`"POST /1 HTTP/1.1" 200`))) - g.Expect(logs).To(ContainSubstring(`"POST /BAD/HOST HTTP/1.1" 404`)) - - }, time.Second*30, time.Second/2).Should(Succeed()) - }) - }) - -}) - -func runAccessLog(ctx context.Context, accessLogPort uint32) <-chan *envoy_data_accesslog_v3.HTTPAccessLogEntry { - msgChan := make(chan *envoy_data_accesslog_v3.HTTPAccessLogEntry, 10) - - opts := loggingservice.Options{ - Ordered: true, - Callbacks: loggingservice.AlsCallbackList{ - func(ctx context.Context, message *envoyals.StreamAccessLogsMessage) error { - defer GinkgoRecover() - httpLogs := message.GetHttpLogs() - Expect(httpLogs).NotTo(BeNil()) - for _, v := range httpLogs.LogEntry { - select { - case msgChan <- v: - return nil - case <-time.After(time.Second): - Fail("unable to send log message on channel") - } - } - return nil - }, - }, - Ctx: ctx, - } - - service := loggingservice.NewServer(opts) - - settings := runner.Settings{ - DebugPort: 0, - ServerPort: int(accessLogPort), - ServiceName: "AccessLog", - } - - go func(testctx context.Context) { - defer GinkgoRecover() - err := runner.RunWithSettings(testctx, service, settings) - if testctx.Err() == nil { - Expect(err).NotTo(HaveOccurred()) - } - }(ctx) - return msgChan -} diff --git a/test/e2e/aggregate_listener_test.go b/test/e2e/aggregate_listener_test.go deleted file mode 100644 index fdf870269cc..00000000000 --- a/test/e2e/aggregate_listener_test.go +++ /dev/null @@ -1,1083 +0,0 @@ -package e2e_test - -import ( - "net/http" - - "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/e2e" - - "github.com/golang/protobuf/ptypes/duration" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/selectors" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -var _ = Describe("Aggregate Listener", func() { - - // An AggregateListener is a type of Listener supported on a Proxy - // Proxies only contain this type of Listener by configuring the - // IsolateVirtualHostsBySslConfig property in the Settings CR to true - // These tests generally perform the following with and without this setting: - // 1. Produce Gateways and VirtualServices - // 2. Convert those resources into a Proxy - // 3. Configure an instance of Envoy with that Proxy configuration - // 4. Confirm the routing behavior - - var ( - isolateVirtualHostsBySslConfig bool - - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.SetRunSettings(&gloov1.Settings{ - Gateway: &gloov1.GatewayOptions{ - EnableGatewayController: &wrappers.BoolValue{ - Value: true, - }, - IsolateVirtualHostsBySslConfig: &wrappers.BoolValue{ - Value: isolateVirtualHostsBySslConfig, - }, - }, - }) - - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Insecure HttpGateway", func() { - - BeforeEach(func() { - vsBuilder := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace) - - vsEast := vsBuilder.Clone(). - WithName("vs-east"). - WithDomain("east.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/east"). - Build() - - vsWest := vsBuilder.Clone(). - WithName("vs-west"). - WithDomain("west.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/west"). - Build() - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsEast, vsWest, - } - }) - - Context("IsolateVirtualHostsBySslConfig = false", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = false - }) - - It("produces a Proxy with a single HttpListener", func() { - proxy, err := testContext.ReadDefaultProxy() - Expect(err).NotTo(HaveOccurred()) - - Expect(proxy.GetListeners()).To(HaveLen(1)) - Expect(proxy.GetListeners()[0].GetHttpListener()).NotTo(BeNil()) - }) - - DescribeTable("routes requests", - func(host, path string, statusCode int) { - httpRequestBuilder := testContext.GetHttpRequestBuilder(). - WithHost(host). - WithPath(path) - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - Entry("east host", - "east.com", - "east/1", - http.StatusOK), - Entry("west host", - "west.com", - "west/1", - http.StatusOK), - ) - }) - - Context("IsolateVirtualHostsBySslConfig = true", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = true - }) - - It("produces a Proxy with a single AggregateListener", func() { - proxy, err := testContext.ReadDefaultProxy() - Expect(err).NotTo(HaveOccurred()) - - Expect(proxy.GetListeners()).To(HaveLen(1)) - Expect(proxy.GetListeners()[0].GetAggregateListener()).NotTo(BeNil()) - }) - - DescribeTable("routes requests", - func(host, path string, statusCode int) { - httpRequestBuilder := testContext.GetHttpRequestBuilder(). - WithHost(host). - WithPath(path) - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - Entry("east host", - "east.com", - "east/1", - http.StatusOK), - Entry("west host", - "west.com", - "west/1", - http.StatusOK), - ) - }) - - }) - - Context("Secure HttpGateway", func() { - - var ( - eastCert, eastPK = gloohelpers.GetCerts(gloohelpers.Params{ - Hosts: "east.com", - IsCA: false, - }) - westCert, westPK = gloohelpers.GetCerts(gloohelpers.Params{ - Hosts: "west.com,northwest.com,southwest.com", - IsCA: false, - }) - ) - - BeforeEach(func() { - vsBuilder := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace) - - eastTLSSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "east-tls-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: eastCert, - PrivateKey: eastPK, - }, - }, - } - westTLSSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "west-tls-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: westCert, - PrivateKey: westPK, - }, - }, - } - - vsEast := vsBuilder.Clone(). - WithName("vs-east"). - WithDomain("east.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/east"). - WithSslConfig(&ssl.SslConfig{ - SniDomains: []string{"east.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: eastTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - vsNorthWest := vsBuilder.Clone(). - WithName("vs-northwest"). - WithDomain("northwest.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/northwest"). - WithSslConfig(&ssl.SslConfig{ - OneWayTls: &wrappers.BoolValue{ - Value: false, - }, - SniDomains: []string{"northwest.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: westTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - vsSouthWest := vsBuilder.Clone(). - WithName("vs-southwest"). - WithDomain("southwest.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/southwest"). - WithSslConfig(&ssl.SslConfig{ - OneWayTls: &wrappers.BoolValue{ - Value: false, - }, - SniDomains: []string{"southwest.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: westTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gatewaydefaults.DefaultSslGateway(writeNamespace), - } - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsEast, vsNorthWest, vsSouthWest, - } - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - eastTLSSecret, westTLSSecret, - } - }) - - Context("IsolateVirtualHostsBySslConfig = false", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = false - }) - - It("produces a Proxy with a single HttpListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetHttpListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(serverName, host, path, cert string, statusCode int) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(cert). - WithTLSServerName(serverName). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder(). - WithHost(host). - WithPath(path) - - Eventually(func(g Gomega) { - g.Expect(httpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - // This test demonstrates the flaw with HttpListeners: - // The West VirtualServices should only be exposing routes if the westCert is provided, - // but in this test we can successfully execute requests against the west routes, - // by providing an east certificate. - // - // This is due to the fact that an HttpListener creates an aggregate set of RouteConfiguration - // and then produces duplicate FilterChains, based on all available SslConfig's from VirtualServices - Entry("east host with east cert", - "east.com", - "east.com", - "east/1", - eastCert, - http.StatusOK), - Entry("northwest host with east cert", - "east.com", - "northwest.com", - "northwest/1", - eastCert, - http.StatusOK), - Entry("southwest host with east cert", - "east.com", - "southwest.com", - "southwest/1", - eastCert, - http.StatusOK), - ) - - }) - - Context("IsolateVirtualHostsBySslConfig = true", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = true - }) - - It("produces a Proxy with a single AggregateListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetAggregateListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(serverName, host, path, cert string, statusCode int) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(cert). - WithTLSServerName(serverName). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder(). - WithHost(host). - WithPath(path) - - Eventually(func(g Gomega) { - g.Expect(httpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - // This test demonstrates the solution with AggregateListeners: - // The West VirtualService is no longer routable with the eastCert. - Entry("east host with east cert", - "east.com", - "east.com", - "east/1", - eastCert, - http.StatusOK), - Entry("northwest host with east cert", - "east.com", - "northwest.com", - "northwest/1", - eastCert, - http.StatusNotFound), - Entry("southwest host with east cert", - "east.com", - "southwest.com", - "southwest/1", - eastCert, - http.StatusNotFound), - Entry("northwest host with west cert", - "northwest.com", - "northwest.com", - "northwest/1", - westCert, - http.StatusOK), - Entry("southwest host with west cert", - "southwest.com", - "southwest.com", - "southwest/1", - westCert, - http.StatusOK), - ) - }) - - }) - - Context("Insecure HybridGateway (Matched)", func() { - - BeforeEach(func() { - vsBuilder := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace) - - vsEast := vsBuilder.Clone(). - WithName("vs-east"). - WithDomain("east.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/east"). - Build() - - vsWest := vsBuilder.Clone(). - WithName("vs-west"). - WithDomain("west.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/west"). - Build() - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gatewaydefaults.DefaultHybridGateway(writeNamespace), - } - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsEast, vsWest, - } - }) - - Context("IsolateVirtualHostsBySslConfig = false", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = false - }) - - It("produces a Proxy with a single HybridListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetHybridListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(host, path string, statusCode int) { - httpRequestBuilder := testContext.GetHttpRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - Entry("east host", - "east.com", - "east/1", - http.StatusOK), - Entry("west host", - "west.com", - "west/1", - http.StatusOK), - ) - - }) - - Context("IsolateVirtualHostsBySslConfig = true", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = true - }) - - It("produces a Proxy with a single AggregateListener", func() { - proxy, err := testContext.ReadDefaultProxy() - Expect(err).NotTo(HaveOccurred()) - - Expect(proxy.GetListeners()).To(HaveLen(1)) - Expect(proxy.GetListeners()[0].GetAggregateListener()).NotTo(BeNil()) - }) - - DescribeTable("routes requests", - func(host, path string, statusCode int) { - httpRequestBuilder := testContext.GetHttpRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - Entry("east host", - "east.com", - "east/1", - http.StatusOK), - Entry("west host", - "west.com", - "west/1", - http.StatusOK), - ) - - }) - - }) - - Context("Secure HybridGateway (Matched)", func() { - - var ( - eastCert, eastPK = gloohelpers.GetCerts(gloohelpers.Params{ - Hosts: "east.com", - IsCA: false, - }) - westCert, westPK = gloohelpers.GetCerts(gloohelpers.Params{ - Hosts: "west.com,northwest.com,southwest.com", - IsCA: false, - }) - ) - - BeforeEach(func() { - vsBuilder := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace) - - eastTLSSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "east-tls-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: eastCert, - PrivateKey: eastPK, - }, - }, - } - westTLSSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "west-tls-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: westCert, - PrivateKey: westPK, - }, - }, - } - - vsEast := vsBuilder.Clone(). - WithName("vs-east"). - WithDomain("east.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/east"). - WithSslConfig(&ssl.SslConfig{ - SniDomains: []string{"east.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: eastTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - vsNorthWest := vsBuilder.Clone(). - WithName("vs-northwest"). - WithDomain("northwest.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/northwest"). - WithSslConfig(&ssl.SslConfig{ - OneWayTls: &wrappers.BoolValue{ - Value: false, - }, - SniDomains: []string{"northwest.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: westTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - vsSouthWest := vsBuilder.Clone(). - WithName("vs-southwest"). - WithDomain("southwest.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/southwest"). - WithSslConfig(&ssl.SslConfig{ - OneWayTls: &wrappers.BoolValue{ - Value: false, - }, - SniDomains: []string{"southwest.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: westTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gatewaydefaults.DefaultHybridSslGateway(writeNamespace), - } - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsEast, vsNorthWest, vsSouthWest, - } - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - eastTLSSecret, westTLSSecret, - } - }) - - Context("IsolateVirtualHostsBySslConfig = false", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = false - }) - - It("produces a Proxy with a single HybridListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetHybridListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(serverName, host, path, cert string, statusCode int) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(cert). - WithTLSServerName(serverName). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(httpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - // This test demonstrates the flaw with HybridListeners: - // The West VirtualService should only be exposing routes if the westCert is provided, - // but in this test we can successfully execute requests against the west routes, - // by providing an east certificate. - // - // This is due to the fact that a HybridListener creates an aggregate set of RouteConfiguration - // and then produces duplicate FilterChains, based on all available SslConfig's from VirtualServices - Entry("east host with east cert", - "east.com", - "east.com", - "east/1", - eastCert, - http.StatusOK), - Entry("northwest host with east cert", - "east.com", - "northwest.com", - "northwest/1", - eastCert, - http.StatusOK), - Entry("southwest host with east cert", - "east.com", - "southwest.com", - "southwest/1", - eastCert, - http.StatusOK), - ) - - }) - - Context("IsolateVirtualHostsBySslConfig = true", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = true - }) - - It("produces a Proxy with a single AggregateListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetAggregateListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(serverName, host, path, cert string, statusCode int) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(cert). - WithTLSServerName(serverName). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(httpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - // This test demonstrates the solution with AggregateListeners: - // The West VirtualService is no longer routable with the eastCert. - Entry("east host with east cert", - "east.com", - "east.com", - "east/1", - eastCert, - http.StatusOK), - Entry("northwest host with east cert", - "east.com", - "northwest.com", - "northwest/1", - eastCert, - http.StatusNotFound), - Entry("southwest host with east cert", - "east.com", - "southwest.com", - "southwest/1", - eastCert, - http.StatusNotFound), - Entry("northwest host with west cert", - "northwest.com", - "northwest.com", - "northwest/1", - westCert, - http.StatusOK), - Entry("southwest host with west cert", - "southwest.com", - "southwest.com", - "southwest/1", - westCert, - http.StatusOK), - ) - - }) - - }) - - Context("Insecure HybridGateway (Delegated)", func() { - - BeforeEach(func() { - vsBuilder := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace) - - vsEast := vsBuilder.Clone(). - WithName("vs-east"). - WithDomain("east.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/east"). - Build() - - vsWest := vsBuilder.Clone(). - WithName("vs-west"). - WithDomain("west.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/west"). - Build() - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - buildInsecureHybridGatewayWithDelegation(writeNamespace), - } - testContext.ResourcesToCreate().HttpGateways = v1.MatchableHttpGatewayList{ - gatewaydefaults.DefaultMatchableHttpGateway(writeNamespace, nil), - } - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsEast, vsWest, - } - }) - - Context("IsolateVirtualHostsBySslConfig = false", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = false - }) - - It("produces a Proxy with a single HybridListener", func() { - proxy, err := testContext.ReadDefaultProxy() - Expect(err).NotTo(HaveOccurred()) - - Expect(proxy.GetListeners()).To(HaveLen(1)) - Expect(proxy.GetListeners()[0].GetHybridListener()).NotTo(BeNil()) - }) - - DescribeTable("routes requests", - func(host, path string, statusCode int) { - httpRequestBuilder := testContext.GetHttpRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - Entry("east host", - "east.com", - "east/1", - http.StatusOK), - Entry("west host", - "west.com", - "west/1", - http.StatusOK), - ) - - }) - - Context("IsolateVirtualHostsBySslConfig = true", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = true - }) - - It("produces a Proxy with a single AggregateListener", func() { - proxy, err := testContext.ReadDefaultProxy() - Expect(err).NotTo(HaveOccurred()) - - Expect(proxy.GetListeners()).To(HaveLen(1)) - Expect(proxy.GetListeners()[0].GetAggregateListener()).NotTo(BeNil()) - }) - - DescribeTable("routes requests", - func(host, path string, statusCode int) { - httpRequestBuilder := testContext.GetHttpRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - Entry("east host", - "east.com", - "east/1", - http.StatusOK), - Entry("west host", - "west.com", - "west/1", - http.StatusOK), - ) - - }) - - }) - - Context("Secure HybridGateway (Delegated)", func() { - - var ( - eastCert, eastPK = gloohelpers.GetCerts(gloohelpers.Params{ - Hosts: "east.com", - IsCA: false, - }) - westCert, westPK = gloohelpers.GetCerts(gloohelpers.Params{ - Hosts: "west.com,northwest.com,southwest.com", - IsCA: false, - }) - ) - - BeforeEach(func() { - vsBuilder := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace) - - eastTLSSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "east-tls-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: eastCert, - PrivateKey: eastPK, - }, - }, - } - westTLSSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "west-tls-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: westCert, - PrivateKey: westPK, - }, - }, - } - - vsEast := vsBuilder.Clone(). - WithName("vs-east"). - WithDomain("east.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/east"). - WithSslConfig(&ssl.SslConfig{ - SniDomains: []string{"east.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: eastTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - vsNorthWest := vsBuilder.Clone(). - WithName("vs-northwest"). - WithDomain("northwest.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/northwest"). - WithSslConfig(&ssl.SslConfig{ - OneWayTls: &wrappers.BoolValue{ - Value: false, - }, - SniDomains: []string{"northwest.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: westTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - vsSouthWest := vsBuilder.Clone(). - WithName("vs-southwest"). - WithDomain("southwest.com"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/southwest"). - WithSslConfig(&ssl.SslConfig{ - OneWayTls: &wrappers.BoolValue{ - Value: false, - }, - SniDomains: []string{"southwest.com"}, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: westTLSSecret.GetMetadata().Ref(), - }, - }). - Build() - - nonEmptySslConfig := &ssl.SslConfig{ - TransportSocketConnectTimeout: &duration.Duration{ - Seconds: 30, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - buildHybridGatewayWithDelegation(writeNamespace, nonEmptySslConfig), - } - testContext.ResourcesToCreate().HttpGateways = v1.MatchableHttpGatewayList{ - gatewaydefaults.DefaultMatchableHttpGateway(writeNamespace, nonEmptySslConfig), - } - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsEast, vsNorthWest, vsSouthWest, - } - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - eastTLSSecret, westTLSSecret, - } - }) - - Context("IsolateVirtualHostsBySslConfig = false", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = false - }) - - It("produces a Proxy with a single HybridListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetHybridListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(serverName, host, path, cert string, statusCode int) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(cert). - WithTLSServerName(serverName). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(httpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - // This test demonstrates the flaw with HybridListeners: - // The West VirtualService should only be exposing routes if the westCert is provided, - // but in this test we can successfully execute requests against the west routes, - // by providing an east certificate. - // - // This is due to the fact that a HybridListener creates an aggregate set of RouteConfiguration - // and then produces duplicate FilterChains, based on all available SslConfig's from VirtualServices - Entry("east host with east cert", - "east.com", - "east.com", - "east/1", - eastCert, - http.StatusOK), - Entry("northwest host with east cert", - "east.com", - "northwest.com", - "northwest/1", - eastCert, - http.StatusOK), - Entry("southwest host with east cert", - "east.com", - "southwest.com", - "southwest/1", - eastCert, - http.StatusOK), - ) - - }) - - Context("IsolateVirtualHostsBySslConfig = true", func() { - - BeforeEach(func() { - isolateVirtualHostsBySslConfig = true - }) - - It("produces a Proxy with a single AggregateListener", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetAggregateListener()).NotTo(BeNil()) - }).Should(Succeed()) - }) - - DescribeTable("routes requests", - func(serverName, host, path, cert string, statusCode int) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(cert). - WithTLSServerName(serverName). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder(). - WithHost(host). - WithPath(path). - WithPort(testContext.EnvoyInstance().HybridPort) - - Eventually(func(g Gomega) { - g.Expect(httpClient.Do(httpRequestBuilder.Build())).To(matchers.HaveStatusCode(statusCode)) - }, "10s", "1s").Should(Succeed()) - }, - // This test demonstrates the solution with AggregateListeners: - // The West VirtualService is no longer routable with the eastCert. - Entry("east host with east cert", - "east.com", - "east.com", - "east/1", - eastCert, - http.StatusOK), - Entry("northwest host with east cert", - "east.com", - "northwest.com", - "northwest/1", - eastCert, - http.StatusNotFound), - Entry("southwest host with east cert", - "east.com", - "southwest.com", - "southwest/1", - eastCert, - http.StatusNotFound), - Entry("northwest host with west cert", - "northwest.com", - "northwest.com", - "northwest/1", - westCert, - http.StatusOK), - Entry("southwest host with west cert", - "southwest.com", - "southwest.com", - "southwest/1", - westCert, - http.StatusOK), - ) - - }) - - }) - -}) - -func buildInsecureHybridGatewayWithDelegation(writeNamespace string) *v1.Gateway { - return buildHybridGatewayWithDelegation(writeNamespace, nil) -} - -func buildHybridGatewayWithDelegation(writeNamespace string, sslConfig *ssl.SslConfig) *v1.Gateway { - gw := gatewaydefaults.DefaultHybridGateway(writeNamespace) - gw.GatewayType = &v1.Gateway_HybridGateway{ - HybridGateway: &v1.HybridGateway{ - DelegatedHttpGateways: &v1.DelegatedHttpGateway{ - SslConfig: sslConfig, - SelectionType: &v1.DelegatedHttpGateway_Selector{ - Selector: &selectors.Selector{ - // select all MatchableHttpGateways in the same namespace - Namespaces: []string{writeNamespace}, - }, - }, - }, - }, - } - return gw -} diff --git a/test/e2e/aws_ec2_test.go b/test/e2e/aws_ec2_test.go deleted file mode 100644 index bed48210598..00000000000 --- a/test/e2e/aws_ec2_test.go +++ /dev/null @@ -1,262 +0,0 @@ -package e2e_test - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "strings" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - "github.com/aws/aws-sdk-go/aws/credentials" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/rotisserie/eris" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - glooec2 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/aws/ec2" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -/* -# Configure an EC2 instance for this test -- Do this if this test ever starts to fail because the EC2 instance that it tests against has become unavailable. - -- Provision an EC2 instance - - Use an "amazon linux" image - - Configure the security group to allow http traffic on port 80 - -- Tag your instance with the following tags - - svc: worldwide-hello - -- Set up your EC2 instance - - ssh into your instance - - download a demo app: an http response code echo app - - this app responds to requests with the corresponding response code - - ex: http:///?code=404 produces a `404` response - - make the app executable - - run it in the background - -```bash -wget https://mitch-solo-public.s3.amazonaws.com/echoapp2 -chmod +x echoapp2 -sudo ./echoapp2 --port 80 & -``` -- Note: other dummy webservers will work fine - you may just need to update the path of the request - - Currently, we call the /metrics path during our tests - -- Verify that you can reach the app - - `curl` the app, you should see a help menu for the app -```bash -curl http:/// -``` -*/ - -var _ = Describe("AWS EC2 Plugin utils test", func() { - if testutils.ShouldSkipTempDisabledTests() { - return - } - const ( - region = "us-east-1" - awsRoleArn = "AWS_ROLE_ARN" - ) - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - secret *gloov1.Secret - upstream *gloov1.Upstream - roleArn string - ) - - addCredentials := func() { - localAwsCredentials := credentials.NewSharedCredentials("", "") - v, err := localAwsCredentials.Get() - if err != nil { - Skip("no AWS creds available") - } - - // role arn format: "arn:aws:iam::[account_number]:role/[role_name]" - roleArn = os.Getenv(awsRoleArn) - if roleArn == "" { - Skip("no AWS role ARN available") - } - var opts clients.WriteOpts - - accessKey := v.AccessKeyID - secretKey := v.SecretAccessKey - - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: region, - }, - Kind: &gloov1.Secret_Aws{ - Aws: &gloov1.AwsSecret{ - AccessKey: accessKey, - SecretKey: secretKey, - }, - }, - } - - _, err = testClients.SecretClient.Write(secret, opts) - Expect(err).NotTo(HaveOccurred()) - - } - - addUpstream := func() { - secretRef := secret.Metadata.Ref() - upstream = &gloov1.Upstream{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: region, - }, - UpstreamType: &gloov1.Upstream_AwsEc2{ - AwsEc2: &glooec2.UpstreamSpec{ - Region: region, - SecretRef: secretRef, - RoleArn: roleArn, - Filters: []*glooec2.TagFilter{ - { - Spec: &glooec2.TagFilter_KvPair_{ - KvPair: &glooec2.TagFilter_KvPair{ - Key: "svc", - Value: "worldwide-hello", - }, - }, - }, - }, - PublicIp: true, - Port: 80, - }, - }, - } - - var opts clients.WriteOpts - _, err := testClients.UpstreamClient.Write(upstream, opts) - Expect(err).NotTo(HaveOccurred()) - } - - validateUrl := func(url, substring string) { - Eventually(func() (string, error) { - res, err := http.Get(url) - if err != nil { - return "", eris.Wrapf(err, "unable to call GET") - } - if res.StatusCode != http.StatusOK { - return "", eris.New(fmt.Sprintf("%v is not OK", res.StatusCode)) - } - - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return "", eris.Wrapf(err, "unable to read body") - } - - return string(body), nil - }, "10s", "1s").Should(ContainSubstring(substring)) - } - - validateEc2Endpoint := func(envoyPort uint32, substring string) { - // first make sure that the instance is ready (to avoid false negatives) - By("verifying instance is ready - if this failed, you may need to restart the EC2 instance") - // Stitch the url together to avoid bot spam - // The IP address corresponds to the public ip of an EC2 instance managed by Solo.io for the purpose of - // verifying that the EC2 upstream works as expected. - // The port is where the app listens for connections. The instance has been configured with an inbound traffic - // rule that allows port 80. - // TODO[test enhancement] - create an EC2 instance on demand (or auto-skip the test) if the expected instance is unavailable - // See notes in the header of this file for instructions on how to restore the instance - ec2Port := 80 - // This is an Elastic IP in us-east-1 and can be reassigned if the instance ever goes down - ec2Url := fmt.Sprintf("http://%v:%v/metrics", strings.Join([]string{"100", "24", "224", "6"}, "."), ec2Port) - validateUrl(ec2Url, substring) - - // do the actual verification - By("verifying Gloo has routed to the instance") - gatewayUrl := fmt.Sprintf("http://%v:%v/metrics", "localhost", envoyPort) - validateUrl(gatewayUrl, substring) - } - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - // NOTE: you need to configure EC2 instances before running this - It("be able to call upstream function", func() { - err := envoyInstance.RunWithRoleAndRestXds(envoy.DefaultProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - proxy := &gloov1.Proxy{ - Metadata: &core.Metadata{ - Name: "proxy", - Namespace: "default", - }, - Listeners: []*gloov1.Listener{{ - Name: "listener", - BindAddress: "::", - BindPort: envoyInstance.HttpPort, - ListenerType: &gloov1.Listener_HttpListener{ - HttpListener: &gloov1.HttpListener{ - VirtualHosts: []*gloov1.VirtualHost{{ - Name: "virt1", - Domains: []string{"*"}, - Routes: []*gloov1.Route{{ - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream.Metadata.Ref(), - }, - }, - }, - }, - }, - }}, - }}, - }, - }, - }}, - } - - var opts clients.WriteOpts - _, err = testClients.ProxyClient.Write(proxy, opts) - Expect(err).NotTo(HaveOccurred()) - - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.ProxyClient.Read(proxy.Metadata.Namespace, proxy.Metadata.Name, clients.ReadOpts{}) - }) - - validateEc2Endpoint(envoyInstance.HttpPort, "Counts") - }) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - runOptions := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableGateway: true, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, runOptions) - - addCredentials() - addUpstream() - }) -}) diff --git a/test/e2e/aws_test.go b/test/e2e/aws_test.go deleted file mode 100644 index 4a7eb718c1f..00000000000 --- a/test/e2e/aws_test.go +++ /dev/null @@ -1,906 +0,0 @@ -package e2e_test - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "io" - "net/http" - "net/url" - "os" - "time" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/services/envoy" - - errors "github.com/rotisserie/eris" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/sts" - "github.com/form3tech-oss/jwt-go" - aws2 "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/extensions/aws" - "github.com/solo-io/gloo/test/helpers" - "google.golang.org/protobuf/types/known/wrapperspb" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - "github.com/solo-io/gloo/test/services" - - gw1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gwdefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - aws_plugin "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/aws" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/hcm" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/transformation" - - "github.com/aws/aws-sdk-go/aws/credentials" -) - -var _ = Describe("AWS Lambda", func() { - const ( - defaultRegion = "us-east-1" - secondaryRegion = "us-east-2" - webIdentityTokenFile = "AWS_WEB_IDENTITY_TOKEN_FILE" - jwtPrivateKey = "JWT_PRIVATE_KEY" - awsRoleArn = "AWS_ROLE_ARN" - ) - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - secret *gloov1.Secret - upstream *gloov1.Upstream - runOptions *services.RunOptions - ) - - BeforeEach(func() { - testutils.ValidateRequirementsAndNotifyGinkgo(testutils.AwsCredentials()) - runOptions = &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableFds: false, - }, - } - }) - - setupEnvoy := func(justGloo bool) { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - runOptions.WhatToRun.DisableGateway = justGloo - testClients = services.RunGlooGatewayUdsFds(ctx, runOptions) - - err := helpers.WriteDefaultGateways(defaults.GlooSystem, testClients.GatewayClient) - Expect(err).NotTo(HaveOccurred(), "Should be able to write default gateways") - } - - type lambdaValidationParams struct { - offset int - envoyPort uint32 - requestBody string - expectedSubstrings []string - requestHeaders, expectedHeaders http.Header - exactExpectedHeaderKeys []string - requestUrl *url.URL - expectedStatus *int - } - validateLambda := func(params lambdaValidationParams) { - - body := []byte("\"solo.io\"") - if params.requestBody != "" { - body = []byte(params.requestBody) - } - headers := http.Header{"Content-Type": {"application/octet-stream"}} - if params.requestHeaders != nil { - headers = params.requestHeaders - } - u := &url.URL{Scheme: "http", Host: fmt.Sprintf("localhost:%d", params.envoyPort), Path: "/1", RawQuery: "param_a=value_1¶m_b=value_b"} - if params.requestUrl != nil { - u = params.requestUrl - } - expectedStatus := http.StatusOK - if params.expectedStatus != nil { - expectedStatus = *params.expectedStatus - } - - EventuallyWithOffset(params.offset, func(g Gomega) { - // send a request with a body - var buf bytes.Buffer - buf.Write(body) - - httpClient := &http.Client{ - Timeout: time.Minute * 5, - } - - req := http.Request{ - Method: http.MethodPost, - URL: u, - Header: headers, - Body: io.NopCloser(&buf), - } - - req.Header.Add("x-header-a", "header_value_1") - req.Header.Add("x-header-a", "header_value_2") - req.Header.Add("x-header-b", "header_value_b") - - res, err := httpClient.Do(&req) - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(res).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: expectedStatus, - Body: testmatchers.ContainSubstrings(params.expectedSubstrings), - Custom: And(testmatchers.ContainHeaders(params.expectedHeaders), testmatchers.ContainHeaderKeysExact(params.exactExpectedHeaderKeys)), - })) - - }, "30s", "1s").Should(Succeed()) - } - validateLambdaUppercase := func(envoyPort uint32) { - validateLambda(lambdaValidationParams{ - offset: 2, - envoyPort: envoyPort, - expectedSubstrings: []string{"SOLO.IO"}, - }) - } - - addUpstream := func() { - upstream = &gloov1.Upstream{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: defaultRegion, - }, - UpstreamType: &gloov1.Upstream_Aws{ - Aws: &aws_plugin.UpstreamSpec{ - Region: defaultRegion, - SecretRef: secret.Metadata.Ref(), - }, - }, - } - - var opts clients.WriteOpts - _, err := testClients.UpstreamClient.Write(upstream, opts) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func() []*aws_plugin.LambdaFunctionSpec { - us, err := testClients.UpstreamClient.Read( - upstream.GetMetadata().Namespace, - upstream.GetMetadata().Name, - clients.ReadOpts{}, - ) - if err != nil { - return nil - } - return us.GetAws().GetLambdaFunctions() - }, "2m", "1s").Should(ContainElement(&aws_plugin.LambdaFunctionSpec{ - LogicalName: "uppercase", - LambdaFunctionName: "uppercase", - Qualifier: "$LATEST", - })) - } - - addCrossAccountUpstream := func() { - upstream = &gloov1.Upstream{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: "cross-account-us", - }, - UpstreamType: &gloov1.Upstream_Aws{ - Aws: &aws_plugin.UpstreamSpec{ - Region: defaultRegion, - SecretRef: secret.Metadata.Ref(), - // this is a separate account ID from the one that all other lambda - // functions tested in this file are in - AwsAccountId: "986112284769", - LambdaFunctions: []*aws_plugin.LambdaFunctionSpec{ - { - LogicalName: "resource-based-cross-account-hello", - LambdaFunctionName: "resource-based-cross-account-hello", - }, - }, - }, - }, - } - - var opts clients.WriteOpts - _, err := testClients.UpstreamClient.Write(upstream, opts) - Expect(err).NotTo(HaveOccurred()) - - // wait for the upstream to be created - // Upstreams no longer report status if they have not been translated at all to avoid conflicting with - // other syncers that have translated them, so we can only detect that the objects exist here - helpers.EventuallyResourceExists(func() (resources.Resource, error) { - return testClients.UpstreamClient.Read(upstream.Metadata.Namespace, upstream.Metadata.Name, clients.ReadOpts{}) - }, "30s", "1s") - } - - createProxy := func(unwrapAsApiGateway, requestTransformation, responseTransformation bool, logicalName string) { - proxy := &gloov1.Proxy{ - Metadata: &core.Metadata{ - Name: "proxy", - Namespace: "default", - }, - Listeners: []*gloov1.Listener{{ - Name: "listener", - BindAddress: "::", - BindPort: envoyInstance.HttpPort, - ListenerType: &gloov1.Listener_HttpListener{ - HttpListener: &gloov1.HttpListener{ - VirtualHosts: []*gloov1.VirtualHost{{ - Name: "virt1", - Domains: []string{"*"}, - Routes: []*gloov1.Route{{ - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream.Metadata.Ref(), - }, - DestinationSpec: &gloov1.DestinationSpec{ - DestinationType: &gloov1.DestinationSpec_Aws{ - Aws: &aws_plugin.DestinationSpec{ - LogicalName: logicalName, - UnwrapAsApiGateway: unwrapAsApiGateway, - RequestTransformation: requestTransformation, - ResponseTransformation: responseTransformation, - }, - }, - }, - }, - }, - }, - }, - }}, - }, - }, - }, - }, - }}, - } - - var opts clients.WriteOpts - _, err := testClients.ProxyClient.Write(proxy, opts) - Expect(err).NotTo(HaveOccurred()) - - // wait for proxy to be accepted - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.ProxyClient.Read(proxy.Metadata.Namespace, proxy.Metadata.Name, clients.ReadOpts{}) - }, "30s", "1s") - - } - - testProxy := func() { - err := envoyInstance.RunWithRoleAndRestXds(envoy.DefaultProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(false, false, false, "uppercase") - validateLambdaUppercase(envoyInstance.HttpPort) - } - - testProxyWithResponseTransform := func() { - err := envoyInstance.RunWithRoleAndRestXds(envoy.DefaultProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(false, false, true, "contact-form") - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - expectedSubstrings: []string{``}, - }) - } - - testProxyWithRequestTransform := func() { - err := envoyInstance.RunWithRole(envoy.DefaultProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(false, true, false, "dumpContext") - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - expectedSubstrings: []string{`\"body\": \"\\\"solo.io\\\"\", \"headers\": `, - `\"queryString\": \"param_a=value_1¶m_b=value_b\"`, - `\"path\": \"/1\"`, - `\"httpMethod\": \"POST\"`}, - }) - } - - testProxyWithUnwrapAsApiGateway := func() { - err := envoyInstance.RunWithRole(envoy.DefaultProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(true, false, false, "echo") - expectedStatus := 201 - // need querystring, multivaluequerystring - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - requestBody: `{"headers":{"Content-Type":"application/test"}, "body":"solo.io", "multiValueHeaders":{"x-header":["value-1", "value-2"]}, "statusCode":201, "queryStringParameters":{"param_a":"value_2", "param_b":"value_b"}, "multiValueQueryStringParameters":{"param_a":["value_1", "value_2"]}}`, - expectedSubstrings: []string{"solo.io"}, - expectedHeaders: http.Header{"Content-Type": {"application/test"}, "X-Header": {"value-1,value-2"}}, - expectedStatus: &expectedStatus, - }) - } - - testProxyWithRequestAndResponseTransforms := func() { - err := envoyInstance.RunWithRole(envoy.DefaultProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(false, true, true, "dumpContext") - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - expectedSubstrings: []string{`"\"solo.io\""`}, - }) - } - - // this tests the case where a lambda returns non-string values in multiValueHeaders, a case which previously caused - // Envoy to return a 500 response - // lambda: https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions/non-string-headers-test - // expected response: - // 'body': json.dumps('test body'), - // 'multiValueHeaders': { - // 'foo': [ - // None, - // "bar", - // 123 - // ] - // } - testProxyWithUnwrapAsApiGatewayNonStringHeaderResponse := func() { - err := envoyInstance.RunWithRole(envoy.DefaultProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(true, false, false, "non-string-headers-test") - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - expectedSubstrings: []string{`"test body"`}, - expectedHeaders: http.Header{"Foo": []string{"null,bar,123"}}, - }) - } - - // this tests the case where a lambda returns malformed multiValueHeaders, a case which previously caused Envoy to - // return a 500 response - // we now expect a 200 response with no body and no headers - // the headers are malformed because multiValueHeaders is an object and not an array - // lambda: https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions/malformed-headers-test - // expected response: - // 'body': json.dumps('test body'), - // 'multiValueHeaders': { - // 'foo': { - // None, - // "bar", - // 123 - // } - // } - testProxyWithUnwrapAsApiGatewayMalformedHeaderResponse := func() { - err := envoyInstance.RunWithRole(envoy.DefaultProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(true, false, false, "malformed-headers-test") - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - expectedSubstrings: []string{"{}"}, - expectedHeaders: map[string][]string{}, - exactExpectedHeaderKeys: []string{"Date", "Server", "Content-Length"}, - }) - } - - testProxyWithCrossAccountLambda := func() { - err := envoyInstance.RunWithRole(envoy.DefaultProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - createProxy(false, false, false, "resource-based-cross-account-hello") - validateLambda(lambdaValidationParams{ - offset: 1, - envoyPort: envoyInstance.HttpPort, - expectedSubstrings: []string{`"\"Hello from Lambda!\""`}, - }) - } - - testLambdaWithVirtualService := func() { - err := envoyInstance.RunWithRoleAndRestXds("gloo-system~"+gwdefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - vs := &gw1.VirtualService{ - Metadata: &core.Metadata{ - Name: "app", - Namespace: "gloo-system", - }, - VirtualHost: &gw1.VirtualHost{ - Domains: []string{"*"}, - Routes: []*gw1.Route{{ - Action: &gw1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream.Metadata.Ref(), - }, - DestinationSpec: &gloov1.DestinationSpec{ - DestinationType: &gloov1.DestinationSpec_Aws{ - Aws: &aws_plugin.DestinationSpec{ - LogicalName: "uppercase", - }, - }, - }, - }, - }, - }, - }, - }}, - }, - } - - var opts clients.WriteOpts - _, err = testClients.VirtualServiceClient.Write(vs, opts) - Expect(err).NotTo(HaveOccurred()) - - validateLambdaUppercase(envoyInstance.HttpPort) - } - - testLambdaTransformations := func() { - // don't generate request id, so that the returned body is predictable (see the MatchJson below). - gateway, err := testClients.GatewayClient.Read(defaults.GlooSystem, gwdefaults.GatewayProxyName, clients.ReadOpts{}) - gateway.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - GenerateRequestId: wrapperspb.Bool(false), - }, - } - _, err = testClients.GatewayClient.Write(gateway, clients.WriteOpts{OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - - err = envoyInstance.RunWithRoleAndRestXds(defaults.GlooSystem+"~"+gwdefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - prepVs := func(addResp bool) { - path := "/transforms-req-test" - if addResp { - path = "/transforms-resp-test" - } - - vs := &gw1.VirtualService{ - Metadata: &core.Metadata{ - Name: "app", - Namespace: "gloo-system", - }, - VirtualHost: &gw1.VirtualHost{ - Domains: []string{"*"}, - Routes: []*gw1.Route{{ - Options: &gloov1.RouteOptions{ - Transformations: &transformation.Transformations{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - Headers: map[string]*transformation.InjaTemplate{ - "foo": { - Text: "bar", - }, - }, - }, - }, - }, - }, - }, - Matchers: []*matchers.Matcher{ - { - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: path, - }, - }, - }, - Action: &gw1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream.Metadata.Ref(), - }, - DestinationSpec: &gloov1.DestinationSpec{ - DestinationType: &gloov1.DestinationSpec_Aws{ - Aws: &aws_plugin.DestinationSpec{ - LogicalName: "echo", - RequestTransformation: true, - ResponseTransformation: addResp, - }, - }, - }, - }, - }, - }, - }, - }}, - }, - } - - var opts clients.WriteOpts - _, err = testClients.VirtualServiceClient.Write(vs, opts) - Expect(err).NotTo(HaveOccurred()) - } - - By("sending a request with no response transformation") - prepVs(false) - var res *http.Response - var body []byte - path := "transforms-req-test" - waitForLambdaAndGetBody := func() error { - httpClient := testutils.DefaultClientBuilder().WithTimeout(time.Second * 10).Build() - - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/%s?foo=bar", "localhost", envoyInstance.HttpPort, path), bytes.NewBufferString(`"test"`)) - Expect(err).NotTo(HaveOccurred()) - req.Header.Set("Content-Type", "application/octet-stream") - req.Host = "test" - res, err = httpClient.Do(req) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - res.Body.Close() - return errors.New(fmt.Sprintf("%v is not OK", res.StatusCode)) - } - - defer res.Body.Close() - body, err = io.ReadAll(res.Body) - Expect(err).NotTo(HaveOccurred()) - return nil - } - EventuallyWithOffset(1, waitForLambdaAndGetBody, "5m", "1s").ShouldNot(HaveOccurred()) - - Expect(res.Header).To(HaveKeyWithValue("Foo", ContainElement("bar"))) - // see that the AWS request transform applied - this means that the lambda will get a json body - // and will return its error response - not a string - Expect(string(body)).To(MatchJSON(`{"body":"\"test\"","headers":{":authority":"test",":method":"POST",":path":"/transforms-req-test?foo=bar",":scheme":"http","accept-encoding":"gzip","content-length":"6","content-type":"application/octet-stream","user-agent":"Go-http-client/1.1","x-forwarded-proto":"http"},"httpMethod":"POST","multiValueHeaders":{},"multiValueQueryStringParameters":{},"path":"/transforms-req-test","queryString":"foo=bar","queryStringParameters":{"foo":"bar"}}`)) - - By("sending a request with response transformation") - path = "transforms-resp-test" - err = testClients.VirtualServiceClient.Delete("gloo-system", "app", clients.DeleteOpts{}) - Expect(err).NotTo(HaveOccurred()) - prepVs(true) - EventuallyWithOffset(1, waitForLambdaAndGetBody, "5m", "1s").ShouldNot(HaveOccurred()) - - Expect(res.Header).To(HaveKeyWithValue("Foo", ContainElement("bar"))) - // response transform restores the body - Expect(string(body)).To(Equal(`"test"`)) - - } - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - Context("Basic Auth", func() { - - addBasicCredentials := func() { - - localAwsCredentials := credentials.NewSharedCredentials("", "") - v, err := localAwsCredentials.Get() - if err != nil { - Fail("no AWS creds available") - } - var opts clients.WriteOpts - - accesskey := v.AccessKeyID - secretkey := v.SecretAccessKey - - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: defaultRegion, - }, - Kind: &gloov1.Secret_Aws{ - Aws: &gloov1.AwsSecret{ - AccessKey: accesskey, - SecretKey: secretkey, - }, - }, - } - - _, err = testClients.SecretClient.Write(secret, opts) - Expect(err).NotTo(HaveOccurred()) - } - Context("Without gateway translation", func() { - BeforeEach(func() { - setupEnvoy(true) - addBasicCredentials() - addUpstream() - }) - - It("should be able to call lambda", testProxy) - - It("should be able to call lambda with response transform", testProxyWithResponseTransform) - - It("should be able to call lambda with unwrapAsApiGateway", testProxyWithUnwrapAsApiGateway) - - It("should be able to call lambda with unwrapAsApiGateway with non-string headers in response", testProxyWithUnwrapAsApiGatewayNonStringHeaderResponse) - - It("should be able to call lambda with unwrapAsApiGateway with malformed headers in response", testProxyWithUnwrapAsApiGatewayMalformedHeaderResponse) - - It("should be able to call lambda with request transform", testProxyWithRequestTransform) - - It("should be able to call lambda with request and response transforms", testProxyWithRequestAndResponseTransforms) - }) - Context("With gateway translation", func() { - BeforeEach(func() { - setupEnvoy(false) - addBasicCredentials() - addUpstream() - }) - It("should be able to call lambda via gateway", testLambdaWithVirtualService) - - It("should be able to call lambda transformation and regular transformation", testLambdaTransformations) - }) - Context("Resource-based cross-account lambda", func() { - BeforeEach(func() { - runOptions.WhatToRun.DisableFds = true - setupEnvoy(true) - addBasicCredentials() - addCrossAccountUpstream() - }) - - It("should be able to interact with resource-based cross-account lambda", testProxyWithCrossAccountLambda) - }) - }) - Context("Temporary Credentials", func() { - - addCredentials := func() { - localAwsCredentials := credentials.NewSharedCredentials("", "") - sess, err := session.NewSession(&aws.Config{Region: aws.String(defaultRegion), Credentials: localAwsCredentials}) - if err != nil { - Fail("no AWS creds available") - } - stsClient := sts.New(sess) - result, err := stsClient.GetSessionToken(&sts.GetSessionTokenInput{}) - Expect(err).NotTo(HaveOccurred()) - - var opts clients.WriteOpts - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: defaultRegion, - }, - Kind: &gloov1.Secret_Aws{ - Aws: &gloov1.AwsSecret{ - AccessKey: *result.Credentials.AccessKeyId, - SecretKey: *result.Credentials.SecretAccessKey, - SessionToken: *result.Credentials.SessionToken, - }, - }, - } - - _, err = testClients.SecretClient.Write(secret, opts) - Expect(err).NotTo(HaveOccurred()) - } - Context("No gateway translation", func() { - - BeforeEach(func() { - setupEnvoy(true) - addCredentials() - addUpstream() - }) - - It("should be able to call lambda", testProxy) - - It("should be able to call lambda with response transform", testProxyWithResponseTransform) - - It("should be able to call lambda with request transform", testProxyWithRequestTransform) - - It("should be able to call lambda with request and response transforms", testProxyWithRequestAndResponseTransforms) - }) - Context("With gateawy translation", func() { - BeforeEach(func() { - setupEnvoy(false) - addCredentials() - addUpstream() - }) - - It("should be able to call lambda via gateway", testLambdaWithVirtualService) - - It("should be able to call lambda transformation and regular transformation", testLambdaTransformations) - }) - }) - Context("AssumeRoleWithWebIdentity Credentials", func() { - - var ( - tmpFile *os.File - ) - - addCredentialsSts := func() { - roleArn := "arn:aws:iam::802411188784:role/gloo-edge-e2e-sts" - jwtKey := os.Getenv(jwtPrivateKey) - if jwtKey == "" { - Fail(fmt.Sprintf("Token location unset, set via %s", jwtPrivateKey)) - } - - // Need to store the private key in base 64 otherwise the newlines get lost in the env var - data, err := base64.StdEncoding.DecodeString(jwtKey) - Expect(err).NotTo(HaveOccurred()) - - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(data) - Expect(err).NotTo(HaveOccurred()) - - now := time.Now() - - tokenToSign := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ - "sub": "1234567890", - "name": "Solo Test User", - "admin": true, - "iat": now.Unix(), - "exp": now.Add(time.Minute * 10).Unix(), - "nbf": now.Unix(), - "iss": "https://fake-oidc.solo.io", - "aud": "sts.amazonaws.com", - "kid": "XwCb60dEzG6QF4-5iCwFRE1w1hP_VEoy3JWcokISRp4", - }) - - signedJwt, err := tokenToSign.SignedString(privateKey) - Expect(err).NotTo(HaveOccurred()) - - tmpFile, err = os.CreateTemp("/tmp", "") - Expect(err).NotTo(HaveOccurred()) - defer tmpFile.Close() - - _, err = tmpFile.Write([]byte(signedJwt)) - Expect(err).NotTo(HaveOccurred()) - - // Have to set these values for tests which use the envoy binary - os.Setenv(webIdentityTokenFile, tmpFile.Name()) - os.Setenv(awsRoleArn, roleArn) - - envoyInstance.DockerOptions = envoy.DockerOptions{ - Volumes: []string{fmt.Sprintf("%s:%s", tmpFile.Name(), tmpFile.Name())}, - Env: []string{webIdentityTokenFile, awsRoleArn}, - } - } - - addUpstreamSts := func(region string) { - upstream = &gloov1.Upstream{ - Metadata: &core.Metadata{ - Namespace: "default", - Name: region, - }, - UpstreamType: &gloov1.Upstream_Aws{ - Aws: &aws_plugin.UpstreamSpec{ - Region: region, - }, - }, - } - - var opts clients.WriteOpts - _, err := testClients.UpstreamClient.Write(upstream, opts) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func() []*aws_plugin.LambdaFunctionSpec { - us, err := testClients.UpstreamClient.Read( - upstream.GetMetadata().Namespace, - upstream.GetMetadata().Name, - clients.ReadOpts{}, - ) - if err != nil { - return nil - } - return us.GetAws().GetLambdaFunctions() - }, "2m", "1s").Should(ContainElement(&aws_plugin.LambdaFunctionSpec{ - LogicalName: "uppercase", - LambdaFunctionName: "uppercase", - Qualifier: "$LATEST", - })) - } - - setupEnvoySts := func(justGloo bool, region string) { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - var uri string - if region == "" { - region = defaultRegion - uri = "sts.amazonaws.com" - } else { - uri = fmt.Sprintf("sts.%s.amazonaws.com", region) - } - - ns := defaults.GlooSystem - ro := &services.RunOptions{ - NsToWrite: ns, - NsToWatch: []string{"default", ns}, - WhatToRun: services.What{ - DisableGateway: justGloo, - }, - Settings: &gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - AwsOptions: &gloov1.GlooOptions_AWSOptions{ - CredentialsFetcher: &gloov1.GlooOptions_AWSOptions_ServiceAccountCredentials{ - ServiceAccountCredentials: &aws2.AWSLambdaConfig_ServiceAccountCredentials{ - Cluster: "aws_sts_cluster", - Uri: uri, - Region: region, - }, - }, - }, - }, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - - err := helpers.WriteDefaultGateways(defaults.GlooSystem, testClients.GatewayClient) - Expect(err).NotTo(HaveOccurred(), "Should be able to write default gateways") - } - - BeforeEach(func() { - testutils.ValidateRequirementsAndNotifyGinkgo(testutils.DefinedEnv(jwtPrivateKey)) - }) - - AfterEach(func() { - if tmpFile != nil { - os.Remove(tmpFile.Name()) - } - os.Unsetenv(webIdentityTokenFile) - }) - Context("No gateway translation ", func() { - Context("primary region", func() { - BeforeEach(func() { - setupEnvoySts(true, defaultRegion) - addCredentialsSts() - addUpstreamSts(defaultRegion) - }) - /* - * these tests can start failing if certs get rotated underneath us. - * the fix is to update the rotated thumbprint on our fake AWS OIDC per - * https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html - */ - It("should be able to call lambda", testProxy) - - It("should be able to call lambda with response transform", testProxyWithResponseTransform) - - It("should be able to call lambda with request transform", testProxyWithRequestTransform) - - It("should be able to call lambda with request and response transforms", testProxyWithRequestAndResponseTransforms) - }) - Context("secondary region", func() { - BeforeEach(func() { - setupEnvoySts(true, secondaryRegion) - addCredentialsSts() - addUpstreamSts(secondaryRegion) - }) - - It("should be able to call lambda", testProxy) - }) - Context("default region", func() { - BeforeEach(func() { - setupEnvoySts(true, "") - addCredentialsSts() - addUpstreamSts(defaultRegion) - }) - - It("should be able to call lambda", testProxy) - }) - }) - Context("With gateway translation", func() { - BeforeEach(func() { - setupEnvoySts(false, defaultRegion) - addCredentialsSts() - addUpstreamSts(defaultRegion) - }) - It("should be able to call lambda via gateway", testLambdaWithVirtualService) - - It("should be able to call lambda transformation and regular transformation", testLambdaTransformations) - }) - }) - -}) diff --git a/test/e2e/buffer_test.go b/test/e2e/buffer_test.go deleted file mode 100644 index 176ce01c2b6..00000000000 --- a/test/e2e/buffer_test.go +++ /dev/null @@ -1,312 +0,0 @@ -package e2e_test - -import ( - "net/http" - "time" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/golang/protobuf/ptypes/wrappers" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/helpers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - buffer "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/extensions/filters/http/buffer/v3" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" -) - -var _ = Describe("buffer", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("filter defined on listener", func() { - - Context("Large buffer ", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 4098, // max size - }, - }, - } - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - }) - - It("valid buffer size should succeed", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(`{"value":"test"}`). - WithContentType("application/json") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveExactResponseBody("{\"value\":\"test\"}")) - }, 10*time.Second, 1*time.Second).Should(Succeed()) - }) - - }) - - Context("Small buffer ", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 1, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - }) - - It("empty buffer should fail", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(`{"value":"test"}`). - WithContentType("application/json") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - Body: "Payload Too Large", - StatusCode: http.StatusRequestEntityTooLarge, - })) - }, 10*time.Second, 1*time.Second).Should(Succeed()) - }) - }) - }) - - Context("filter defined on listener and vhost", func() { - - Context("Large buffer ", func() { - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 1, - }, - }, - } - vsToTestUpstream := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - BufferPerRoute: &buffer.BufferPerRoute{ - Override: &buffer.BufferPerRoute_Buffer{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 4098, // max size - }, - }, - }, - }, - }). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsToTestUpstream, - } - }) - - It("valid buffer size should succeed", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(`{"value":"test"}`). - WithContentType("application/json") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveExactResponseBody("{\"value\":\"test\"}")) - }, 10*time.Second, 1*time.Second).Should(Succeed()) - }) - - }) - - Context("Small buffer ", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 4098, - }, - }, - } - vsToTestUpstream := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - BufferPerRoute: &buffer.BufferPerRoute{ - Override: &buffer.BufferPerRoute_Buffer{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 1, - }, - }, - }, - }, - }). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsToTestUpstream, - } - }) - - It("empty buffer should fail", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(`{"value":"test"}`). - WithContentType("application/json") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - Body: "Payload Too Large", - StatusCode: http.StatusRequestEntityTooLarge, - })) - }, 10*time.Second, 1*time.Second).Should(Succeed()) - }) - }) - }) - - Context("filter defined on listener and route", func() { - - Context("Large buffer ", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 1, - }, - }, - } - vsToTestUpstream := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - WithRouteOptions("test", &gloov1.RouteOptions{ - BufferPerRoute: &buffer.BufferPerRoute{ - Override: &buffer.BufferPerRoute_Buffer{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 4098, // max size - }, - }, - }, - }, - }). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsToTestUpstream, - } - }) - - It("valid buffer size should succeed", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(`{"value":"test"}`). - WithContentType("application/json") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveExactResponseBody("{\"value\":\"test\"}")) - }, 10*time.Second, 1*time.Second).Should(Succeed()) - }) - - }) - - Context("Small buffer ", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 4098, - }, - }, - } - vsToTestUpstream := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - WithRouteOptions("test", &gloov1.RouteOptions{ - BufferPerRoute: &buffer.BufferPerRoute{ - Override: &buffer.BufferPerRoute_Buffer{ - Buffer: &buffer.Buffer{ - MaxRequestBytes: &wrappers.UInt32Value{ - Value: 1, - }, - }, - }, - }, - }). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsToTestUpstream, - } - }) - - It("empty buffer should fail", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(`{"value":"test"}`). - WithContentType("application/json") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - Body: "Payload Too Large", - StatusCode: http.StatusRequestEntityTooLarge, - })) - }, 10*time.Second, 1*time.Second).Should(Succeed()) - }) - }) - }) - -}) diff --git a/test/e2e/connection_limit_test.go b/test/e2e/connection_limit_test.go deleted file mode 100644 index 06b2cc9b053..00000000000 --- a/test/e2e/connection_limit_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package e2e_test - -import ( - "sync" - "time" - - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/connection_limit" - fault "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/faultinjection" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/gomega/matchers" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/testutils" - "github.com/solo-io/solo-kit/pkg/utils/prototime" - "google.golang.org/protobuf/types/known/wrapperspb" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Connection Limit", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Filter not defined", func() { - - BeforeEach(func() { - injectRouteFaultDelay(testContext) - }) - - It("Should not drop any connections", func() { - var wg sync.WaitGroup - httpClient := testutils.DefaultClientBuilder().WithTimeout(time.Second * 10).Build() - requestBuilder := testContext.GetHttpRequestBuilder() - - expectSuccess := func() { - defer GinkgoRecover() - defer wg.Done() - response, err := httpClient.Do(requestBuilder.Build()) - Expect(response).Should(matchers.HaveOkResponse()) - Expect(err).NotTo(HaveOccurred(), "The connection should not be dropped") - } - - wg.Add(2) - - go expectSuccess() - go expectSuccess() - - wg.Wait() - }) - }) - - Context("Filter defined", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - ConnectionLimit: &connection_limit.ConnectionLimit{ - MaxActiveConnections: &wrapperspb.UInt32Value{ - Value: 1, - }, - }, - } - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - - injectRouteFaultDelay(testContext) - }) - - // This test has flaked before with the following envoy error : - // [error][envoy_bug] [external/envoy/source/common/http/conn_manager_impl.cc:527] envoy bug failure: !local_close_reason.empty(). Details: Local Close Reason was not set! - // This has been fixed in envoy v1.27.0 but since we still use v1.26.x, this issue intermittently occurs. - // Since this bug doesn't affect the functionally of the ConnectionLimit filter (requests still do not cross the limit), we're adding FlakeAttempts until we move to a version of envoy with this fix. - // More info can be found here : https://github.com/solo-io/gloo/issues/8531 - It("Should drop connections after limit is reached", FlakeAttempts(5), func() { - var wg sync.WaitGroup - httpClient := testutils.DefaultClientBuilder().WithTimeout(time.Second * 10).Build() - requestBuilder := testContext.GetHttpRequestBuilder() - - expectSuccess := func() { - defer GinkgoRecover() - defer wg.Done() - response, err := httpClient.Do(requestBuilder.Build()) - Expect(response).Should(matchers.HaveOkResponse()) - Expect(err).NotTo(HaveOccurred(), "The connection should not be dropped") - } - - expectTimeout := func() { - defer GinkgoRecover() - defer wg.Done() - _, err := httpClient.Do(requestBuilder.Build()) - Expect(err).Should(MatchError(ContainSubstring("EOF")), "The connection should close") - } - - wg.Add(2) - - go expectSuccess() - // Since we're sending requests concurrently to test the limits on active connections, - // it is sometimes flaky and the second request gets served first. - // That's why we're adding a delay between the first and second one - time.Sleep(100 * time.Millisecond) - go expectTimeout() - - wg.Wait() - }) - }) -}) - -func injectRouteFaultDelay(testContext *e2e.TestContext) { - // Since we are testing concurrent connections, introducing a delay to ensure that a connection remains open while we attempt to open another one - vs := gloohelpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithName(e2e.DefaultVirtualServiceName). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher("route", "/"). - WithRouteActionToUpstream("route", testContext.TestUpstream().Upstream). - WithRouteOptions("route", &gloov1.RouteOptions{ - Faults: &fault.RouteFaults{ - Delay: &fault.RouteDelay{ - FixedDelay: prototime.DurationToProto(time.Second * 1), - Percentage: float32(100), - }, - }, - }). - Build() - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vs, - } -} diff --git a/test/e2e/consul_test.go b/test/e2e/consul_test.go deleted file mode 100644 index a83ec1b1361..00000000000 --- a/test/e2e/consul_test.go +++ /dev/null @@ -1,441 +0,0 @@ -package e2e_test - -import ( - "context" - "errors" - "time" - - "github.com/solo-io/gloo/test/ginkgo/decorators" - - "github.com/golang/protobuf/ptypes/duration" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/solo-io/gloo/test/testutils" - - consulplugin "github.com/solo-io/gloo/projects/gloo/pkg/plugins/consul" - - "github.com/solo-io/gloo/test/helpers" - "google.golang.org/protobuf/types/known/wrapperspb" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/hashicorp/consul/api" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - consulapi "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/consul" - "github.com/solo-io/gloo/projects/gloo/pkg/upstreams/consul" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" -) - -var _ = Describe("Consul e2e", decorators.Consul, func() { - - Context("Consul Service Registry", func() { - - var ( - svc1, svc2, svc3 *v1helpers.TestUpstream - testContext *e2e.TestContextWithConsul - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContextWithConsul() - testContext.BeforeEach() - - testContext.SetRunSettings(&gloov1.Settings{ - Consul: &gloov1.Settings_ConsulConfiguration{ - DnsAddress: consulplugin.DefaultDnsAddress, - DnsPollingInterval: &duration.Duration{ - Seconds: 1, - }, - ServiceDiscovery: &gloov1.Settings_ConsulConfiguration_ServiceDiscoveryOptions{ - DataCenters: nil, // Use all available data-centers - }, - }, - ConsulDiscovery: &gloov1.Settings_ConsulUpstreamDiscoveryConfiguration{ - ServiceTagsAllowlist: []string{"1", "2"}, - }, - }) - - // Run Consul - testContext.RunConsul() - - // Run web applications - // We don't need to create the corresponding upstream because we are routing directly to consul - svc1 = v1helpers.NewTestHttpUpstreamWithReply(testContext.Ctx(), testContext.EnvoyInstance().LocalAddr(), "svc-1") - svc2 = v1helpers.NewTestHttpUpstreamWithReply(testContext.Ctx(), testContext.EnvoyInstance().LocalAddr(), "svc-2") - svc3 = v1helpers.NewTestHttpUpstreamWithReply(testContext.Ctx(), testContext.EnvoyInstance().LocalAddr(), "svc-3") - - // Register services with consul - consulInstance := testContext.ConsulInstance() - err := consulInstance.RegisterService("my-svc", "my-svc-1", testContext.EnvoyInstance().GlooAddr, []string{"svc", "1"}, svc1.Port) - Expect(err).NotTo(HaveOccurred()) - err = consulInstance.RegisterService("my-svc", "my-svc-2", testContext.EnvoyInstance().GlooAddr, []string{"svc", "2"}, svc2.Port) - Expect(err).NotTo(HaveOccurred()) - //we should not discover this service as it will be filtered out - err = consulInstance.RegisterService("my-svc-1", "my-svc-3", testContext.EnvoyInstance().GlooAddr, []string{"svc", "3"}, svc3.Port) - Expect(err).NotTo(HaveOccurred()) - - vsToConsulService := helpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteActionToSingleDestination(e2e.DefaultRouteName, &gloov1.Destination{ - DestinationType: &gloov1.Destination_Consul{ - Consul: &gloov1.ConsulServiceDestination{ - ServiceName: "my-svc", - Tags: []string{"svc", "1"}, - }, - }, - }). - Build() - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsToConsulService, - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - It("works as expected", func() { - requestBuilder := testContext.GetHttpRequestBuilder() - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "20s", ".1s").Should(Succeed(), "Eventually requests should only go to service with tag `1`") - Consistently(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "2s", ".2s").Should(Succeed(), "Consistently requests should only go to service with tag `1`") - - err := testContext.ConsulInstance().RegisterService("my-svc", "my-svc-2", testContext.EnvoyInstance().GlooAddr, []string{"svc", "1"}, svc2.Port) - Expect(err).NotTo(HaveOccurred()) - - // svc2 first to ensure we also still route to svc1 after registering svc2 - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-2")) - }, "20s", ".1s").Should(Succeed(), "Eventually requests should only go to service with tag `2`") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "20s", ".1s").Should(Succeed(), "Eventually requests should only go to service with tag `1`") - }) - - Context("Using Hostname address (as opposed to IPs addresses)", func() { - - BeforeEach(func() { - // These tests only seem to pass on a Linux machine, I have not investigated why - testutils.ValidateRequirementsAndNotifyGinkgo(testutils.LinuxOnly("Unknown")) - - err := testContext.ConsulInstance().RegisterService("my-svc", "my-svc-1", "my-svc.service.dc1.consul", []string{"svc", "1"}, svc1.Port) - Expect(err).NotTo(HaveOccurred()) - }) - - It("resolves consul services", func() { - requestBuilder := testContext.GetHttpRequestBuilder() - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "20s", ".1s").Should(Succeed(), "Eventually requests should only go to service with tag `1`") - Consistently(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "2s", ".2s").Should(Succeed(), "Consistently requests should only go to service with tag `1`") - }) - }) - - Context("EDS only updates", func() { - - var ( - defaultConsulSettings = &gloov1.Settings{ - Consul: &gloov1.Settings_ConsulConfiguration{ - DnsAddress: consulplugin.DefaultDnsAddress, - DnsPollingInterval: &duration.Duration{ - Seconds: 1, - }, - ServiceDiscovery: &gloov1.Settings_ConsulConfiguration_ServiceDiscoveryOptions{ - DataCenters: nil, // Use all available data-centers - }, - }, - ConsulDiscovery: &gloov1.Settings_ConsulUpstreamDiscoveryConfiguration{ - ServiceTagsAllowlist: []string{"1", "2"}, - }, - } - ) - - runTest := func() { - - By("requests only go to endpoints behind test upstream 1") - requestBuilder := testContext.GetHttpRequestBuilder() - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "20s", ".1s").Should(Succeed(), "Eventually requests should only go to service with tag `1`") - Consistently(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-1")) - }, "2s", ".2s").Should(Succeed(), "Consistently requests should only go to service with tag `1`") - - // update service one to point to test upstream 2 port - err := testContext.ConsulInstance().RegisterService("my-svc", "my-svc-1", testContext.EnvoyInstance().GlooAddr, []string{"svc", "1"}, svc2.Port) - Expect(err).NotTo(HaveOccurred()) - - By("requests only go to endpoints behind test upstream 2") - - // ensure EDS picked up this endpoint-only change - // test upstream 1 endpoint is now stale; should only get requests to endpoints for test upstream 2 for svc1 - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-2")) - }, "20s", ".1s").Should(Succeed(), "Eventually requests should only go to service with tag `2`") - Consistently(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveExactResponseBody("svc-2")) - }, "2s", ".2s").Should(Succeed(), "Consistently requests should only go to service with tag `1`") - } - - Context("non-blocking EDS queries", func() { - - BeforeEach(func() { - defaultConsulSettings.ConsulDiscovery.EdsBlockingQueries = &wrapperspb.BoolValue{Value: false} - testContext.SetRunSettings(defaultConsulSettings) - }) - - It("works as expected", func() { - runTest() - }) - }) - - Context("blocking EDS queries", func() { - - BeforeEach(func() { - defaultConsulSettings.ConsulDiscovery.EdsBlockingQueries = &wrapperspb.BoolValue{Value: true} - testContext.SetRunSettings(defaultConsulSettings) - }) - - It("works as expected", func() { - runTest() - }) - }) - - }) - - }) - - Context("Consul Golang Client / Consul CLI Differences", func() { - // This test was written to prove that the consul golang client behaves differently than the consul CLI, and thus - // that our `refreshSpecs()` usage in consul eds.go is correct and does not miss updates (which also allows - // us to make performance optimizations at scale, since our current implementation has a lot more cache hits). - - var ( - ctx context.Context - cancel context.CancelFunc - - consulInstance *services.ConsulInstance - serviceTagsAllowlist []string - consulWatcher consul.ConsulWatcher - ) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - consulInstance = consulFactory.MustConsulInstance() - - err := consulInstance.Run(ctx) - Expect(err).NotTo(HaveOccurred()) - - // init consul client - client, err := api.NewClient(api.DefaultConfig()) - Expect(err).NotTo(HaveOccurred()) - - serviceTagsAllowlist = []string{"1", "2"} - - consulWatcher, err = consul.NewConsulWatcher(client, nil, serviceTagsAllowlist) - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - cancel() - }) - - // This test was written to prove that the consul golang client behaves differently than the consul CLI, and thus - // that our `refreshSpecs()` usage in consul eds.go is correct and does not miss updates (which also allows - // us to make performance optimizations at scale, since our current implementation has a lot more cache hits). - It("fires service watch even if catalog service is the only update", func() { - svcsChan, errChan := consulWatcher.WatchServices(ctx, []string{"dc1"}, consulapi.ConsulConsistencyModes_DefaultMode, nil) - - // use select instead of eventually for easier debugging. - select { - case err := <-errChan: - Expect(err).NotTo(HaveOccurred()) - Fail("err chan closed prematurely") - case svcsReceived := <-svcsChan: - // the default consul svc in dc1 does not show up in our watch because of service tag filtering - Expect(svcsReceived).To(BeEmpty()) - case <-time.After(5 * time.Second): - Fail("timeout waiting for services") - } - - Consistently(func() error { - select { - case err := <-errChan: - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - return errors.New("err chan closed prematurely") - case svcsReceived := <-svcsChan: - // happy path, continue - ExpectWithOffset(1, svcsReceived).To(BeEmpty()) - case <-time.After(100 * time.Millisecond): - // happy path, continue - } - return nil - }, "2s", "0.2s").Should(Succeed()) - - // now that consul has a chance to update raft index with all internal updates; we should see no more updates - Consistently(func() error { - select { - case err := <-errChan: - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - return errors.New("err chan closed prematurely") - case svcsReceived := <-svcsChan: - ExpectWithOffset(1, svcsReceived).To(BeEmpty()) // we actually expect len(0) if anything; this is just here to get a nice output / diff before we fail regardless - Fail("did not expect to receive empty services") - case <-time.After(100 * time.Millisecond): - // happy path, continue - } - return nil - }, "2s", "0.2s").Should(Succeed()) - - // add a single service. - // this will fire a watch via consul CLI for both: - // - consul watch -type=service -service my-svc ./echo.sh - // - consul watch -type=services ./echo.sh - // - // as the service is new - // - // echo.sh is just a shell script with the contents: `echo "watch fired!"` so we can check that the watch fired. - err := consulInstance.RegisterLiveService("my-svc", "my-svc-1", "127.0.0.1", []string{"svc", "1"}, 80) - Expect(err).NotTo(HaveOccurred()) - - // use select instead of eventually for easier debugging. - select { - case err := <-errChan: - Expect(err).NotTo(HaveOccurred()) - Fail("err chan closed prematurely") - case svcsReceived := <-svcsChan: - // the default consul svc in dc1 does not show up in our watch because of service tag filtering - Expect(svcsReceived).To(HaveLen(1)) - Expect(svcsReceived[0].Name).To(Equal("my-svc")) - Expect(svcsReceived[0].Tags).To(ConsistOf([]string{"svc", "1"})) - Expect(svcsReceived[0].DataCenters).To(ConsistOf([]string{"dc1"})) - case <-time.After(5 * time.Second): - Fail("timeout waiting for services") - } - - Consistently(func() error { - select { - case err := <-errChan: - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - return errors.New("err chan closed prematurely") - case svcsReceived := <-svcsChan: - // happy path, continue - ExpectWithOffset(1, svcsReceived).To(HaveLen(1)) - ExpectWithOffset(1, svcsReceived[0].Name).To(Equal("my-svc")) - ExpectWithOffset(1, svcsReceived[0].Tags).To(ConsistOf([]string{"svc", "1"})) - ExpectWithOffset(1, svcsReceived[0].DataCenters).To(ConsistOf([]string{"dc1"})) - case <-time.After(100 * time.Millisecond): - // happy path, continue - } - return nil - }, "2s", "0.2s").Should(Succeed()) - - // now that consul has a chance to update raft index with all internal updates; we should see no more updates - Consistently(func() error { - select { - case err := <-errChan: - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - return errors.New("err chan closed prematurely") - case svcsReceived := <-svcsChan: - ExpectWithOffset(1, svcsReceived).To(BeEmpty()) // we actually expect len(1) if anything; this is just here to get a nice output / diff before we fail regardless - Fail("did not expect to receive services") - case <-time.After(100 * time.Millisecond): - // happy path, continue - } - return nil - }, "2s", "0.2s").Should(Succeed()) - - // update an existing service. - // this will fire a watch via consul CLI only for: - // - consul watch -type=service -service my-svc ./echo.sh - // - // as the service is new - // - // However this WILL fire an update on our golang client watch for services (last index increments) - // and thus we can depend on this to signal that we should query again for all catalog services in eds.go - // - // It appears the golang client lastIndex mirrors the raft index (per `consul info`), and while dated, - // this behavior still seems to hold: https://github.com/hashicorp/consul/issues/1244#issuecomment-141146851 - // - // In the event this behavior changes (this test fails on newer consul versions), - // we may need to move completely to the `eds_blocking_queries` as true despite the performance implications - // for correctness. - err = consulInstance.RegisterLiveService("my-svc", "my-svc-1", "127.0.0.1", []string{"svc", "1"}, 81) - Expect(err).NotTo(HaveOccurred()) - - // this is where golang client differs from cli! CLI registers watch on svc but not svcs; but we get for both - // use select instead of eventually for easier debugging. - select { - case err := <-errChan: - Expect(err).NotTo(HaveOccurred()) - Fail("err chan closed prematurely") - case svcsReceived := <-svcsChan: - // the default consul svc in dc1 does not show up in our watch - Expect(svcsReceived).To(HaveLen(1)) - Expect(svcsReceived[0].Name).To(Equal("my-svc")) - Expect(svcsReceived[0].Tags).To(ConsistOf([]string{"svc", "1"})) - Expect(svcsReceived[0].DataCenters).To(ConsistOf([]string{"dc1"})) - case <-time.After(5 * time.Second): - Fail("timeout waiting for services") - } - - Consistently(func() error { - select { - case err := <-errChan: - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - return errors.New("err chan closed prematurely") - case svcsReceived := <-svcsChan: - // happy path, continue - ExpectWithOffset(1, svcsReceived).To(HaveLen(1)) - ExpectWithOffset(1, svcsReceived[0].Name).To(Equal("my-svc")) - ExpectWithOffset(1, svcsReceived[0].Tags).To(ConsistOf([]string{"svc", "1"})) - ExpectWithOffset(1, svcsReceived[0].DataCenters).To(ConsistOf([]string{"dc1"})) - case <-time.After(100 * time.Millisecond): - // happy path, continue - } - return nil - }, "2s", "0.2s").Should(Succeed()) - - // now that consul has a chance to update raft index with all internal updates; we should see no more updates - Consistently(func() error { - select { - case err := <-errChan: - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - return errors.New("err chan closed prematurely") - case svcsReceived := <-svcsChan: - ExpectWithOffset(1, svcsReceived).To(BeEmpty()) // we actually expect len(1) if anything; this is just here to get a nice output / diff before we fail regardless - Fail("did not expect to receive services") - case <-time.After(100 * time.Millisecond): - // happy path, continue - } - return nil - }, "2s", "0.2s").Should(Succeed()) - }) - - }) - -}) diff --git a/test/e2e/cors_test.go b/test/e2e/cors_test.go deleted file mode 100644 index 929ba513fe1..00000000000 --- a/test/e2e/cors_test.go +++ /dev/null @@ -1,363 +0,0 @@ -package e2e_test - -import ( - "net/http" - "strings" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - "github.com/solo-io/gloo/test/e2e" - gloohelpers "github.com/solo-io/gloo/test/helpers" - - "github.com/envoyproxy/go-control-plane/pkg/wellknown" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/cors" -) - -const ( - requestACHMethods = "Access-Control-Allow-Methods" - requestACHOrigin = "Access-Control-Allow-Origin" - requestACHExposeHeaders = "Access-Control-Expose-Headers" - corsFilterString = `"name": "` + wellknown.CORS + `"` - corsActiveConfigString = `"envoy.filters.http.cors": {` - - commonHeader = "common-header" - routeHeader = "route-header" - vhHeader = "vh-header" -) - -var _ = Describe("CORS", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - var ( - allowedOrigins = []string{allowedOrigin} - allowedMethods = []string{http.MethodGet, http.MethodPost} - - routeAllowedOrigins = []string{"routeAllowThisOne.solo.io"} - ) - - When("CORS is defined on VirtualHost", func() { - - When("RouteAction is Upstream", func() { - BeforeEach(func() { - vsWithCors := gloohelpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithName("vs-cors"). - WithDomain(e2e.DefaultHost). - WithRouteActionToUpstream("route", testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher("route", "/cors"). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - Cors: &cors.CorsPolicy{ - AllowOrigin: allowedOrigins, - AllowOriginRegex: allowedOrigins, - AllowMethods: allowedMethods, - }}). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsWithCors, - } - }) - - It("should respect CORS", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(cfg).To(MatchRegexp(corsFilterString)) - g.Expect(cfg).To(MatchRegexp(corsActiveConfigString)) - g.Expect(cfg).To(MatchRegexp(allowedOrigin)) - }, "10s", ".1s").ShouldNot(HaveOccurred(), "Envoy config contains CORS filer") - - allowedOriginRequestBuilder := testContext.GetHttpRequestBuilder(). - WithOptionsMethod(). - WithPath("cors"). - WithHeader("Origin", allowedOrigins[0]). - WithHeader("Access-Control-Request-Method", http.MethodGet). - WithHeader("Access-Control-Request-Headers", "X-Requested-With") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(allowedOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: MatchRegexp(strings.Join(allowedMethods, ",")), - requestACHOrigin: Equal(allowedOrigins[0]), - })) - }).Should(Succeed(), "Request with allowed origin") - - disallowedOriginRequestBuilder := allowedOriginRequestBuilder.WithHeader("Origin", unAllowedOrigin) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(disallowedOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: BeEmpty(), - })) - }).Should(Succeed(), "Request with disallowed origin") - }) - - }) - - When("RouteAction is DirectResponseAction", func() { - - BeforeEach(func() { - vs := gloohelpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithName(e2e.DefaultVirtualServiceName). - WithDomain(e2e.DefaultHost). - WithRouteDirectResponseAction("route", &gloov1.DirectResponseAction{ - Status: 200, - }). - WithRoutePrefixMatcher("route", "/cors"). - WithRouteOptions("route", &gloov1.RouteOptions{ - Cors: &cors.CorsPolicy{ - AllowOrigin: allowedOrigins, - AllowOriginRegex: allowedOrigins, - AllowMethods: allowedMethods, - }}). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - }) - - It("should respect CORS", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(cfg).To(MatchRegexp(corsFilterString)) - g.Expect(cfg).NotTo(MatchRegexp(corsActiveConfigString)) - }, "10s", ".1s").ShouldNot(HaveOccurred(), "Envoy config contains CORS filer") - - allowedOriginRequestBuilder := testContext.GetHttpRequestBuilder(). - WithOptionsMethod(). - WithPath("cors"). - WithHeader("Origin", allowedOrigins[0]). - WithHeader("Access-Control-Request-Method", http.MethodGet). - WithHeader("Access-Control-Request-Headers", "X-Requested-With") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(allowedOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: MatchRegexp(strings.Join(allowedMethods, ",")), - requestACHOrigin: Equal(allowedOrigins[0]), - })) - }).Should(Succeed(), "Request with allowed origin") - - disallowedOriginRequestBuilder := allowedOriginRequestBuilder.WithHeader("Origin", unAllowedOrigin) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(disallowedOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: MatchRegexp(strings.Join(allowedMethods, ",")), - requestACHOrigin: Equal(allowedOrigins[0]), - })) - }).Should(Succeed(), "Request with disallowed origin") - }) - - }) - - When("CORS is defined on RouteOptions and VirtualHostOptions without corsPolicyMergeSettings set", func() { - BeforeEach(func() { - vsWithCors := gloohelpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithName("vs-cors"). - WithDomain(e2e.DefaultHost). - WithRouteActionToUpstream("route", testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher("route", "/cors"). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - Cors: &cors.CorsPolicy{ - AllowOrigin: allowedOrigins, - AllowOriginRegex: allowedOrigins, - AllowMethods: allowedMethods, - ExposeHeaders: []string{commonHeader, vhHeader}, - }}). - WithRouteOptions("route", &gloov1.RouteOptions{ - // We don't set allowed methods to show that we still get this from VirtualHost - Cors: &cors.CorsPolicy{ - AllowOrigin: routeAllowedOrigins, - AllowOriginRegex: routeAllowedOrigins, - ExposeHeaders: []string{commonHeader, routeHeader}, - }}). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsWithCors, - } - }) - - It("should respect CORS", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(cfg).To(MatchRegexp(corsFilterString)) - g.Expect(cfg).To(MatchRegexp(corsActiveConfigString)) - g.Expect(cfg).To(MatchRegexp(allowedOrigin)) - }, "10s", ".1s").ShouldNot(HaveOccurred(), "Envoy config contains CORS filer") - - allowedRouteOriginRequestBuilder := testContext.GetHttpRequestBuilder(). - WithOptionsMethod(). - WithPath("cors"). - WithHeader("Origin", routeAllowedOrigins[0]). - WithHeader("Access-Control-Request-Method", http.MethodGet). - WithHeader("Access-Control-Request-Headers", "X-Requested-With") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(allowedRouteOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: MatchRegexp(strings.Join(allowedMethods, ",")), - requestACHOrigin: Equal(routeAllowedOrigins[0]), - requestACHExposeHeaders: Equal(strings.Join([]string{commonHeader, routeHeader}, ",")), - })) - }).Should(Succeed(), "Request with allowed route origin, has expose headers from route only") - - // This demonstrates that when you define options both on the VirtualHost and Route levels, - // only the route definition is respected - allowedVhostOriginRequestBuilder := testContext.GetHttpRequestBuilder(). - WithOptionsMethod(). - WithPath("cors"). - // use the allowed origins defined on the vhost, not the route - WithHeader("Origin", allowedOrigins[0]). - WithHeader("Access-Control-Request-Method", http.MethodGet). - WithHeader("Access-Control-Request-Headers", "X-Requested-With") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(allowedVhostOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: BeEmpty(), - })) - }).Should(Succeed(), "Request with allowed origin from vhost is not allowed, since route overrides it") - - disallowedOriginRequestBuilder := allowedRouteOriginRequestBuilder.WithHeader("Origin", unAllowedOrigin) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(disallowedOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: BeEmpty(), - })) - }).Should(Succeed(), "Request with disallowed origin") - - // request with disallowed method - // shows that vhost field is respected iff route field is not set - allowedOriginRequestBuilder := testContext.GetHttpRequestBuilder(). - WithOptionsMethod(). - WithPath("cors"). - WithHeader("Origin", routeAllowedOrigins[0]). - WithHeader("Access-Control-Request-Method", http.MethodDelete). - WithHeader("Access-Control-Request-Headers", "X-Requested-With") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(allowedOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: MatchRegexp(strings.Join(allowedMethods, ",")), // show that methods are still coming through despite being on the vhost only - })) - }).Should(Succeed(), "Request with disallowed method via vhost") - - }) - - }) - - When("CORS is defined on RouteOptions and VirtualHostOptions with corsPolicyMergeSettings set", func() { - BeforeEach(func() { - vsWithCors := gloohelpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithName("vs-cors"). - WithDomain(e2e.DefaultHost). - WithRouteActionToUpstream("route", testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher("route", "/cors"). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - Cors: &cors.CorsPolicy{ - AllowOrigin: allowedOrigins, - AllowOriginRegex: allowedOrigins, - AllowMethods: allowedMethods, - ExposeHeaders: []string{commonHeader, vhHeader}, - }, - // These CorsPolicyMergeSettings should result in a policy that has all ExposeHeaders from - // both VH and Route - CorsPolicyMergeSettings: &cors.CorsPolicyMergeSettings{ - ExposeHeaders: cors.CorsPolicyMergeSettings_UNION, - }, - }). - WithRouteOptions("route", &gloov1.RouteOptions{ - // We don't set allowed methods to show that we still get this from VirtualHost - Cors: &cors.CorsPolicy{ - AllowOrigin: routeAllowedOrigins, - AllowOriginRegex: routeAllowedOrigins, - ExposeHeaders: []string{commonHeader, routeHeader}, - }}). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsWithCors, - } - }) - - It("should respect CORS policy derived according to merge settings", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(cfg).To(MatchRegexp(corsFilterString)) - g.Expect(cfg).To(MatchRegexp(corsActiveConfigString)) - g.Expect(cfg).To(MatchRegexp(allowedOrigin)) - }, "10s", ".1s").ShouldNot(HaveOccurred(), "Envoy config contains CORS filer") - - allowedRouteOriginRequestBuilder := testContext.GetHttpRequestBuilder(). - WithOptionsMethod(). - WithPath("cors"). - WithHeader("Origin", routeAllowedOrigins[0]). - WithHeader("Access-Control-Request-Method", http.MethodGet). - WithHeader("Access-Control-Request-Headers", "X-Requested-With") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(allowedRouteOriginRequestBuilder.Build())).Should(matchers.HaveOkResponseWithHeaders(map[string]interface{}{ - requestACHMethods: MatchRegexp(strings.Join(allowedMethods, ",")), - requestACHOrigin: Equal(routeAllowedOrigins[0]), - // Expect that we have expose headers from both VH and Route - requestACHExposeHeaders: Equal(strings.Join([]string{commonHeader, vhHeader, routeHeader}, ",")), - })) - }).Should(Succeed(), "Request with allowed route origin, has expose headers from both route and vh") - }) - - }) - - }) - - When("CORS is not defined on VirtualHost", func() { - - When("RouteAction is Upstream", func() { - BeforeEach(func() { - vsWithoutCors := gloohelpers.NewVirtualServiceBuilder().WithNamespace(writeNamespace). - WithName("vs-cors"). - WithDomain("cors.com"). - WithRouteActionToUpstream("route", testContext.TestUpstream().Upstream). - WithRoutePrefixMatcher("route", "/cors"). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vsWithoutCors, - } - }) - - It("should run without cors", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - g.Expect(cfg).To(MatchRegexp(corsFilterString)) - g.Expect(cfg).NotTo(MatchRegexp(corsActiveConfigString)) - }).Should(Succeed(), "Envoy config does not contain CORS filer") - }) - }) - }) - -}) diff --git a/test/e2e/csrf_test.go b/test/e2e/csrf_test.go deleted file mode 100644 index 3a041dd8a68..00000000000 --- a/test/e2e/csrf_test.go +++ /dev/null @@ -1,404 +0,0 @@ -package e2e_test - -import ( - "fmt" - "net/http" - "time" - - "github.com/solo-io/gloo/test/services/envoy" - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/golang/protobuf/ptypes/wrappers" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/helpers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/onsi/gomega/types" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloo_config_core "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/core/v3" - csrf "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/extensions/filters/http/csrf/v3" - gloo_type_matcher "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/type/matcher/v3" - glootype "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/type/v3" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" -) - -const ( - allowedOrigin = "allowThisOne.solo.io" - unAllowedOrigin = "doNot.allowThisOne.solo.io" -) - -var _ = Describe("CSRF", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("no filter defined", func() { - - It("should succeed with allowed origin", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), false) - }) - - It("should succeed with un-allowed origin", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), false) - }) - - }) - - Context("defined on listener", func() { - - Context("only on listener", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Csrf: getCsrfPolicyWithFilterEnabled(allowedOrigin), - } - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - }) - - It("should succeed with allowed origin", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), true) - }) - - It("should fail with un-allowed origin", func() { - EventuallyInvalidOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), true) - }) - }) - - Context("defined on listener with shadow mode config", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Csrf: getCsrfPolicyWithShadowEnabled(allowedOrigin), - } - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - }) - - It("should succeed with allowed origin, unsafe request", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), false) - }) - - It("should succeed with un-allowed origin and update invalid count", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), false) - }) - }) - - Context("defined on listener with filter enabled and shadow mode config", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Csrf: getCsrfPolicyWithFilterEnabledAndShadow(allowedOrigin), - } - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - }) - - It("should succeed with allowed origin, unsafe request", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), true) - }) - - It("should fail with un-allowed origin and update invalid count", func() { - EventuallyInvalidOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), true) - }) - }) - - }) - - Context("enabled on route", func() { - - BeforeEach(func() { - vs := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - WithRouteOptions("test", &gloov1.RouteOptions{ - Csrf: getCsrfPolicyWithFilterEnabled(allowedOrigin), - }). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - }) - - It("should succeed with allowed origin, unsafe request", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), true) - }) - - It("should fail with un-allowed origin", func() { - EventuallyInvalidOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), true) - }) - - }) - - Context("enabled defined on vhost", func() { - - BeforeEach(func() { - vs := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - Csrf: getCsrfPolicyWithFilterEnabled(allowedOrigin), - }). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - }) - - It("should succeed with allowed origin, unsafe request", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), true) - }) - - It("should fail with un-allowed origin", func() { - EventuallyInvalidOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), true) - }) - - }) - - Context("defined on weighted dest", func() { - - BeforeEach(func() { - vs := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToMultiDestination("test", &gloov1.MultiDestination{ - Destinations: []*gloov1.WeightedDestination{{ - Weight: &wrappers.UInt32Value{Value: 1}, - Destination: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: testContext.TestUpstream().Upstream.Metadata.Ref(), - }, - }, - Options: &gloov1.WeightedDestinationOptions{ - Csrf: getCsrfPolicyWithFilterEnabled(allowedOrigin), - }, - }}, - }). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - }) - - It("should succeed with allowed origin, unsafe request", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), true) - }) - - It("should fail with un-allowed origin", func() { - EventuallyInvalidOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), true) - }) - - }) - - Context("defined on listener and vhost, should use vhost definition", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Csrf: getCsrfPolicyWithFilterEnabled(unAllowedOrigin), - } - - vs := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - Csrf: getCsrfPolicyWithFilterEnabled(allowedOrigin), - }). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testContext.TestUpstream().Upstream). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gw, - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - - }) - - It("should succeed with allowed origin, unsafe request", func() { - EventuallyAllowedOriginResponse(buildRequestFromOrigin(allowedOrigin), testContext.EnvoyInstance(), true) - }) - - It("should fail with un-allowed origin", func() { - EventuallyInvalidOriginResponse(buildRequestFromOrigin(unAllowedOrigin), testContext.EnvoyInstance(), true) - }) - - }) - -}) - -// A safe http method is one that doesn't alter the state of the server (ie read only) -// A CSRF attack targets state changing requests, so the filter only acts on unsafe methods (ones that change state) -// This is used to spoof requests from various origins -func buildRequestFromOrigin(origin string) func() (*http.Response, error) { - return func() (*http.Response, error) { - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:%d/", "localhost", defaults.HttpPort), nil) - if err != nil { - return nil, err - } - req.Host = e2e.DefaultHost - req.Header.Set("Origin", origin) - - return testutils.DefaultHttpClient.Do(req) - } -} - -func EventuallyAllowedOriginResponse(request func() (*http.Response, error), envoyInstance *envoy.Instance, validateStatistics bool) { - EventuallyWithOffset(1, func(g Gomega) { - resp, err := request() - g.Expect(err).NotTo(HaveOccurred()) - defer resp.Body.Close() - g.Expect(resp).Should(matchers.HaveOkResponse()) - - if validateStatistics { - statistics, err := envoyInstance.Statistics() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(statistics).To(matchInvalidRequestEqualTo(0)) - g.Expect(statistics).To(matchValidRequestEqualTo(1)) - } - }, time.Second*30).Should(Succeed()) -} - -func EventuallyInvalidOriginResponse(request func() (*http.Response, error), envoyInstance *envoy.Instance, validateStatistics bool) { - EventuallyWithOffset(1, func(g Gomega) { - resp, err := request() - g.Expect(err).NotTo(HaveOccurred()) - defer resp.Body.Close() - g.Expect(resp).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - StatusCode: http.StatusForbidden, - Body: "Invalid origin", - })) - - if validateStatistics { - statistics, err := envoyInstance.Statistics() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(statistics).To(matchInvalidRequestEqualTo(1)) - g.Expect(statistics).To(matchValidRequestEqualTo(0)) - } - }, time.Second*30).Should(Succeed()) -} - -// https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/csrf_filter#statistics -func matchValidRequestEqualTo(count int) types.GomegaMatcher { - return MatchRegexp("http.http.csrf.request_valid: %d", count) -} - -func matchInvalidRequestEqualTo(count int) types.GomegaMatcher { - return MatchRegexp("http.http.csrf.request_invalid: %d", count) -} - -func getCsrfPolicyWithFilterEnabled(origin string) *csrf.CsrfPolicy { - return &csrf.CsrfPolicy{ - FilterEnabled: &gloo_config_core.RuntimeFractionalPercent{ - DefaultValue: &glootype.FractionalPercent{ - Numerator: uint32(100), - Denominator: glootype.FractionalPercent_HUNDRED, - }, - }, - AdditionalOrigins: []*gloo_type_matcher.StringMatcher{{ - MatchPattern: &gloo_type_matcher.StringMatcher_SafeRegex{ - SafeRegex: &gloo_type_matcher.RegexMatcher{ - EngineType: &gloo_type_matcher.RegexMatcher_GoogleRe2{ - GoogleRe2: &gloo_type_matcher.RegexMatcher_GoogleRE2{}, - }, - Regex: origin, - }, - }, - }}, - } -} - -func getCsrfPolicyWithShadowEnabled(origin string) *csrf.CsrfPolicy { - return &csrf.CsrfPolicy{ - ShadowEnabled: &gloo_config_core.RuntimeFractionalPercent{ - DefaultValue: &glootype.FractionalPercent{ - Numerator: uint32(100), - Denominator: glootype.FractionalPercent_HUNDRED, - }, - }, - AdditionalOrigins: []*gloo_type_matcher.StringMatcher{{ - MatchPattern: &gloo_type_matcher.StringMatcher_SafeRegex{ - SafeRegex: &gloo_type_matcher.RegexMatcher{ - EngineType: &gloo_type_matcher.RegexMatcher_GoogleRe2{ - GoogleRe2: &gloo_type_matcher.RegexMatcher_GoogleRE2{}, - }, - Regex: origin, - }, - }, - }}, - } -} - -func getCsrfPolicyWithFilterEnabledAndShadow(origin string) *csrf.CsrfPolicy { - return &csrf.CsrfPolicy{ - FilterEnabled: &gloo_config_core.RuntimeFractionalPercent{ - DefaultValue: &glootype.FractionalPercent{ - Numerator: uint32(100), - Denominator: glootype.FractionalPercent_HUNDRED, - }, - }, - ShadowEnabled: &gloo_config_core.RuntimeFractionalPercent{ - DefaultValue: &glootype.FractionalPercent{ - Numerator: uint32(100), - Denominator: glootype.FractionalPercent_HUNDRED, - }, - }, - AdditionalOrigins: []*gloo_type_matcher.StringMatcher{{ - MatchPattern: &gloo_type_matcher.StringMatcher_SafeRegex{ - SafeRegex: &gloo_type_matcher.RegexMatcher{ - EngineType: &gloo_type_matcher.RegexMatcher_GoogleRe2{ - GoogleRe2: &gloo_type_matcher.RegexMatcher_GoogleRE2{}, - }, - Regex: origin, - }, - }, - }}, - } -} diff --git a/test/e2e/custom_auth_test.go b/test/e2e/custom_auth_test.go deleted file mode 100644 index 12aa27a15d3..00000000000 --- a/test/e2e/custom_auth_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package e2e_test - -import ( - "context" - "fmt" - "net" - "net/http" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - "github.com/gogo/googleapis/google/rpc" - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/extauth/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - "google.golang.org/genproto/googleapis/rpc/status" - "google.golang.org/grpc" -) - -var _ = Describe("CustomAuth", func() { - - var ( - ctx context.Context - cancel context.CancelFunc - envoyInstance *envoy.Instance - testUpstream *v1helpers.TestUpstream - testClients services.TestClients - srv *grpc.Server - ) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - // Initialize Envoy instance - envoyInstance = envoyFactory.NewInstance() - - // Start custom extauth server and create upstream for it - var err error - srv, err = startCustomExtauthServer(8095) - Expect(err).NotTo(HaveOccurred()) - - customAuthServerUs := &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: "custom-auth", - Namespace: "default", - }, - UseHttp2: &wrappers.BoolValue{Value: true}, - UpstreamType: &gloov1.Upstream_Static{ - Static: &static.UpstreamSpec{ - Hosts: []*static.Host{{ - // this is a safe way of referring to localhost - Addr: envoyInstance.GlooAddr, - Port: 8095, - }}, - }, - }, - } - authUsRef := customAuthServerUs.Metadata.Ref() - - // Start Gloo - testClients = services.RunGlooGatewayUdsFds(ctx, &services.RunOptions{ - NsToWrite: defaults.GlooSystem, - NsToWatch: []string{"default", defaults.GlooSystem}, - WhatToRun: services.What{ - DisableGateway: true, - DisableFds: true, - DisableUds: true, - }, - Settings: &gloov1.Settings{ - Extauth: &v1.Settings{ - ExtauthzServerRef: authUsRef, - }, - }, - }) - - // Create static upstream for auth server - _, err = testClients.UpstreamClient.Write(customAuthServerUs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Run envoy - err = envoyInstance.RunWithRoleAndRestXds(envoy.DefaultProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - // Create a test upstream - testUpstream = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - _, err = testClients.UpstreamClient.Write(testUpstream.Upstream, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Create a proxy routing to the upstream and wait for it to be accepted - proxy := getProxyExtAuth("default", "proxy", envoyInstance.HttpPort, testUpstream.Upstream.Metadata.Ref()) - - _, err = testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.ProxyClient.Read(proxy.Metadata.Namespace, proxy.Metadata.Name, clients.ReadOpts{}) - }) - }) - - AfterEach(func() { - envoyInstance.Clean() - srv.GracefulStop() - cancel() - }) - - It("works as expected", func() { - client := &http.Client{} - - getRequest := func(prefix string) *http.Request { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d/%s", "localhost", envoyInstance.HttpPort, prefix), nil) - Expect(err).NotTo(HaveOccurred()) - return req - } - - expectResponseForUser := func(req *http.Request, username string, expectedStatus int) { - req.Header.Set("user", username) - - Eventually(func() (int, error) { - resp, err := client.Do(req) - if err != nil { - return 0, err - } - return resp.StatusCode, nil - }, "5s", "0.5s").Should(Equal(expectedStatus)) - } - publicRoute := getRequest("public") - userRoute := getRequest("user") - adminRoute := getRequest("admin") - - // Public route, everyone allowed - expectResponseForUser(publicRoute, "unknown", http.StatusOK) - expectResponseForUser(publicRoute, "john", http.StatusOK) - expectResponseForUser(publicRoute, "jane", http.StatusOK) - - // User route, only users and admins are allowed - expectResponseForUser(userRoute, "unknown", http.StatusForbidden) - expectResponseForUser(userRoute, "john", http.StatusOK) - expectResponseForUser(userRoute, "jane", http.StatusOK) - - // Admin route, only admins are allowed - expectResponseForUser(adminRoute, "unknown", http.StatusForbidden) - expectResponseForUser(adminRoute, "john", http.StatusForbidden) - expectResponseForUser(adminRoute, "jane", http.StatusOK) - - }) -}) - -func getProxyExtAuth(namespace, name string, envoyPort uint32, upstream *core.ResourceRef) *gloov1.Proxy { - return &gloov1.Proxy{ - Metadata: &core.Metadata{ - Name: name, - Namespace: namespace, - }, - Listeners: []*gloov1.Listener{{ - Name: "listener", - BindAddress: "0.0.0.0", - BindPort: envoyPort, - ListenerType: &gloov1.Listener_HttpListener{ - HttpListener: &gloov1.HttpListener{ - VirtualHosts: []*gloov1.VirtualHost{ - { - Name: "gloo-system.virt1", - Domains: []string{"*"}, - Options: &gloov1.VirtualHostOptions{ - Extauth: &v1.ExtAuthExtension{ - Spec: &v1.ExtAuthExtension_CustomAuth{ - CustomAuth: &v1.CustomAuth{ - ContextExtensions: map[string]string{ - "must-be": "user", // Only authenticated users can access this vhost - }, - }, - }, - }, - }, - Routes: []*gloov1.Route{ - { // This route can be accessed by users - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/user", - }, - }}, - Options: &gloov1.RouteOptions{ - PrefixRewrite: &wrappers.StringValue{Value: "/"}, - }, - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream, - }, - }, - }, - }, - }, - }, - { // This route can be accessed only by admins - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/admin", - }, - }}, - Options: &gloov1.RouteOptions{ - PrefixRewrite: &wrappers.StringValue{Value: "/"}, - Extauth: &v1.ExtAuthExtension{ - Spec: &v1.ExtAuthExtension_CustomAuth{ - CustomAuth: &v1.CustomAuth{ - ContextExtensions: map[string]string{ - "must-be": "admin", // Only authenticated users can access this vhost - }, - }, - }, - }, - }, - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream, - }, - }, - }, - }, - }, - }, - { // This route can be accessed by anyone - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/public", - }, - }}, - Options: &gloov1.RouteOptions{ - PrefixRewrite: &wrappers.StringValue{Value: "/"}, - Extauth: &v1.ExtAuthExtension{ - Spec: &v1.ExtAuthExtension_Disable{ - Disable: true, - }, - }, - }, - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: upstream, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }}, - } -} - -func startCustomExtauthServer(port uint) (*grpc.Server, error) { - srv := grpc.NewServer() - pb.RegisterAuthorizationServer(srv, &customAuthServer{ - // maps a username to a user type - users: map[string]string{ - "john": "user", - "jane": "admin", - }, - }) - - addr := fmt.Sprintf(":%d", port) - lis, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - go func() { - defer GinkgoRecover() - err := srv.Serve(lis) - Expect(err).ToNot(HaveOccurred()) - }() - return srv, nil -} - -var ( - deny = &pb.CheckResponse{Status: &status.Status{Code: int32(rpc.PERMISSION_DENIED)}} - allow = &pb.CheckResponse{Status: &status.Status{Code: int32(rpc.OK)}} -) - -// This custom auth server expects requests to provide the username in a "user" header. -// It checks the username against an internal set of users and denies the request if the user is not known or -// if they don't match the expected user type. -type customAuthServer struct { - users map[string]string // maps a username to a user type -} - -func (c *customAuthServer) Check(ctx context.Context, request *pb.CheckRequest) (*pb.CheckResponse, error) { - ctxExtensions := request.GetAttributes().GetContextExtensions() - - if len(ctxExtensions) == 0 { - return deny, nil - } - - requiredUserType, ok := ctxExtensions["must-be"] - if !ok { - return deny, nil - } - - headers := request.GetAttributes().GetRequest().GetHttp().GetHeaders() - if len(headers) == 0 { - return deny, nil - } - - username, ok := headers["user"] - if !ok { - return deny, nil - } - - actualUserType, ok := c.users[username] - if !ok { - return deny, nil - } - - // If we require an admin, only admin users are allowed - if requiredUserType == "admin" { - if actualUserType == "admin" { - return allow, nil - } - return deny, nil - } - - // If we require a user, both users and admin users are allowed - if requiredUserType == "user" { - if actualUserType == "admin" || actualUserType == "user" { - return allow, nil - } - return deny, nil - } - - return deny, nil -} diff --git a/test/e2e/dns_test.go b/test/e2e/dns_test.go deleted file mode 100644 index cc74a39aaf2..00000000000 --- a/test/e2e/dns_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package e2e_test - -import ( - "regexp" - - "google.golang.org/protobuf/types/known/durationpb" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/test/e2e" -) - -var _ = Describe("DNS E2E Test", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Defined on an Upstream", func() { - // It would be preferable to assert behaviors - // However, in the short term, we assert that the configuration has been received by the gateway-proxy - - It("ignores DnsRefreshRate on STATIC cluster", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - frequency := countRegexFrequency("dns_refresh_rate", cfg) - g.Expect(frequency).To(Equal(0)) - }, "5s", ".5s").Should(Succeed(), "DnsRefreshRate not in ConfigDump") - - // Update the Upstream to include DnsRefreshRate in the definition - // This is a STATIC Upstream, so we would expect the resource to have a warning on it - // and not propagate the configuration - testContext.PatchDefaultUpstream(func(us *gloov1.Upstream) *gloov1.Upstream { - us.DnsRefreshRate = &durationpb.Duration{Seconds: 10} - return us - }) - - Consistently(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - frequency := countRegexFrequency("dns_refresh_rate", cfg) - g.Expect(frequency).To(Equal(0)) - }, "2s", ".5s").Should(Succeed(), "DnsRefreshRate still not in ConfigDump") - }) - - It("supports DnsRefreshRate on STRICT_DNS cluster", func() { - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - frequency := countRegexFrequency("dns_refresh_rate", cfg) - g.Expect(frequency).To(Equal(0)) - }, "5s", ".5s").Should(Succeed(), "DnsRefreshRate not in ConfigDump") - - // Update the Upstream to have a non-IP host - // This will cause the generated cluster to be STRICT_DNS and the control plane - // will accept the DnsRefreshRate change - testContext.PatchDefaultUpstream(func(us *gloov1.Upstream) *gloov1.Upstream { - us.GetStatic().Hosts[0].Addr = "non-ip-host" - us.DnsRefreshRate = &durationpb.Duration{Seconds: 10} - return us - }) - - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - frequency := countRegexFrequency("dns_refresh_rate", cfg) - g.Expect(frequency).To(Equal(1)) - }, "5s", ".5s").Should(Succeed(), "DnsRefreshRate in ConfigDump") - }) - - It("supports RespectDnsTtl", func() { - // Some bootstrap clusters have respect_dns_ttl enabled, so we first count the frequency - originalFrequency := 0 - - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - originalFrequency = countRegexFrequency("respect_dns_ttl", cfg) - g.Expect(originalFrequency).NotTo(Equal(0)) - }, "5s", ".5s").Should(Succeed(), "Count initial RespectDnsTtl in ConfigDump") - - // Update the Upstream to include RespectDnsTtl in the definition - testContext.PatchDefaultUpstream(func(us *gloov1.Upstream) *gloov1.Upstream { - us.RespectDnsTtl = &wrappers.BoolValue{Value: true} - return us - }) - - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - newFrequency := countRegexFrequency("respect_dns_ttl", cfg) - g.Expect(newFrequency).To(Equal(originalFrequency + 1)) - }, "5s", ".5s").Should(Succeed(), "RespectDnsTtl count increased by 1") - }) - }) - -}) - -// countRegexFrequency returns the frequency of a `matcher` within a `text` -func countRegexFrequency(matcher, text string) int { - regex := regexp.MustCompile(matcher) - matches := regex.FindAllStringSubmatch(text, -1) - - return len(matches) -} diff --git a/test/e2e/dynamic_forward_proxy_test.go b/test/e2e/dynamic_forward_proxy_test.go deleted file mode 100644 index 6cc1870931f..00000000000 --- a/test/e2e/dynamic_forward_proxy_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package e2e_test - -import ( - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - defaults2 "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - - "net/http" - - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/helpers" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/dynamic_forward_proxy" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/transformation" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" -) - -var _ = Describe("dynamic forward proxy", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext( - testutils.LinuxOnly("Relies on using an in-memory pipe to ourselves"), - ) - - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("without transformation", func() { - - BeforeEach(func() { - gw := defaults2.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - DynamicForwardProxy: &dynamic_forward_proxy.FilterConfig{}, // pick up system defaults to resolve DNS - } - - vs := helpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteAction(e2e.DefaultRouteName, &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_DynamicForwardProxy{ - DynamicForwardProxy: &dynamic_forward_proxy.PerRouteConfig{ - HostRewriteSpecifier: &dynamic_forward_proxy.PerRouteConfig_AutoHostRewriteHeader{ - AutoHostRewriteHeader: "x-rewrite-me", - }, - }, - }, - }). - Build() - - resourceToCreate := testContext.ResourcesToCreate() - resourceToCreate.Gateways = gatewayv1.GatewayList{ - gw, - } - resourceToCreate.VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - }) - - // simpler e2e test without transformation to validate basic behavior - It("should proxy http if dynamic forward proxy header provided on request", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("get"). - WithHeader("x-rewrite-me", "postman-echo.com") - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: ContainSubstring(`"host": "postman-echo.com"`), - })) - }, "10s", ".1s").Should(Succeed()) - }) - }) - - Context("with transformation can set dynamic forward proxy header to rewrite authority", func() { - - BeforeEach(func() { - gw := defaults2.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - DynamicForwardProxy: &dynamic_forward_proxy.FilterConfig{}, // pick up system defaults to resolve DNS - } - vs := helpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteAction(e2e.DefaultRouteName, &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_DynamicForwardProxy{ - DynamicForwardProxy: &dynamic_forward_proxy.PerRouteConfig{ - HostRewriteSpecifier: &dynamic_forward_proxy.PerRouteConfig_AutoHostRewriteHeader{AutoHostRewriteHeader: "x-rewrite-me"}, - }, - }, - }). - WithRouteOptions(e2e.DefaultRouteName, &gloov1.RouteOptions{ - StagedTransformations: &transformation.TransformationStages{ - Early: &transformation.RequestResponseTransformations{ - RequestTransforms: []*transformation.RequestMatch{{ - RequestTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - Headers: map[string]*transformation.InjaTemplate{ - "x-rewrite-me": {Text: "postman-echo.com"}, - }, - }, - }, - }, - }}, - }, - }, - }). - Build() - - resourceToCreate := testContext.ResourcesToCreate() - resourceToCreate.Gateways = gatewayv1.GatewayList{ - gw, - } - resourceToCreate.VirtualServices = gatewayv1.VirtualServiceList{ - vs, - } - }) - - // This is an important test since the most common use case here will be to grab information from the - // request using a transformation and use that to determine the upstream destination to route to - It("should proxy http", func() { - requestBuilder := testContext.GetHttpRequestBuilder().WithPath("get") - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: ContainSubstring(`"host": "postman-echo.com"`), - })) - }, "10s", ".1s").Should(Succeed()) - }) - }) - -}) diff --git a/test/e2e/fault_injection_test.go b/test/e2e/fault_injection_test.go deleted file mode 100644 index b1776cb0b63..00000000000 --- a/test/e2e/fault_injection_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package e2e_test - -import ( - "net/http" - "time" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/solo-io/gloo/test/e2e" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - fault "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/faultinjection" - "github.com/solo-io/solo-kit/pkg/utils/prototime" -) - -var _ = Describe("Fault Injection", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Envoy Abort Fault", func() { - - It("works", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - Faults: &fault.RouteFaults{ - Abort: &fault.RouteAbort{ - HttpStatus: uint32(503), - Percentage: float32(100), - }, - }, - } - return vs - }) - - requestBuilder := testContext.GetHttpRequestBuilder() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(matchers.HaveHttpResponse(&matchers.HttpResponse{ - StatusCode: http.StatusServiceUnavailable, - Body: "fault filter abort", - })) - }, "5s", ".5s").Should(Succeed()) - }) - }) - - Context("Envoy Delay Fault", func() { - - It("works", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - Faults: &fault.RouteFaults{ - Delay: &fault.RouteDelay{ - FixedDelay: prototime.DurationToProto(time.Second * 3), - Percentage: float32(100), - }, - }, - } - return vs - }) - - // We need a client with a longer timeout than efault to allow for the fixed delay - httpClient := testutils.DefaultClientBuilder().WithTimeout(time.Second * 10).Build() - requestBuilder := testContext.GetHttpRequestBuilder() - Eventually(func(g Gomega) { - start := time.Now() - response, err := httpClient.Do(requestBuilder.Build()) - elapsed := time.Now().Sub(start) - - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(response).Should(matchers.HaveOkResponse()) - - // This test regularly flakes, and the error is usually of the form: - // "Elapsed time 2.998280684s not longer than delay 3s" - // There's a small precision issue when communicating with Envoy, so including a small - // margin of error to eliminate the test flake. - marginOfError := 100 * time.Millisecond - g.Expect(elapsed + marginOfError).To(BeNumerically(">", 3*time.Second)) - }, "20s", ".1s").Should(Succeed()) - - }) - }) -}) diff --git a/test/e2e/gateway_test.go b/test/e2e/gateway_test.go deleted file mode 100644 index c3c226df7a0..00000000000 --- a/test/e2e/gateway_test.go +++ /dev/null @@ -1,1347 +0,0 @@ -package e2e_test - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/rotisserie/eris" - "google.golang.org/protobuf/types/known/wrapperspb" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/solo-io/gloo/pkg/utils/statsutils/metrics" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - v3 "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/core/v3" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - extauthv1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/extauth/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" - "github.com/solo-io/gloo/projects/gloo/pkg/translator" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/common/kubernetes" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - "github.com/solo-io/solo-kit/pkg/errors" - "github.com/solo-io/solo-kit/pkg/utils/kubeutils" - corev1 "k8s.io/api/core/v1" -) - -const ( - tlsInspectorType = "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" -) - -var _ = Describe("Gateway", func() { - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - - vsMetric = metrics.Names[gatewayv1.VirtualServiceGVK] - ) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - }) - - AfterEach(func() { - cancel() - }) - - Describe("in memory", func() { - - BeforeEach(func() { - ro := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableFds: true, - DisableUds: true, - }, - Settings: &gloov1.Settings{ - // Record the config status for virtual services. Use the resource name as a - // label on the metric so that a unique time series is tracked for each VS - ObservabilityOptions: &gloov1.Settings_ObservabilityOptions{ - ConfigStatusMetricLabels: map[string]*metrics.MetricLabels{ - "VirtualService.v1.gateway.solo.io": { - LabelToPath: map[string]string{ - "name": "{.metadata.name}", - }, - }, - }, - }, - }, - } - - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - }) - - Context("http gateway", func() { - - var ( - envoyInstance *envoy.Instance - defaultGateways []*gatewayv1.Gateway - ) - - BeforeEach(func() { - envoyInstance = envoyFactory.NewInstance() - - defaultGateway := gatewaydefaults.DefaultGateway(writeNamespace) - defaultSslGateway := gatewaydefaults.DefaultSslGateway(writeNamespace) - - defaultGateways = []*gatewayv1.Gateway{ - defaultGateway, - defaultSslGateway, - } - }) - - JustBeforeEach(func() { - for _, gw := range defaultGateways { - _, err := testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - } - - // wait for the two gateways to be created. - Eventually(func() (gatewayv1.GatewayList, error) { - return testClients.GatewayClient.List(writeNamespace, clients.ListOpts{Ctx: ctx}) - }, "10s", "0.1s").Should(HaveLen(2), "Gateways should be present") - }) - - JustAfterEach(func() { - for _, gw := range defaultGateways { - err := testClients.GatewayClient.Delete(gw.GetMetadata().GetNamespace(), gw.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should create 2 gateways (1 ssl)", func() { - gw, err := testClients.GatewayClient.List(writeNamespace, clients.ListOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - numssl := 0 - if gw[0].Ssl { - numssl += 1 - } - if gw[1].Ssl { - numssl += 1 - } - Expect(numssl).To(Equal(1)) - }) - - It("correctly configures gateway for a virtual service which contains a route to a service", func() { - // Create a service so gloo can generate "fake" upstreams for it - svc := kubernetes.NewService("default", "my-service") - svc.Spec = corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 1234}}} - svc, err := testClients.ServiceClient.Write(svc, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Create a virtual service with a route pointing to the above service - vs := getTrivialVirtualServiceForService(writeNamespace, kubeutils.FromKubeMeta(svc.ObjectMeta, true).Ref(), uint32(svc.Spec.Ports[0].Port)) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Wait for proxy to be accepted - var proxy *gloov1.Proxy - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - var err error - proxy, err = testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - if err != nil { - return nil, err - } - // Verify that the proxy has the expected route - Expect(proxy.Listeners).To(HaveLen(2)) - var nonSslListener gloov1.Listener - for _, l := range proxy.Listeners { - if l.BindPort == defaults.HttpPort { - nonSslListener = *l - break - } - } - Expect(nonSslListener.GetHttpListener()).NotTo(BeNil()) - Expect(nonSslListener.GetHttpListener().VirtualHosts).To(HaveLen(1)) - Expect(nonSslListener.GetHttpListener().VirtualHosts[0].Routes).To(HaveLen(1)) - Expect(nonSslListener.GetHttpListener().VirtualHosts[0].Routes[0].GetRouteAction()).NotTo(BeNil()) - Expect(nonSslListener.GetHttpListener().VirtualHosts[0].Routes[0].GetRouteAction().GetSingle()).NotTo(BeNil()) - service := nonSslListener.GetHttpListener().VirtualHosts[0].Routes[0].GetRouteAction().GetSingle().GetKube() - Expect(service.Ref.Namespace).To(Equal(svc.Namespace)) - Expect(service.Ref.Name).To(Equal(svc.Name)) - Expect(service.Port).To(BeEquivalentTo(svc.Spec.Ports[0].Port)) - - return proxy, err - }) - - // clean up the virtual service that we created - err = testClients.VirtualServiceClient.Delete(vs.GetMetadata().GetNamespace(), vs.GetMetadata().GetName(), clients.DeleteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - }) - - It("won't allow a bad authconfig in a virtualservice to block updates to a gateway", func() { - // Test the following scenario: - // 1 VS is written with good config - // A 2nd VS is written with bad config (refers to an authConfig which doesn't exist) - // A 3rd VS is written with good config - // Expected Behavior: - // Final Proxy should have VS 1 & 3, reporting no errors - // The gateway output by these three VS's should also have no errors - // The 2nd VS should have an error complaining about the auth config not being found - - // Create a service so gloo can generate "fake" upstreams for it - svc := kubernetes.NewService("default", "my-service") - svc.Spec = corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 1234}}} - svc, err := testClients.ServiceClient.Write(svc, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Create a trivial, working VS with a route pointing to the above service - vs1 := getTrivialVirtualServiceForService(writeNamespace, kubeutils.FromKubeMeta(svc.ObjectMeta, true).Ref(), uint32(svc.Spec.Ports[0].Port)) - vs1.VirtualHost.Domains = []string{"vs1"} - vs1.Metadata.Name = "vs1" - _, err = testClients.VirtualServiceClient.Write(vs1, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Wait for proxy to be accepted - var proxy *gloov1.Proxy - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - proxy, err = testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - return proxy, err - }) - - // Create a second vs with a bad authconfig - vs2 := getTrivialVirtualServiceForService(defaults.GlooSystem, kubeutils.FromKubeMeta(svc.ObjectMeta, true).Ref(), uint32(svc.Spec.Ports[0].Port)) - vs2.VirtualHost.Domains = []string{"vs2"} - vs2.Metadata.Name = "vs2" - vs2.VirtualHost.Options = &gloov1.VirtualHostOptions{ - Extauth: &extauthv1.ExtAuthExtension{ - Spec: &extauthv1.ExtAuthExtension_ConfigRef{ - ConfigRef: &core.ResourceRef{ - Name: "bad-authconfig-doesnt-exist", - Namespace: defaults.GlooSystem, - }, - }, - }, - } - - // Write vs2 - _, err = testClients.VirtualServiceClient.Write(vs2, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Check that virtualservice is reporting an error because of missing authconfig: - gloohelpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - return testClients.VirtualServiceClient.Read(writeNamespace, "vs2", clients.ReadOpts{}) - }) - - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.GatewayClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - }) - - By("second virtualservice should not end up in the proxy (bad config)") - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - proxy, err = testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - if err != nil { - return nil, err - } - nonSslListener := getNonSSLListener(proxy) - vhostCount := len(nonSslListener.GetHttpListener().VirtualHosts) - if vhostCount == 1 { - return proxy, nil - } - - return nil, errors.Errorf("non-ssl listener virtual hosts: expected 1, found %d ", vhostCount) - }) - - // Create a third trivial vs with valid config - vs3 := getTrivialVirtualServiceForService(defaults.GlooSystem, kubeutils.FromKubeMeta(svc.ObjectMeta, true).Ref(), uint32(svc.Spec.Ports[0].Port)) - vs3.Metadata.Name = "vs3" - vs3.VirtualHost.Domains = []string{"vs3"} - _, err = testClients.VirtualServiceClient.Write(vs3, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Wait for proxy to be accepted - By("third virtualservice should end up in the proxy (good config)") - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - proxy, err = testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - if err != nil { - return nil, err - } - nonSslListener := getNonSSLListener(proxy) - vhostCount := len(nonSslListener.GetHttpListener().VirtualHosts) - if vhostCount == 2 { - return proxy, nil - } - - return nil, errors.Errorf("non-ssl listener virtual hosts: expected 2, found %d ", vhostCount) - }) - - // Verify that the proxy is as expected (2 functional virtualservices) - Expect(proxy.Listeners).To(HaveLen(2)) - nonSslListener := getNonSSLListener(proxy) - httpListener := nonSslListener.GetHttpListener() - - Expect(httpListener).NotTo(BeNil(), "should have a (non-ssl) http listener") - Expect(httpListener.VirtualHosts).To(HaveLen(2), "should have 2 virtualHosts") - Expect(httpListener.VirtualHosts[0].Domains[0]).To(Equal("vs1"), "should have vs1") - Expect(httpListener.VirtualHosts[1].Domains[0]).To(Equal("vs3"), "should have vs3") - Expect(httpListener.VirtualHosts[0].Routes).To(HaveLen(1), "should have 1 route in each host") - Expect(httpListener.VirtualHosts[1].Routes).To(HaveLen(1), "should have 1 route in each host") - - // Make sure the routes are as expected: - allRoutes := []*gloov1.Route{httpListener.VirtualHosts[0].Routes[0], httpListener.VirtualHosts[1].Routes[0]} - for _, route := range allRoutes { - Expect(route.GetRouteAction()).NotTo(BeNil()) - Expect(route.GetRouteAction().GetSingle()).NotTo(BeNil()) - service := route.GetRouteAction().GetSingle().GetKube() - Expect(service.Ref.Namespace).To(Equal(svc.Namespace)) - Expect(service.Ref.Name).To(Equal(svc.Name)) - Expect(service.Port).To(BeEquivalentTo(svc.Spec.Ports[0].Port)) - } - - // We have to sleep for a moment to avoid a race condition - time.Sleep(5 * time.Second) - - // Make sure each virtual service's status metric is as expected: - Expect(gloohelpers.ReadMetricByLabel(vsMetric, "name", "vs1")).To(Equal(0)) - Expect(gloohelpers.ReadMetricByLabel(vsMetric, "name", "vs2")).To(Equal(1)) - Expect(gloohelpers.ReadMetricByLabel(vsMetric, "name", "vs3")).To(Equal(0)) - }) - - Context("traffic", func() { - - var ( - testUpstream *v1helpers.TestUpstream - ) - - TestUpstreamReachable := func() { - v1helpers.TestUpstreamReachable(envoyInstance.HttpPort, testUpstream, nil) - } - - BeforeEach(func() { - testUpstream = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - - err := envoyInstance.RunWithRoleAndRestXds(writeNamespace+"~"+gatewaydefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - }) - - JustBeforeEach(func() { - _, err := testClients.UpstreamClient.Write(testUpstream.Upstream, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - JustAfterEach(func() { - err := testClients.UpstreamClient.Delete(testUpstream.Upstream.GetMetadata().GetNamespace(), testUpstream.Upstream.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - envoyInstance.Clean() - }) - - It("works when rapid virtual service creation and deletion causes no race conditions", func() { - vs := getTrivialVirtualServiceForUpstream(writeNamespace, testUpstream.Upstream.Metadata.Ref()) - - // Write the Virtual Service - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Wait for proxy to be created - var proxyList gloov1.ProxyList - Eventually(func() bool { - proxyList, err = testClients.ProxyClient.List(writeNamespace, clients.ListOpts{}) - if err != nil { - return false - } - return len(proxyList) == 1 - }, "20s", "1s").Should(BeTrue()) - - TestUpstreamReachable() - - // Delete the Virtual Service - err = testClients.VirtualServiceClient.Delete(writeNamespace, vs.GetMetadata().Name, clients.DeleteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // The vs should be deleted - var vsList gatewayv1.VirtualServiceList - Eventually(func() bool { - vsList, err = testClients.VirtualServiceClient.List(writeNamespace, clients.ListOpts{}) - if err != nil { - return false - } - if len(vsList) != 0 { - testClients.VirtualServiceClient.Delete(writeNamespace, vs.GetMetadata().Name, clients.DeleteOpts{}) - return false - } - return true - }, "10s", "0.5s").Should(BeTrue()) - Consistently(func() bool { - vsList, err = testClients.VirtualServiceClient.List(writeNamespace, clients.ListOpts{}) - if err != nil { - return false - } - return len(vsList) == 0 - }, "10s", "0.5s").Should(BeTrue()) - }) - - It("should work with no ssl and clean up the envoy config when the virtual service is deleted", func() { - vs := getTrivialVirtualServiceForUpstream(writeNamespace, testUpstream.Upstream.Metadata.Ref()) - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - TestUpstreamReachable() - - // Delete the Virtual Service - err = testClients.VirtualServiceClient.Delete(writeNamespace, vs.GetMetadata().Name, clients.DeleteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Wait for proxy to be deleted - var proxyList gloov1.ProxyList - Eventually(func() bool { - proxyList, err = testClients.ProxyClient.List(writeNamespace, clients.ListOpts{}) - if err != nil { - return false - } - return len(proxyList) == 0 - }, "10s", "0.1s").Should(BeTrue()) - - // Create a regular request - request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d", envoyInstance.HttpPort), nil) - Expect(err).NotTo(HaveOccurred()) - request = request.WithContext(ctx) - - // Check that we can no longer reach the upstream - client := &http.Client{} - Eventually(func() int { - response, err := client.Do(request) - if err != nil { - return 503 - } - return response.StatusCode - }, 20*time.Second, 500*time.Millisecond).Should(Equal(503)) - }) - - It("should not match requests that contain a header that is excluded from match", func() { - up := testUpstream.Upstream - vs := getTrivialVirtualServiceForUpstream("gloo-system", up.Metadata.Ref()) - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Create a regular request - request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", envoyInstance.HttpPort), nil) - Expect(err).NotTo(HaveOccurred()) - request = request.WithContext(ctx) - - // Check that we can reach the upstream - client := &http.Client{} - Eventually(func() int { - response, err := client.Do(request) - if err != nil { - return 0 - } - return response.StatusCode - }, 20*time.Second, 500*time.Millisecond).Should(Equal(200)) - - // Add the header that we are explicitly excluding from the match - request.Header = map[string][]string{"this-header-must-not-be-present": {"some-value"}} - - // We should get a 404 - Consistently(func() int { - response, err := client.Do(request) - if err != nil { - return 0 - } - return response.StatusCode - }, time.Second, 200*time.Millisecond).Should(Equal(404)) - }) - - It("should direct requests that use cluster_header to the proper upstream", func() { - upstreamName := translator.UpstreamToClusterName(testUpstream.Upstream.Metadata.Ref()) - - vs := getTrivialVirtualService(writeNamespace) - // Create route that uses cluster header destination - vs.GetVirtualHost().Routes = []*gatewayv1.Route{{ - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_ClusterHeader{ - ClusterHeader: "cluster-header-name", - }, - }, - }}} - - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Create a regular request - request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", envoyInstance.HttpPort), nil) - Expect(err).NotTo(HaveOccurred()) - request = request.WithContext(context.TODO()) - request.Header.Add("cluster-header-name", upstreamName) - - // Check that we can reach the upstream - client := &http.Client{} - Eventually(func() (int, error) { - response, err := client.Do(request) - if response == nil { - return 0, err - } - return response.StatusCode, err - }, 10*time.Second, 500*time.Millisecond).Should(Equal(200)) - }) - - Context("ssl", func() { - - var secret *gloov1.Secret - - BeforeEach(func() { - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - }) - - JustBeforeEach(func() { - _, err := testClients.SecretClient.Write(secret, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - JustAfterEach(func() { - err := testClients.SecretClient.Delete(secret.GetMetadata().GetNamespace(), secret.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - TestUpstreamSslReachable := func() { - cert := gloohelpers.Certificate() - v1helpers.TestUpstreamReachable(envoyInstance.HttpsPort, testUpstream, &cert) - } - - It("should work with ssl", func() { - // Check tls inspector has not been added yet - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(Not(MatchRegexp(tlsInspectorType))) - - vs := getTrivialVirtualServiceForUpstream(writeNamespace, testUpstream.Upstream.Metadata.Ref()) - vs.SslConfig = &ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: &core.ResourceRef{ - Name: secret.GetMetadata().GetName(), - Namespace: secret.GetMetadata().GetNamespace(), - }, - }, - } - - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - TestUpstreamSslReachable() - - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(MatchRegexp(tlsInspectorType)) - }) - }) - }) - }) - - Context("tcp gateway", func() { - - var ( - defaultGateways []*gatewayv1.Gateway - envoyInstance *envoy.Instance - tu *v1helpers.TestUpstream - ) - - BeforeEach(func() { - envoyInstance = envoyFactory.NewInstance() - - // Use tcp gateway instead of default - // Resources need to be created after the Envoy Instance because the port is dynamically allocated - defaultGateway := gatewaydefaults.DefaultTcpGateway(writeNamespace) - defaultSslGateway := gatewaydefaults.DefaultTcpSslGateway(writeNamespace) - - defaultGateways = []*gatewayv1.Gateway{ - defaultGateway, - defaultSslGateway, - } - - tu = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - - err := envoyInstance.RunWithRoleAndRestXds(writeNamespace+"~"+gatewaydefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - }) - - JustBeforeEach(func() { - for _, gw := range defaultGateways { - _, err := testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to write default gateways") - } - - _, err := testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - JustAfterEach(func() { - for _, gw := range defaultGateways { - err := testClients.GatewayClient.Delete(gw.GetMetadata().GetNamespace(), gw.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to delete default gateways") - } - }) - - AfterEach(func() { - envoyInstance.Clean() - }) - - Context("ssl", func() { - - var secret *gloov1.Secret - - BeforeEach(func() { - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - }) - - JustBeforeEach(func() { - _, err := testClients.SecretClient.Write(secret, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - JustAfterEach(func() { - err := testClients.SecretClient.Delete(secret.GetMetadata().GetNamespace(), secret.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - TestUpstreamSslReachableTcp := func() { - cert := gloohelpers.Certificate() - v1helpers.TestUpstreamReachable(envoyInstance.HttpsPort, tu, &cert) - } - - It("should work with ssl", func() { - // Check tls inspector has not been added yet - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(Not(MatchRegexp(tlsInspectorType))) - - host := &gloov1.TcpHost{ - Name: "one", - Destination: &gloov1.TcpHost_TcpAction{ - Destination: &gloov1.TcpHost_TcpAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: tu.Upstream.Metadata.Ref(), - }, - }, - }, - }, - SslConfig: &ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: &core.ResourceRef{ - Name: secret.GetMetadata().GetName(), - Namespace: secret.GetMetadata().GetNamespace(), - }, - }, - AlpnProtocols: []string{"http/1.1"}, - }, - } - - // Update gateway with tcp hosts - tcpGatewayRef := gatewaydefaults.DefaultTcpSslGateway(writeNamespace).GetMetadata().Ref() - err := gloohelpers.PatchResource( - ctx, - tcpGatewayRef, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetTcpGateway().TcpHosts = []*gloov1.TcpHost{host} - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - // Check tls inspector is correctly configured - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(MatchRegexp(tlsInspectorType)) - - TestUpstreamSslReachableTcp() - }) - }) - - Context("proxyProtocol", func() { - var ( - secret *gloov1.Secret - ) - - BeforeEach(func() { - - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - }) - - JustBeforeEach(func() { - _, err := testClients.SecretClient.Write(secret, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - tu.Upstream.ProxyProtocolVersion = &wrapperspb.StringValue{Value: "V1"} - }) - - JustAfterEach(func() { - err := testClients.SecretClient.Delete(secret.GetMetadata().GetNamespace(), secret.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - tu.Upstream.ProxyProtocolVersion = nil - }) - - It("should set the transport socket", func() { - - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(Not(MatchRegexp(tlsInspectorType))) - - host := &gloov1.TcpHost{ - Name: "one", - Destination: &gloov1.TcpHost_TcpAction{ - Destination: &gloov1.TcpHost_TcpAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: tu.Upstream.Metadata.Ref(), - }, - }, - }, - }, - SslConfig: &ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: &core.ResourceRef{ - Name: secret.GetMetadata().GetName(), - Namespace: secret.GetMetadata().GetNamespace(), - }, - }, - AlpnProtocols: []string{"http/1.1"}, - }, - } - - tcpGatewayRef := gatewaydefaults.DefaultTcpSslGateway(writeNamespace).GetMetadata().Ref() - err := gloohelpers.PatchResource( - ctx, - tcpGatewayRef, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetTcpGateway().TcpHosts = []*gloov1.TcpHost{host} - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - // Check tls inspector is correctly configured - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(MatchRegexp(tlsInspectorType)) - cd, _ := envoyInstance.ConfigDump() - Expect(cd).To(ContainSubstring("envoy.extensions.transport_sockets.proxy_protocol.v3.ProxyProtocolUpstreamTransport")) - }) - - }) - - }) - - // These tests are meant to test the hybrid-specific functionality - // The underlying Http and Tcp logic is tested independently - Context("hybrid gateway", func() { - - var ( - envoyInstance *envoy.Instance - testUpstream *v1helpers.TestUpstream - - virtualService *gatewayv1.VirtualService - hybridGateway *gatewayv1.Gateway - ) - - BeforeEach(func() { - envoyInstance = envoyFactory.NewInstance() - - testUpstream = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - virtualService = getTrivialVirtualServiceForUpstream(writeNamespace, testUpstream.Upstream.Metadata.Ref()) - hybridGateway = gatewaydefaults.DefaultHybridGateway(writeNamespace) - - err := envoyInstance.RunWithRoleAndRestXds(writeNamespace+"~"+gatewaydefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - }) - - JustBeforeEach(func() { - var err error - - _, err = testClients.UpstreamClient.Write(testUpstream.Upstream, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to write upstream") - - virtualService, err = testClients.VirtualServiceClient.Write(virtualService, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to write virtual service") - - hybridGateway, err = testClients.GatewayClient.Write(hybridGateway, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to write hybrid gateway") - }) - - JustAfterEach(func() { - var err error - - err = testClients.GatewayClient.Delete(hybridGateway.GetMetadata().GetNamespace(), hybridGateway.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to delete hybrid gateway") - - err = testClients.VirtualServiceClient.Delete(virtualService.GetMetadata().GetNamespace(), virtualService.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx, IgnoreNotExist: true}) - Expect(err).NotTo(HaveOccurred(), "Should be able to delete virtual service") - - err = testClients.UpstreamClient.Delete(testUpstream.Upstream.GetMetadata().GetNamespace(), testUpstream.Upstream.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred(), "Should be able to delete upstream") - }) - - AfterEach(func() { - envoyInstance.Clean() - }) - - It("should create a hybrid listener with http and tcp matched listeners", func() { - modifiedHybridGateway, err := testClients.GatewayClient.Read(writeNamespace, hybridGateway.GetMetadata().GetName(), clients.ReadOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - modifiedHybridGateway.GetHybridGateway().MatchedGateways = []*gatewayv1.MatchedGateway{ - { - Matcher: &gatewayv1.Matcher{ - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - GatewayType: &gatewayv1.MatchedGateway_HttpGateway{ - HttpGateway: &gatewayv1.HttpGateway{ - VirtualServiceNamespaces: []string{writeNamespace}, - }, - }, - }, - { - Matcher: &gatewayv1.Matcher{ - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "5.6.7.8", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - GatewayType: &gatewayv1.MatchedGateway_TcpGateway{ - TcpGateway: &gatewayv1.TcpGateway{}, - }, - }, - } - Eventually(func() error { - current, err := testClients.GatewayClient.Read(modifiedHybridGateway.Metadata.Namespace, modifiedHybridGateway.Metadata.Name, clients.ReadOpts{Ctx: ctx}) - if err != nil { - return err - } - modifiedHybridGateway.Metadata.ResourceVersion = current.Metadata.ResourceVersion - _, err = testClients.GatewayClient.Write(modifiedHybridGateway, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - return err - }, "5s", "0.3s").ShouldNot(HaveOccurred()) - - // wait for hybrid listener to propagate to the proxy - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - proxy, err := testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - if err != nil { - return nil, err - } - - // There should only be a single listener on the proxy and it should be a HybridListener - hybridListener := proxy.GetListeners()[0].GetHybridListener() - if hybridListener == nil { - return nil, eris.New("HybridListener is not present on Proxy") - } - - matchedListeners := hybridListener.GetMatchedListeners() - if len(matchedListeners) != 2 { - return nil, eris.New("HybridListener should have 2 matched listeners") - } - - if len(matchedListeners[0].GetHttpListener().GetVirtualHosts()) != 1 { - return nil, eris.New("HybridListener should have HttpListener with 1 Virtual host") - } - - if matchedListeners[1].GetTcpListener() == nil { - return nil, eris.New("HybridListener should have non-nil TcpListener") - } - - // if all conditions are met, return the proxy - return proxy, nil - }) - }) - - It("correctly configures gateway for a virtual service which contains a route to a service", func() { - // Create a service so gloo can generate "fake" upstreams for it - svc := kubernetes.NewService("default", "my-service") - svc.Spec = corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 1234}}} - svc, err := testClients.ServiceClient.Write(svc, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Update the existing virtual service with a route pointing to the above service - virtualService.VirtualHost.Routes[0].GetRouteAction().GetSingle().DestinationType = &gloov1.Destination_Kube{ - Kube: &gloov1.KubernetesServiceDestination{ - Ref: kubeutils.FromKubeMeta(svc.ObjectMeta, true).Ref(), - Port: uint32(svc.Spec.Ports[0].Port), - }, - } - Eventually(func() error { - current, err := testClients.VirtualServiceClient.Read(virtualService.Metadata.Namespace, virtualService.Metadata.Name, clients.ReadOpts{Ctx: ctx}) - if err != nil { - return err - } - virtualService.Metadata.ResourceVersion = current.Metadata.ResourceVersion - _, err = testClients.VirtualServiceClient.Write(virtualService, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - return err - }, "5s", "0.3s").ShouldNot(HaveOccurred()) - - // Wait for proxy to be accepted - var proxy *gloov1.Proxy - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - proxy, err = testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - if err != nil { - return nil, err - } - for _, l := range proxy.Listeners { - if hl := l.GetHybridListener(); hl != nil { - if len(hl.MatchedListeners) != 1 { - continue - } - return proxy, nil - } - } - - // Verify that the proxy has the expected route - Expect(proxy.Listeners).To(HaveLen(1)) - listener := proxy.Listeners[0] - - Expect(listener.GetHybridListener().GetMatchedListeners()[0].GetHttpListener()).NotTo(BeNil()) - httpListener := listener.GetHybridListener().GetMatchedListeners()[0].GetHttpListener() - Expect(httpListener.VirtualHosts).To(HaveLen(1)) - Expect(httpListener.VirtualHosts[0].Routes).To(HaveLen(1)) - Expect(httpListener.VirtualHosts[0].Routes[0].GetRouteAction()).NotTo(BeNil()) - Expect(httpListener.VirtualHosts[0].Routes[0].GetRouteAction().GetSingle()).NotTo(BeNil()) - service := httpListener.VirtualHosts[0].Routes[0].GetRouteAction().GetSingle().GetKube() - Expect(service.GetRef().GetNamespace()).To(Equal(svc.Namespace)) - Expect(service.GetRef().GetName()).To(Equal(svc.Name)) - Expect(service.Port).To(BeEquivalentTo(svc.Spec.Ports[0].Port)) - return nil, nil - }, "5s", "0.1s") - - }) - - Context("http traffic", func() { - - TestUpstreamReachable := func() { - v1helpers.TestUpstreamReachable(envoyInstance.HybridPort, testUpstream, nil) - } - - It("works when rapid virtual service creation and deletion causes no race conditions", func() { - var err error - - TestUpstreamReachable() - - // Delete the Virtual Service - err = testClients.VirtualServiceClient.Delete(writeNamespace, virtualService.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func() (gatewayv1.VirtualServiceList, error) { - return testClients.VirtualServiceClient.List(writeNamespace, clients.ListOpts{Ctx: ctx}) - }, "10s", "0.5s").Should(BeEmpty()) - Consistently(func() (gatewayv1.VirtualServiceList, error) { - return testClients.VirtualServiceClient.List(writeNamespace, clients.ListOpts{Ctx: ctx}) - }, "10s", "0.5s").Should(BeEmpty()) - }) - - It("should work with no ssl and clean up the envoy config when the virtual service is deleted", func() { - TestUpstreamReachable() - - // Delete the Virtual Service - err := testClients.VirtualServiceClient.Delete(writeNamespace, virtualService.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - // Wait for proxy to be deleted - var proxyList gloov1.ProxyList - Eventually(func() bool { - proxyList, err = testClients.ProxyClient.List(writeNamespace, clients.ListOpts{}) - if err != nil { - return false - } - return len(proxyList) == 0 - }, "10s", "0.1s").Should(BeTrue()) - - // Create a regular request - request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d", envoyInstance.HybridPort), nil) - Expect(err).NotTo(HaveOccurred()) - request = request.WithContext(ctx) - - // Check that we can no longer reach the upstream - client := &http.Client{} - Eventually(func() int { - response, err := client.Do(request) - if err != nil { - return 503 - } - return response.StatusCode - }, 20*time.Second, 500*time.Millisecond).Should(Equal(503)) - }) - - It("should not match requests that contain a header that is excluded from match", func() { - // Create a regular request - request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", envoyInstance.HybridPort), nil) - Expect(err).NotTo(HaveOccurred()) - request = request.WithContext(ctx) - - // Check that we can reach the upstream - client := &http.Client{} - Eventually(func() int { - response, err := client.Do(request) - if err != nil { - return 0 - } - return response.StatusCode - }, 20*time.Second, 500*time.Millisecond).Should(Equal(200)) - - // Add the header that we are explicitly excluding from the match - request.Header = map[string][]string{"this-header-must-not-be-present": {"some-value"}} - - // We should get a 404 - Consistently(func() int { - response, err := client.Do(request) - if err != nil { - return 0 - } - return response.StatusCode - }, time.Second, 200*time.Millisecond).Should(Equal(404)) - }) - - It("should direct requests that use cluster_header to the proper upstream", func() { - upstreamName := translator.UpstreamToClusterName(testUpstream.Upstream.Metadata.Ref()) - - // Create route that uses cluster header destination - virtualService.GetVirtualHost().Routes = []*gatewayv1.Route{{ - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_ClusterHeader{ - ClusterHeader: "cluster-header-name", - }, - }, - }}} - - Eventually(func() error { - current, err := testClients.VirtualServiceClient.Read(virtualService.Metadata.Namespace, virtualService.Metadata.Name, clients.ReadOpts{Ctx: ctx}) - if err != nil { - return err - } - virtualService.Metadata.ResourceVersion = current.Metadata.ResourceVersion - _, err = testClients.VirtualServiceClient.Write(virtualService, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - return err - }, "5s", "0.3s").ShouldNot(HaveOccurred()) - - // Create a regular request - request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%d/", envoyInstance.HybridPort), nil) - Expect(err).NotTo(HaveOccurred()) - request = request.WithContext(context.TODO()) - request.Header.Add("cluster-header-name", upstreamName) - - // Check that we can reach the upstream - client := &http.Client{} - Eventually(func() (int, error) { - response, err := client.Do(request) - if response == nil { - return 0, err - } - return response.StatusCode, err - }, 10*time.Second, 500*time.Millisecond).Should(Equal(200)) - }) - - Context("ssl", func() { - - var secret *gloov1.Secret - - BeforeEach(func() { - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - }) - - JustBeforeEach(func() { - _, err := testClients.SecretClient.Write(secret, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - JustAfterEach(func() { - err := testClients.SecretClient.Delete(secret.GetMetadata().GetNamespace(), secret.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - TestUpstreamSslReachable := func() { - cert := gloohelpers.Certificate() - v1helpers.TestUpstreamReachable(envoyInstance.HybridPort, testUpstream, &cert) - } - - It("should work with ssl if ssl config is present in matcher", func() { - // Check tls inspector has not been added yet - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(Not(MatchRegexp(tlsInspectorType))) - - sslConfig := &ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: secret.GetMetadata().Ref(), - }, - } - - // Update gateway with ssl config - gw, err := testClients.GatewayClient.List(writeNamespace, clients.ListOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - for _, g := range gw { - hybridGateway := g.GetHybridGateway() - if hybridGateway != nil { - hybridGateway.MatchedGateways[0].Matcher = &gatewayv1.Matcher{ - SslConfig: sslConfig, - } - } - Eventually(func() error { - current, err := testClients.GatewayClient.Read(g.Metadata.Namespace, g.Metadata.Name, clients.ReadOpts{Ctx: ctx}) - if err != nil { - return err - } - g.Metadata.ResourceVersion = current.Metadata.ResourceVersion - _, err = testClients.GatewayClient.Write(g, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - return err - }, "5s", "0.3s").ShouldNot(HaveOccurred()) - } - - virtualService.SslConfig = sslConfig - Eventually(func() error { - current, err := testClients.VirtualServiceClient.Read(virtualService.Metadata.Namespace, virtualService.Metadata.Name, clients.ReadOpts{Ctx: ctx}) - if err != nil { - return err - } - virtualService.Metadata.ResourceVersion = current.Metadata.ResourceVersion - _, err = testClients.VirtualServiceClient.Write(virtualService, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - return err - }, "5s", "0.3s").ShouldNot(HaveOccurred()) - - TestUpstreamSslReachable() - - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(MatchRegexp(tlsInspectorType)) - }) - }) - }) - - Context("tcp ssl", func() { - - var secret *gloov1.Secret - - BeforeEach(func() { - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - }) - - JustBeforeEach(func() { - _, err := testClients.SecretClient.Write(secret, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - JustAfterEach(func() { - err := testClients.SecretClient.Delete(secret.GetMetadata().GetNamespace(), secret.GetMetadata().GetName(), clients.DeleteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - TestUpstreamSslReachableTcp := func() { - cert := gloohelpers.Certificate() - v1helpers.TestUpstreamReachable(envoyInstance.HybridPort, testUpstream, &cert) - } - - It("should work with ssl", func() { - // Check tls inspector has not been added yet - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(Not(MatchRegexp(tlsInspectorType))) - - host := &gloov1.TcpHost{ - Name: "tcp-host-one", - Destination: &gloov1.TcpHost_TcpAction{ - Destination: &gloov1.TcpHost_TcpAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: testUpstream.Upstream.Metadata.Ref(), - }, - }, - }, - }, - SslConfig: &ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: &core.ResourceRef{ - Name: secret.GetMetadata().GetName(), - Namespace: secret.GetMetadata().GetNamespace(), - }, - }, - AlpnProtocols: []string{"http/1.1"}, - }, - } - - // Update gateway with tcp hosts - gw, err := testClients.GatewayClient.List(writeNamespace, clients.ListOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - for _, g := range gw { - hybridGateway := g.GetHybridGateway() - if hybridGateway != nil { - hybridGateway.MatchedGateways = []*gatewayv1.MatchedGateway{ - // Even though this test does not operate on HttpGateways, we intentionally include - // the configuration to ensure that it does not affect TcpGateway translation - { - Matcher: &gatewayv1.Matcher{ - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - GatewayType: &gatewayv1.MatchedGateway_HttpGateway{ - HttpGateway: &gatewayv1.HttpGateway{}, - }, - }, - { - Matcher: &gatewayv1.Matcher{}, - GatewayType: &gatewayv1.MatchedGateway_TcpGateway{ - TcpGateway: &gatewayv1.TcpGateway{ - TcpHosts: []*gloov1.TcpHost{host}, - }, - }, - }, - } - } - Eventually(func() error { - current, err := testClients.GatewayClient.Read(g.Metadata.Namespace, g.Metadata.Name, clients.ReadOpts{Ctx: ctx}) - if err != nil { - return err - } - g.Metadata.ResourceVersion = current.Metadata.ResourceVersion - _, err = testClients.GatewayClient.Write(g, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - return err - }, "5s", "0.3s").ShouldNot(HaveOccurred()) - } - - // Check tls inspector is correctly configured - Eventually(envoyInstance.ConfigDump, "10s", "0.1s").Should(MatchRegexp(tlsInspectorType)) - - TestUpstreamSslReachableTcp() - }) - }) - - }) - }) -}) - -func getTrivialVirtualServiceForUpstream(ns string, upstream *core.ResourceRef) *gatewayv1.VirtualService { - vs := getTrivialVirtualService(ns) - vs.VirtualHost.Routes[0].GetRouteAction().GetSingle().DestinationType = &gloov1.Destination_Upstream{ - Upstream: upstream, - } - return vs -} - -func getTrivialVirtualServiceForService(ns string, service *core.ResourceRef, port uint32) *gatewayv1.VirtualService { - vs := getTrivialVirtualService(ns) - vs.VirtualHost.Routes[0].GetRouteAction().GetSingle().DestinationType = &gloov1.Destination_Kube{ - Kube: &gloov1.KubernetesServiceDestination{ - Ref: service, - Port: port, - }, - } - return vs -} - -func getTrivialVirtualService(ns string) *gatewayv1.VirtualService { - return &gatewayv1.VirtualService{ - Metadata: &core.Metadata{ - Name: "vs", - Namespace: ns, - }, - VirtualHost: &gatewayv1.VirtualHost{ - Domains: []string{"*"}, - Routes: []*gatewayv1.Route{{ - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{}, - }, - }, - }, - Matchers: []*matchers.Matcher{ - { - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/", - }, - Headers: []*matchers.HeaderMatcher{ - { - Name: "this-header-must-not-be-present", - InvertMatch: true, - }, - }, - }, - }, - }}, - }, - } -} - -// Given a proxy, reuturns the non-ssl listener from -// that proxy, or nil if it can't be found -func getNonSSLListener(proxy *gloov1.Proxy) *gloov1.Listener { - for _, l := range proxy.Listeners { - if l.BindPort == defaults.HttpPort { - return l - } - } - return nil -} diff --git a/test/e2e/grpc_discovery_test.go b/test/e2e/grpc_discovery_test.go deleted file mode 100644 index 3c65bce2455..00000000000 --- a/test/e2e/grpc_discovery_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package e2e_test - -import ( - "context" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/grpc" - - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/e2e" - - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" -) - -var _ = Describe("GRPC to JSON Transcoding Plugin - Discovery", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - // This test seems to work locally without linux and we don't remember why it used to require linux, - // but if it starts failing locally, that might be the issue. - testContext = testContextFactory.NewTestContext() - testContext.SetUpstreamGenerator(func(ctx context.Context, addr string) *v1helpers.TestUpstream { - return v1helpers.NewTestGRPCUpstream(ctx, addr, 1) - }) - testContext.BeforeEach() - - testContext.SetRunServices(services.What{ - DisableGateway: false, - DisableUds: true, - // test relies on FDS to discover the grpc spec via reflection - DisableFds: false, - }) - testContext.SetRunSettings(&gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - // https://github.com/solo-io/gloo/issues/7577 - RemoveUnusedFilters: &wrappers.BoolValue{Value: false}, - }, - Discovery: &gloov1.Settings_DiscoveryOptions{ - FdsMode: gloov1.Settings_DiscoveryOptions_BLACKLIST, - }, - }) - }) - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - JustAfterEach(func() { - testContext.JustAfterEach() - }) - AfterEach(func() { - testContext.AfterEach() - }) - basicReq := func(body string, expected string) func(g Gomega) { - return func(g Gomega) { - req := testContext.GetHttpRequestBuilder().WithPostBody(body).WithContentType("application/json").WithPath("test") - g.Expect(testutils.DefaultHttpClient.Do(req.Build())).Should(testmatchers.HaveExactResponseBody(expected)) - } - } - Context("New API", func() { - It("Routes to GRPC Functions", func() { - - body := `"foo"` - testRequest := basicReq(body, `{"str":"foo"}`) - - Eventually(testRequest, 30, 1).Should(Succeed()) - - Eventually(testContext.TestUpstream().C).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - //basically `matchIncomingRequestRoute` needs to be set for this to work - It("Routes to GRPC functions with prefix matcher in VS", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Routes[0].Matchers = []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/test", - }, - }} - return vs - }) - body := `"foo"` - testRequest := basicReq(body, `{"str":"foo"}`) - - Eventually(testRequest, 30, 1).Should(Succeed()) - - Eventually(testContext.TestUpstream().C).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - It("Routes to GRPC Functions with parameters in URL", func() { - - testRequest := func(g Gomega) { - // GET request with parameters in URL - req := testContext.GetHttpRequestBuilder().WithPath("t/foo").Build() - g.Expect(testutils.DefaultHttpClient.Do(req)).Should(testmatchers.HaveExactResponseBody(`{"str":"foo"}`)) - } - Eventually(testRequest, 30, 1).Should(Succeed()) - Eventually(testContext.TestUpstream().C).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - }) - Context("Deprecated API", func() { - BeforeEach(func() { - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{getGrpcVs(e2e.WriteNamespace, testContext.TestUpstream().Upstream.GetMetadata().Ref())} - testContext.ResourcesToCreate().Upstreams = gloov1.UpstreamList{populateDeprecatedApi(testContext.TestUpstream().Upstream).(*gloov1.Upstream)} - }) - It("Does not overwrite existing upstreams with the deprecated API", func() { - - body := `{"str":"foo"}` - - testRequest := basicReq(body, body) - - Eventually(testRequest, 30, 1).Should(Succeed()) - - Eventually(testContext.TestUpstream().C).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - }) - Context("mismatched APIs", func() { - BeforeEach(func() { - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{getGrpcVs(e2e.WriteNamespace, testContext.TestUpstream().Upstream.GetMetadata().Ref())} - }) - // The test vs we generate already has a prefix matcher because that was how this API was documented - It("Routes to GRPC Functions", func() { - - body := `"foo"` - testRequest := basicReq(body, `{"str":"foo"}`) - - Eventually(testRequest, 30, 1).Should(Succeed()) - - Eventually(testContext.TestUpstream().C).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - It("Routes to GRPC Functions with parameters in URL", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Routes = []*gatewayv1.Route{ - { - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/t", - }, - }}, - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: testContext.TestUpstream().Upstream.GetMetadata().Ref(), - }, - DestinationSpec: &gloov1.DestinationSpec{ - DestinationType: &gloov1.DestinationSpec_Grpc{ - Grpc: &grpc.DestinationSpec{ - Package: "glootest", - Function: "TestParameterMethod", - Service: "TestService", - }, - }, - }, - }, - }, - }, - }}, - } - return vs - }) - - testRequest := func(g Gomega) { - // GET request with parameters in URL - req := testContext.GetHttpRequestBuilder().WithPath("t/foo").Build() - g.Expect(testutils.DefaultHttpClient.Do(req)).Should(testmatchers.HaveExactResponseBody(`{"str":"foo"}`)) - } - Eventually(testRequest, 30, 1).Should(Succeed()) - Eventually(testContext.TestUpstream().C).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - }) -}) diff --git a/test/e2e/grpc_json_test.go b/test/e2e/grpc_json_test.go deleted file mode 100644 index 5a73b572923..00000000000 --- a/test/e2e/grpc_json_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package e2e_test - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "net/http" - "os" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - "github.com/onsi/gomega/format" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gwdefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/grpc_json" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" -) - -var _ = Describe("GRPC to JSON Transcoding Plugin - Envoy API", func() { - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - tu *v1helpers.TestUpstream - ) - format.MaxLength = 0 - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - ro := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableGateway: false, - DisableUds: true, - DisableFds: true, - }, - Settings: &gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - // https://github.com/solo-io/gloo/issues/8374 - RemoveUnusedFilters: &wrappers.BoolValue{Value: false}, - }, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - - Expect(envoyInstance.RunWith(envoy.RunConfig{ - Context: ctx, - Role: writeNamespace + "~" + gwdefaults.GatewayProxyName, - Port: uint32(testClients.GlooPort), - RestXdsPort: uint32(testClients.RestXdsPort), - })).NotTo(HaveOccurred()) - - tu = v1helpers.NewTestGRPCUpstream(ctx, envoyInstance.LocalAddr(), 1) - _, err := testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - testRequest := func(path string, shouldMatch bool) { - body := fmt.Sprintf("%q", "foo") // this is valid JSON because the final encoded bytestring includes quote characters. - sendReq := func() (*http.Response, error) { - // send a request with a body - return http.Post(fmt.Sprintf("http://%s:%d/%s", "localhost", defaults.HttpPort, path), "application/json", bytes.NewBufferString(body)) - } - expectedResp := `{"str":"foo"}` - expectedFields := Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - } - if shouldMatch { - EventuallyWithOffset(1, func(g Gomega) { - g.Expect(sendReq()).Should(testmatchers.HaveExactResponseBody(expectedResp), "Did not get expected response") - }, 5, 1).Should(Succeed()) - EventuallyWithOffset(1, func(g Gomega) { - g.Expect(tu.C).Should(Receive(PointTo(MatchFields(IgnoreExtras, expectedFields))), "Upstream did not record expected request") - }).Should(Succeed()) - } else { - EventuallyWithOffset(1, func(g Gomega) { - g.Expect(sendReq()).ShouldNot(testmatchers.HaveExactResponseBody(expectedResp), "Got unexpected response") - }, 5, 1).Should(Succeed()) - EventuallyWithOffset(1, func(g Gomega) { - g.Expect(tu.C).ShouldNot(Receive(PointTo(MatchFields(IgnoreExtras, expectedFields))), "Upstream recorded unexpected request") - }).Should(Succeed()) - } - } - - Context("Routes to GRPC Functions", func() { - - It("with protodescriptor specified on gateway", func() { - gw := getGrpcJsonGateway() - _, err := testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - - vs := getGrpcJsonRawVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - testRequest("test", true) - }) - - It("with protodescriptor from configmap", func() { - // create an artifact containing the proto descriptor data - pathToDescriptors := "../v1helpers/test_grpc_service/descriptors/proto.pb" - bytes, err := os.ReadFile(pathToDescriptors) - Expect(err).ToNot(HaveOccurred()) - encoded := base64.StdEncoding.EncodeToString(bytes) - artifact := &gloov1.Artifact{ - Metadata: &core.Metadata{ - Name: "my-config-map", - Namespace: "gloo-system", - }, - Data: map[string]string{ - "protoDesc": encoded, - }, - } - _, err = testClients.ArtifactClient.Write(artifact, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - - // use the configmap ref in the gateway - gw := getGrpcJsonGateway() - gw.GatewayType.(*gatewayv1.Gateway_HttpGateway).HttpGateway.Options.GrpcJsonTranscoder.DescriptorSet = - &grpc_json.GrpcJsonTranscoder_ProtoDescriptorConfigMap{ - ProtoDescriptorConfigMap: &grpc_json.GrpcJsonTranscoder_DescriptorConfigMap{ - ConfigMapRef: &core.ResourceRef{Name: "my-config-map", Namespace: "gloo-system"}, - }, - } - _, err = testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - - vs := getGrpcJsonRawVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - testRequest("test", true) - }) - }) - Describe("Route matching behavior", func() { - It("When the route prefix is set to match one of the GrpcJsonTranscoder's services, requests to /test should go through", func() { - // Write a virtual service with a single route that matches against the prefix "/glootest.TestService" - vs := getGrpcJsonRawVs(writeNamespace, tu.Upstream.Metadata.Ref()) - vs.VirtualHost.Routes[0].Matchers[0].PathSpecifier.(*matchers.Matcher_Prefix).Prefix = "/glootest.TestService" - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - gw := getGrpcJsonGateway() - _, err = testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - - testRequest("test", true) - }) - - It("When MatchIncomingRequestRoute is true and route prefix matches service, requests to /test should fail", func() { - vs := getGrpcJsonRawVs(writeNamespace, tu.Upstream.Metadata.Ref()) - vs.VirtualHost.Routes[0].Matchers[0].PathSpecifier.(*matchers.Matcher_Prefix).Prefix = "/glootest.TestService" - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - gw := getGrpcJsonGateway() - gw.GatewayType.(*gatewayv1.Gateway_HttpGateway).HttpGateway.Options.GrpcJsonTranscoder.MatchIncomingRequestRoute = true - _, err = testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - - testRequest("test", false) - }) - - It("When MatchIncomingRequestRoute is true and route prefix matches request path, requests to /test should succeed", func() { - vs := getGrpcJsonRawVs(writeNamespace, tu.Upstream.Metadata.Ref()) - vs.VirtualHost.Routes[0].Matchers[0].PathSpecifier.(*matchers.Matcher_Prefix).Prefix = "/test" - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - - gw := getGrpcJsonGateway() - gw.GatewayType.(*gatewayv1.Gateway_HttpGateway).HttpGateway.Options.GrpcJsonTranscoder.MatchIncomingRequestRoute = true - _, err = testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - - testRequest("test", true) - }) - }) - - Context("GRPC configured on Upstream", func() { - It("with protodescriptor on upstream", func() { - - gw := gwdefaults.DefaultGateway(writeNamespace) - - _, err := testClients.GatewayClient.Write(gw, clients.WriteOpts{Ctx: ctx}) - Expect(err).ToNot(HaveOccurred()) - helpers.PatchResource(ctx, tu.Upstream.Metadata.Ref(), addGrpcJsonToUpstream, testClients.UpstreamClient.BaseClient()) - Expect(err).NotTo(HaveOccurred()) - vs := getGrpcJsonRawVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - testRequest("test", true) - }) - }) -}) - -func getGrpcJsonGateway() *gatewayv1.Gateway { - // Get the descriptor set bytes from the generated proto, rather than the go file (pb.go) - // as the generated go file doesn't have the annotations we need for gRPC to JSON transcoding - pathToDescriptors := "../v1helpers/test_grpc_service/descriptors/proto.pb" - bytes, err := os.ReadFile(pathToDescriptors) - Expect(err).ToNot(HaveOccurred()) - - return &gatewayv1.Gateway{ - BindAddress: "::", - BindPort: defaults.HttpPort, - NamespacedStatuses: &core.NamespacedStatuses{}, - Metadata: &core.Metadata{ - Name: "gateway-proxy", - Namespace: "gloo-system", - }, - GatewayType: &gatewayv1.Gateway_HttpGateway{ - HttpGateway: &gatewayv1.HttpGateway{ - Options: &gloov1.HttpListenerOptions{ - GrpcJsonTranscoder: &grpc_json.GrpcJsonTranscoder{ - DescriptorSet: &grpc_json.GrpcJsonTranscoder_ProtoDescriptorBin{ProtoDescriptorBin: bytes}, - Services: []string{"glootest.TestService"}, - }, - }, - }, - }, - ProxyNames: []string{"gateway-proxy"}, - } -} -func addGrpcJsonToUpstream(res resources.Resource) resources.Resource { - // Get the descriptor set bytes from the generated proto, rather than the go file (pb.go) - // as the generated go file doesn't have the annotations we need for gRPC to JSON transcoding - tu := res.(*gloov1.Upstream) - pathToDescriptors := "../v1helpers/test_grpc_service/descriptors/proto.pb" - bytes, err := os.ReadFile(pathToDescriptors) - Expect(err).ToNot(HaveOccurred()) - t := tu.GetUpstreamType().(*gloov1.Upstream_Static) - t.SetServiceSpec(&options.ServiceSpec{ - PluginType: &options.ServiceSpec_GrpcJsonTranscoder{ - GrpcJsonTranscoder: &grpc_json.GrpcJsonTranscoder{ - DescriptorSet: &grpc_json.GrpcJsonTranscoder_ProtoDescriptorBin{ - ProtoDescriptorBin: bytes, - }, - Services: []string{"glootest.TestService"}, - }, - }}) - return tu -} - -func getGrpcJsonRawVs(writeNamespace string, usRef *core.ResourceRef) *gatewayv1.VirtualService { - return &gatewayv1.VirtualService{ - Metadata: &core.Metadata{ - Name: "default", - Namespace: writeNamespace, - }, - VirtualHost: &gatewayv1.VirtualHost{ - Routes: []*gatewayv1.Route{ - { - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - // the grpc_json transcoding filter clears the cache so it no longer would match on /test (this can be configured) - Prefix: "/", - }, - }}, - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: usRef, - }, - }, - }, - }, - }, - }, - }, - }, - } -} diff --git a/test/e2e/grpc_plugin_test.go b/test/e2e/grpc_plugin_test.go deleted file mode 100644 index 1118764a3cb..00000000000 --- a/test/e2e/grpc_plugin_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package e2e_test - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "io" - "net/http" - "os" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/e2e" - - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options" - - "github.com/golang/protobuf/ptypes/wrappers" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gwdefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/grpc" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/transformation" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" -) - -var _ = Describe("GRPC to JSON Transcoding Plugin - Gloo API", func() { - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - tu *v1helpers.TestUpstream - ) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - ro := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableGateway: false, - DisableUds: true, - DisableFds: true, - }, - Settings: &gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - // https://github.com/solo-io/gloo/issues/8374 - RemoveUnusedFilters: &wrappers.BoolValue{Value: false}, - }, - Discovery: &gloov1.Settings_DiscoveryOptions{ - FdsMode: gloov1.Settings_DiscoveryOptions_DISABLED, - }, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - err := helpers.WriteDefaultGateways(writeNamespace, testClients.GatewayClient) - Expect(err).NotTo(HaveOccurred(), "Should be able to create the default gateways") - err = envoyInstance.RunWithRoleAndRestXds(writeNamespace+"~"+gwdefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - tu = v1helpers.NewTestGRPCUpstream(ctx, envoyInstance.LocalAddr(), 1) - _, err = testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{Ctx: ctx}) - Expect(err).NotTo(HaveOccurred()) - // Discovery is off so we fill in the upstream here. - helpers.PatchResource(ctx, tu.Upstream.Metadata.Ref(), populateDeprecatedApi, testClients.UpstreamClient.BaseClient()) - }) - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - basicReq := func(b []byte) func() (string, error) { - return func() (string, error) { - // send a request with a body - var buf bytes.Buffer - buf.Write(b) - res, err := http.Post(fmt.Sprintf("http://%s:%d/test", "localhost", envoyInstance.HttpPort), "application/json", &buf) - if err != nil { - return "", err - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - return string(body), err - } - } - - It("Routes to GRPC Functions", func() { - - vs := getGrpcVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - body := []byte(`{"str": "foo"}`) - - testRequest := basicReq(body) - - Eventually(testRequest, 30, 1).Should(Equal(`{"str":"foo"}`)) - }) - - It("Routes to GRPC Functions with parameters", func() { - - vs := getGrpcVs(writeNamespace, tu.Upstream.Metadata.Ref()) - grpc := vs.VirtualHost.Routes[0].GetRouteAction().GetSingle().GetDestinationSpec().GetGrpc() - grpc.Parameters = &transformation.Parameters{ - Path: &wrappers.StringValue{Value: "/test/{str}"}, - } - _, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - testRequest := func() (string, error) { - res, err := http.Get(fmt.Sprintf("http://%s:%d/test/foo", "localhost", defaults.HttpPort)) - if err != nil { - return "", err - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - return string(body), err - } - - Eventually(testRequest, 30, 1).Should(Equal(`{"str":"foo"}`)) - - }) -}) - -func getGrpcVs(writeNamespace string, usRef *core.ResourceRef) *gatewayv1.VirtualService { - return &gatewayv1.VirtualService{ - Metadata: &core.Metadata{ - Name: e2e.DefaultVirtualServiceName, - Namespace: writeNamespace, - }, - VirtualHost: &gatewayv1.VirtualHost{ - Routes: []*gatewayv1.Route{ - { - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - Prefix: "/test", - }, - }}, - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: usRef, - }, - DestinationSpec: &gloov1.DestinationSpec{ - DestinationType: &gloov1.DestinationSpec_Grpc{ - Grpc: &grpc.DestinationSpec{ - Package: "glootest", - Function: "TestMethod", - Service: "TestService", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func populateDeprecatedApi(res resources.Resource) resources.Resource { - tu := res.(*gloov1.Upstream) - pathToDescriptors := "../v1helpers/test_grpc_service/descriptors/proto.pb" - bytes, err := os.ReadFile(pathToDescriptors) - Expect(err).ToNot(HaveOccurred()) - singleEncoded := []byte(base64.StdEncoding.EncodeToString(bytes)) - grpcServices := []*grpc.ServiceSpec_GrpcService{ - { - ServiceName: "TestService", - PackageName: "glootest", - }, - } - t := tu.GetUpstreamType().(*gloov1.Upstream_Static) - t.SetServiceSpec(&options.ServiceSpec{ - PluginType: &options.ServiceSpec_Grpc{ - Grpc: &grpc.ServiceSpec{ - Descriptors: singleEncoded, - GrpcServices: grpcServices, - }, - }}) - return tu -} diff --git a/test/e2e/grpcweb_test.go b/test/e2e/grpcweb_test.go deleted file mode 100644 index b5e2c123b24..00000000000 --- a/test/e2e/grpcweb_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package e2e_test - -import ( - "bytes" - "encoding/base64" - "fmt" - "net/http" - "time" - - "github.com/solo-io/gloo/test/gomega/matchers" - - proto_matchers "github.com/solo-io/solo-kit/test/matchers" - - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/grpc_web" - - envoy_data_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3" - envoyals "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3" - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - static_plugin_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -var _ = Describe("Grpc Web", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Disable", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - GrpcWeb: &grpc_web.GrpcWeb{ - Disable: true, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("can disable grpc web filter", func() { - Eventually(func(g Gomega) { - proxy, err := testContext.ReadDefaultProxy() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(proxy.GetListeners()).To(HaveLen(1)) - g.Expect(proxy.GetListeners()[0].GetHttpListener().GetOptions().GetGrpcWeb()).To(proto_matchers.MatchProto(&grpc_web.GrpcWeb{ - Disable: true, - })) - }, "5s", ".5s").Should(Succeed()) - }) - }) - - Context("Grpc", func() { - - var ( - msgChan <-chan *envoy_data_accesslog_v3.HTTPAccessLogEntry - ) - - BeforeEach(func() { - grpcUpstream := &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: "grpc-service", - Namespace: writeNamespace, - }, - UseHttp2: &wrappers.BoolValue{Value: true}, - UpstreamType: &gloov1.Upstream_Static{ - Static: &static_plugin_gloo.UpstreamSpec{ - Hosts: []*static_plugin_gloo.Host{ - { - Addr: testContext.EnvoyInstance().LocalAddr(), - Port: testContext.EnvoyInstance().AccessLogPort, - }, - }, - }, - }, - } - vsToGrpcUpstream := helpers.NewVirtualServiceBuilder(). - WithName("vs-grpc"). - WithNamespace(writeNamespace). - WithDomain("grpc.com"). - WithRoutePrefixMatcher("grpc", "/"). - WithRouteActionToUpstream("grpc", grpcUpstream). - Build() - - // we want to test grpc web, so lets reuse the access log service - // we could use any other service, but we already have the ALS setup for tests - msgChan = runAccessLog(testContext.Ctx(), testContext.EnvoyInstance().AccessLogPort) - - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - GrpcWeb: &grpc_web.GrpcWeb{ - Disable: false, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - vsToGrpcUpstream, - } - testContext.ResourcesToCreate().Upstreams = gloov1.UpstreamList{ - grpcUpstream, - } - }) - - It("works with grpc web", func() { - // make a grpc web request - toSend := &envoyals.StreamAccessLogsMessage{ - LogEntries: &envoyals.StreamAccessLogsMessage_HttpLogs{ - HttpLogs: &envoyals.StreamAccessLogsMessage_HTTPAccessLogEntries{ - LogEntry: []*envoy_data_accesslog_v3.HTTPAccessLogEntry{{ - CommonProperties: &envoy_data_accesslog_v3.AccessLogCommon{ - UpstreamCluster: "foo", - }, - }}, - }, - }, - } - - // send toSend using grpc web - body, err := proto.Marshal(toSend) - Expect(err).NotTo(HaveOccurred()) - - var buffer bytes.Buffer - // write the length in the buffer - // compressed flag - buffer.Write([]byte{0}) - // length - Expect(len(body)).To(BeNumerically("<=", 0xff)) - buffer.Write([]byte{0, 0, 0, byte(len(body))}) - - // write the body to the buffer - buffer.Write(body) - - dest := make([]byte, base64.StdEncoding.EncodedLen(len(buffer.Bytes()))) - base64.StdEncoding.Encode(dest, buffer.Bytes()) - var bufferbase64 bytes.Buffer - bufferbase64.Write(dest) - - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:%d/envoy.service.accesslog.v3.AccessLogService/StreamAccessLogs", testContext.EnvoyInstance().HttpPort), &bufferbase64) - Expect(err).NotTo(HaveOccurred()) - req.Host = "grpc.com" - req.Header.Set("content-type", "application/grpc-web-text") - - Eventually(func(g Gomega) { - g.Expect(http.DefaultClient.Do(req)).Should(matchers.HaveOkResponse()) - }, "10s", "0.5s").Should(Succeed()) - - var entry *envoy_data_accesslog_v3.HTTPAccessLogEntry - Eventually(msgChan, time.Second).Should(Receive(&entry)) - Expect(entry.CommonProperties.UpstreamCluster).To(Equal("foo")) - }) - }) - -}) diff --git a/test/e2e/gzip_test.go b/test/e2e/gzip_test.go deleted file mode 100644 index 652ea3a3199..00000000000 --- a/test/e2e/gzip_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package e2e_test - -import ( - "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/gomega/transforms" - "github.com/solo-io/gloo/test/testutils" - - "net/http" - - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - - "github.com/solo-io/gloo/test/e2e" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gloogzip "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/filter/http/gzip/v2" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" -) - -var _ = Describe("gzip", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("filter undefined", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Gzip: nil, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("should return uncompressed json", func() { - jsonStr := `{"value":"Hello, world! It's me. I've been wondering if after all these years you'd like to meet."}` - jsonRequestBuilder := testContext.GetHttpRequestBuilder(). - WithContentType("application/json"). - WithAcceptEncoding("gzip"). - WithPostBody(jsonStr) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(jsonRequestBuilder.Build())).Should(matchers.HaveExactResponseBody(jsonStr)) - }, "5s", ".1s").Should(Succeed(), "json shorter than default content length is not compressed") - }) - }) - - Context("filter defined", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - Gzip: &gloogzip.Gzip{ - MemoryLevel: &wrappers.UInt32Value{ - Value: 5, - }, - CompressionLevel: gloogzip.Gzip_CompressionLevel_SPEED, - CompressionStrategy: gloogzip.Gzip_HUFFMAN, - WindowBits: &wrappers.UInt32Value{ - Value: 12, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("should return compressed json", func() { - jsonRequestBuilder := testContext.GetHttpRequestBuilder(). - WithContentType("application/json"). - WithAcceptEncoding("gzip") - - shortJsonStr := `{"value":"Hello, world!"}` // len(short json) < 30 - shortRequestBuilder := jsonRequestBuilder.WithPostBody(shortJsonStr) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(shortRequestBuilder.Build())).Should(matchers.HaveExactResponseBody(shortJsonStr)) - }).Should(Succeed(), "json shorter than content length should not be compressed") - - longJsonStr := `{"value":"Hello, world! It's me. I've been wondering if after all these years you'd like to meet."}` - longRequestBuilder := jsonRequestBuilder.WithPostBody(longJsonStr) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(longRequestBuilder.Build())).Should(matchers.HaveHttpResponse(&matchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: WithTransform(transforms.WithDecompressorTransform(), Equal(longJsonStr)), - })) - }).Should(Succeed(), "json longer than content length should be compressed") - }) - }) -}) diff --git a/test/e2e/happypath_test.go b/test/e2e/happypath_test.go deleted file mode 100644 index 4e119e0c04f..00000000000 --- a/test/e2e/happypath_test.go +++ /dev/null @@ -1,501 +0,0 @@ -package e2e_test - -import ( - "context" - "crypto/tls" - "fmt" - "net" - "net/http" - "time" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - "google.golang.org/protobuf/types/known/wrapperspb" - - envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - errors "github.com/rotisserie/eris" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/healthcheck" - routerV1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/router" - static_plugin_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/stats" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" - testhelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -var _ = Describe("Happy path", func() { - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - tu *v1helpers.TestUpstream - envoyPort uint32 - ) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - tu = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - envoyPort = envoyInstance.HttpPort - }) - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - TestUpstreamReachable := func() { - v1helpers.TestUpstreamReachableWithOffset(3, envoyPort, tu, nil) - } - - Describe("in memory", func() { - - var up *gloov1.Upstream - - BeforeEach(func() { - ns := defaults.GlooSystem - ro := &services.RunOptions{ - NsToWrite: ns, - NsToWatch: []string{"default", ns}, - WhatToRun: services.What{ - DisableGateway: true, - DisableUds: true, - DisableFds: true, - }, - Settings: &gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - EnableRestEds: &wrappers.BoolValue{ - Value: false, - }, - }, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - envoyInstance.ApiVersion = envoy_config_core_v3.ApiVersion_V3.String() - err := envoyInstance.RunWithRoleAndRestXds(ns+"~"+gatewaydefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - up = tu.Upstream - _, err = testClients.UpstreamClient.Write(up, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should not crash", func() { - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - TestUpstreamReachable() - }) - - It("should not crash multiple methods", func() { - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - proxy.Listeners[0].ListenerType.(*gloov1.Listener_HttpListener).HttpListener. - VirtualHosts[0].Routes[0].Matchers = []*matchers.Matcher{ - { - PathSpecifier: &matchers.Matcher_Prefix{Prefix: "/"}, - Methods: []string{"GET", "POST"}, - }, - } - - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - TestUpstreamReachable() - }) - - It("correctly configures envoy to emit virtual cluster statistics", func() { - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - - // Set a virtual cluster matching everything - proxy.Listeners[0].GetHttpListener().VirtualHosts[0].Options = &gloov1.VirtualHostOptions{ - Stats: &stats.Stats{ - VirtualClusters: []*stats.VirtualCluster{{ - Name: "test-vc", - Pattern: ".*", - }}, - }, - } - - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // This will hit the virtual host with the above virtual cluster config - TestUpstreamReachable() - - stats, err := envoyInstance.AdminClient().GetStats(ctx, nil) - Expect(err).NotTo(HaveOccurred()) - - // Verify that stats for the above virtual cluster are present - Expect(stats).To(ContainSubstring("vhost.virt1.vcluster.test-vc.")) - }) - - It("it correctly passes the suppress envoy headers config", func() { - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - - // configuring an http listener option to set suppressEnvoyHeaders to true - //projects/gloo/api/v1/options/router/router.proto - proxy.Listeners[0].GetHttpListener().Options = &gloov1.HttpListenerOptions{ - Router: &routerV1.Router{ - SuppressEnvoyHeaders: wrapperspb.Bool(true), - }, - } - - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{ - Ctx: ctx, - }) - Expect(err).NotTo(HaveOccurred()) - - TestUpstreamReachable() - - // This will hit the virtual host with the above virtual cluster config - response, err := http.Get(fmt.Sprintf("http://%s:%d/", "localhost", envoyInstance.HttpPort)) - Expect(err).NotTo(HaveOccurred()) - Expect(response.Header).NotTo(HaveKey("X-Envoy-Upstream-Service-Time")) - - cfg, err := envoyInstance.ConfigDump() - Expect(err).NotTo(HaveOccurred()) - - // We expect the envoy configuration to contain these properties in the configuration dump - Expect(cfg).To(MatchRegexp("\"suppress_envoy_headers\": true")) - - }) - - It("it correctly DID NOT pass the suppress envoy headers config", func() { - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - - // Set a virtual cluster listener that is blank and has no options - proxy.Listeners[0].GetHttpListener().Options = &gloov1.HttpListenerOptions{} - - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{ - Ctx: ctx, - }) - Expect(err).NotTo(HaveOccurred()) - TestUpstreamReachable() - - // This will hit the virtual host with the above virtual cluster config - response, err := http.Get(fmt.Sprintf("http://%s:%d/", "localhost", envoyInstance.HttpPort)) - Expect(err).NotTo(HaveOccurred()) - Expect(response.Header).To(HaveKey("X-Envoy-Upstream-Service-Time")) - - cfg, err := envoyInstance.ConfigDump() - Expect(err).NotTo(HaveOccurred()) - - // We expect the envoy configuration to NOT contain these properties in the configuration dump - Expect(cfg).To(Not(MatchRegexp("\"suppress_envoy_headers\": true"))) - }) - - It("passes a health check", func() { - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - - // Set a virtual cluster matching everything - proxy.Listeners[0].GetHttpListener().Options = &gloov1.HttpListenerOptions{ - HealthCheck: &healthcheck.HealthCheck{ - Path: "/healthy", - }, - } - - proxy.Listeners[0].GetHttpListener().VirtualHosts[0].Routes[0].Action = &gloov1.Route_DirectResponseAction{ - DirectResponseAction: &gloov1.DirectResponseAction{ - Status: http.StatusBadRequest, - Body: "only health checks work on me. sorry!", - }, - } - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func() error { - res, err := http.Get(fmt.Sprintf("http://%s:%d/healthy", "localhost", envoyPort)) - if err != nil { - return err - } - if res.StatusCode != http.StatusOK { - return errors.Errorf("bad status code: %v", res.StatusCode) - } - return nil - }, time.Second*10, time.Second/2).ShouldNot(HaveOccurred()) - - res, err := http.Post(fmt.Sprintf("http://localhost:%v/healthcheck/fail", envoyInstance.AdminPort), "", nil) - Expect(err).NotTo(HaveOccurred()) - Expect(res.StatusCode).To(Equal(http.StatusOK)) - - Eventually(func() error { - res, err := http.Get(fmt.Sprintf("http://%s:%d/healthy", "localhost", envoyPort)) - if err != nil { - return err - } - if res.StatusCode != http.StatusServiceUnavailable { - return errors.Errorf("bad status code: %v", res.StatusCode) - } - return nil - }, time.Second*10, time.Second/2).ShouldNot(HaveOccurred()) - }) - - Context("ssl", func() { - type sslConn struct { - sni string - port uint32 - } - var ( - upSsl *gloov1.Upstream - hellos chan sslConn - sslport1 uint32 - sslport2 uint32 - ) - BeforeEach(func() { - hellos = make(chan sslConn, 100) - sslSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - PrivateKey: testhelpers.PrivateKey(), - CertChain: testhelpers.Certificate(), - RootCa: testhelpers.Certificate(), - }, - }, - } - _, err := testClients.SecretClient.Write(sslSecret, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - // create ssl proxy - copyUp := *tu.Upstream - copyUp.Metadata.Name = copyUp.Metadata.Name + "-ssl" - port := tu.Upstream.UpstreamType.(*gloov1.Upstream_Static).Static.Hosts[0].Port - addr := tu.Upstream.UpstreamType.(*gloov1.Upstream_Static).Static.Hosts[0].Addr - sslport1 = v1helpers.StartSslProxyWithHelloCB(ctx, port, func(chi *tls.ClientHelloInfo) { - hellos <- sslConn{sni: chi.ServerName, port: sslport1} - }) - sslport2 = v1helpers.StartSslProxyWithHelloCB(ctx, port, func(chi *tls.ClientHelloInfo) { - hellos <- sslConn{sni: chi.ServerName, port: sslport2} - }) - ref := sslSecret.Metadata.Ref() - - copyUp.UpstreamType = &gloov1.Upstream_Static{ - Static: &static_plugin_gloo.UpstreamSpec{ - Hosts: []*static_plugin_gloo.Host{{ - Addr: addr, - Port: sslport1, - }, { - Addr: addr, - Port: sslport2, - }}, - }, - } - copyUp.SslConfig = &ssl.UpstreamSslConfig{ - SslSecrets: &ssl.UpstreamSslConfig_SecretRef{ - SecretRef: ref, - }, - } - upSsl = ©Up - }) - - Context("simple ssl", func() { - BeforeEach(func() { - _, err := testClients.UpstreamClient.Write(upSsl, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - }) - It("should work with ssl", func() { - proxycli := testClients.ProxyClient - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, upSsl.Metadata.Ref()) - _, err := proxycli.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - TestUpstreamReachable() - }) - }) - - Context("sni", func() { - - BeforeEach(func() { - upSsl.GetStatic().GetHosts()[0].SniAddr = "solo-sni-test" - upSsl.GetStatic().GetHosts()[1].SniAddr = "solo-sni-test2" - - _, err := testClients.UpstreamClient.Write(upSsl, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should work with ssl", func() { - proxycli := testClients.ProxyClient - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, upSsl.Metadata.Ref()) - _, err := proxycli.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - match1 := sslConn{sni: "solo-sni-test", port: sslport1} - match2 := sslConn{sni: "solo-sni-test2", port: sslport2} - - matched1 := false - matched2 := false - - timeout := time.After(5 * time.Second) - for { - TestUpstreamReachable() - select { - case <-timeout: - Fail("timedout waiting for sni") - case clienthello := <-hellos: - Expect(clienthello).To(SatisfyAny(Equal(match1), Equal(match2))) - if clienthello == match1 { - matched1 = true - } else { - matched2 = true - } - } - if matched1 && matched2 { - break - } - } - - }) - }) - }) - - Context("sad path", func() { - It("should error the proxy with two listeners with the same bind address", func() { - // create a proxy with two identical listeners to see errors come up - proxy := getTrivialProxyForUpstream(defaults.GlooSystem, envoyPort, up.Metadata.Ref()) - proxy.Listeners = append(proxy.Listeners, proxy.Listeners[0]) - - // persist the proxy - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // eventually the proxy is rejected - testhelpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - return testClients.ProxyClient.Read(proxy.Metadata.Namespace, proxy.Metadata.Name, clients.ReadOpts{}) - }) - }) - }) - }) - -}) - -//nolint:unparam // ns always receives "gloo-system" -func getTrivialProxyForUpstream(ns string, bindPort uint32, upstream *core.ResourceRef) *gloov1.Proxy { - proxy := getTrivialProxy(ns, bindPort) - proxy.Listeners[0].ListenerType.(*gloov1.Listener_HttpListener).HttpListener. - VirtualHosts[0].Routes[0].Action.(*gloov1.Route_RouteAction).RouteAction. - Destination.(*gloov1.RouteAction_Single).Single.DestinationType = - &gloov1.Destination_Upstream{Upstream: upstream} - return proxy -} - -func getTrivialProxyForService(ns string, bindPort uint32, service *core.ResourceRef, svcPort uint32) *gloov1.Proxy { - proxy := getTrivialProxy(ns, bindPort) - proxy.Listeners[0].ListenerType.(*gloov1.Listener_HttpListener).HttpListener. - VirtualHosts[0].Routes[0].Action.(*gloov1.Route_RouteAction).RouteAction. - Destination.(*gloov1.RouteAction_Single).Single.DestinationType = - &gloov1.Destination_Kube{ - Kube: &gloov1.KubernetesServiceDestination{ - Ref: service, - Port: svcPort, - }, - } - return proxy -} - -func getTrivialProxy(ns string, bindPort uint32) *gloov1.Proxy { - return &gloov1.Proxy{ - Metadata: &core.Metadata{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: ns, - }, - Listeners: []*gloov1.Listener{{ - Name: "listener", - BindAddress: "::", - BindPort: bindPort, - ListenerType: &gloov1.Listener_HttpListener{ - HttpListener: &gloov1.HttpListener{ - VirtualHosts: []*gloov1.VirtualHost{{ - Name: "virt1", - Domains: []string{"*"}, - Routes: []*gloov1.Route{{ - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{}, - }, - }, - }, - }}, - }}, - }, - }, - }}, - } -} - -// getNonSpecialIP returns a non-special IP that Kubernetes will allow in an endpoint. -func getNonSpecialIP(instance *envoy.Instance) string { - if instance.UseDocker { - return instance.LocalAddr() - } - - ifaces, err := net.Interfaces() - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - for _, iface := range ifaces { - if iface.Flags&net.FlagLoopback != 0 { - continue - } - addrs, err := iface.Addrs() - if err != nil { - continue - } - for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - default: - continue - } - if isNonSpecialIP(ip) { - return ip.String() - } - } - } - Fail("no ip address available", 1) - return "" -} - -// isNonSpecialIP is adapted from ValidateNonSpecialIP in k8s.io/kubernetes/pkg/apis/core/validation/validation.go -// -// Specifically disallowed are unspecified, loopback addresses, and link-local addresses -// which tend to be used for node-centric purposes (e.g. metadata service). -func isNonSpecialIP(ip net.IP) bool { - if ip == nil { - return false // must be a valid IP address - } - if ip.IsUnspecified() { - return false // may not be unspecified - } - if ip.IsLoopback() { - return false // may not be in the loopback range (127.0.0.0/8, ::1/128) - } - if ip.IsLinkLocalUnicast() { - return false // may not be in the link-local range (169.254.0.0/16, fe80::/10) - } - if ip.IsLinkLocalMulticast() { - return false // may not be in the link-local multicast range (224.0.0.0/24, ff02::/10) - } - return true -} diff --git a/test/e2e/header_validation_test.go b/test/e2e/header_validation_test.go deleted file mode 100644 index 78025080969..00000000000 --- a/test/e2e/header_validation_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package e2e_test - -import ( - "net/http" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/test/gomega/matchers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - header_validation "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/header_validation" - "github.com/solo-io/gloo/test/e2e" -) - -var _ = Describe("Header Validation", Label(), func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - var testRequirements []testutils.Requirement - - testContext = testContextFactory.NewTestContext(testRequirements...) - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - waitUntilProxyIsRunning := func() { - // Do a GET request to make sure the proxy is running - EventuallyWithOffset(1, func(g Gomega) { - req := testContext.GetHttpRequestBuilder().Build() - result, err := testutils.DefaultHttpClient.Do(req) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(result).Should(matchers.HaveOkResponse()) - }, "5s", ".5s").Should(Succeed(), "GET with valid host returns a 200") - } - - buildRequest := func(methodName string) *http.Request { - return testContext.GetHttpRequestBuilder(). - WithMethod(methodName). - Build() - } - - Context("Header Validation tests", func() { - It("rejects custom methods with default configuration", func() { - waitUntilProxyIsRunning() - Expect(testutils.DefaultHttpClient.Do(buildRequest("CUSTOMMETHOD"))).Should(matchers.HaveStatusCode(http.StatusBadRequest)) - }) - - It("rejects standard extended methods with default configuration", func() { - // We expect that this test will fail when UHV is enabled. By - // default, UHV does not restrict any HTTP methods. However, even - // when UHV is configured to restrict HTTP methods (using this - // option): - // https://github.com/envoyproxy/envoy/blob/release/v1.30/api/envoy/extensions/http/header_validators/envoy_default/v3/header_validator.proto#L143 - // it will still allow methods from this list: - // https://github.com/envoyproxy/envoy/blob/0b9f67e7f71bcba3ff49575dc61676478cb68614/source/extensions/http/header_validators/envoy_default/header_validator.cc#L53-L93 - // which is substantially more methods than the original list of - // allowed methods: - // https://github.com/envoyproxy/envoy/blob/2970ddbd4ade787dd51dfbe605ae2e8c5d8ffcf7/source/common/http/http1/balsa_parser.cc#L54 - waitUntilProxyIsRunning() - Expect(testutils.DefaultHttpClient.Do(buildRequest("LABEL"))).Should(matchers.HaveStatusCode(http.StatusBadRequest)) - }) - - It("allows custom methods when DisableHttp1MethodValidation is set", func() { - testContext.PatchDefaultGateway(func(gateway *gatewayv1.Gateway) *gatewayv1.Gateway { - gateway.GatewayType = &gatewayv1.Gateway_HttpGateway{ - HttpGateway: &gatewayv1.HttpGateway{ - Options: &gloov1.HttpListenerOptions{ - HeaderValidationSettings: &header_validation.HeaderValidationSettings{ - HeaderMethodValidation: &header_validation.HeaderValidationSettings_DisableHttp1MethodValidation{}, - }, - }, - }, - } - return gateway - }) - testContext.EventuallyProxyAccepted() - waitUntilProxyIsRunning() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(buildRequest("CUSTOMMETHOD"))).Should(matchers.HaveStatusCode(http.StatusOK)) - }, "10s", "1s").Should(Succeed()) - - }) - }) - -}) diff --git a/test/e2e/headers_test.go b/test/e2e/headers_test.go deleted file mode 100644 index 957d8148c56..00000000000 --- a/test/e2e/headers_test.go +++ /dev/null @@ -1,750 +0,0 @@ -package e2e_test - -import ( - "context" - "encoding/json" - "fmt" - "html" - "io" - "net/http" - "os" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/golang/protobuf/ptypes/wrappers" - "github.com/solo-io/gloo/pkg/utils/api_conversion" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - envoytrace_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/trace/v3" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/hcm" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/headers" - static_plugin_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/tracing" - "github.com/solo-io/gloo/test/e2e" - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/services/envoy" - "github.com/solo-io/gloo/test/testutils" - "github.com/solo-io/gloo/test/v1helpers" - envoycore_sk "github.com/solo-io/solo-kit/pkg/api/external/envoy/api/v2/core" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - coreV1 "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -var _ = Describe("HeaderManipulation", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Secrets in HeaderManipulation", func() { - BeforeEach(func() { - // put a secret in `writeNamespace` so we have it in the snapshot - // The upstream is in `default` so when we enforce that secrets + upstream namespaces match, it should not be allowed - forbiddenSecret := &gloov1.Secret{ - Kind: &gloov1.Secret_Header{ - Header: &gloov1.HeaderSecret{ - Headers: map[string]string{ - "Authorization": "basic dXNlcjpwYXNzd29yZA==", - }, - }, - }, - Metadata: &coreV1.Metadata{ - Name: "foo", - Namespace: writeNamespace, - }, - } - // Create a secret in the same namespace as the upstream - allowedSecret := &gloov1.Secret{ - Kind: &gloov1.Secret_Header{ - Header: &gloov1.HeaderSecret{ - Headers: map[string]string{ - "Authorization": "basic dXNlcjpwYXNzd29yZA==", - }, - }, - }, - Metadata: &coreV1.Metadata{ - Name: "goodsecret", - Namespace: testContext.TestUpstream().Upstream.GetMetadata().GetNamespace(), - }, - } - headerManipVsBuilder := helpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/endpoint"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream) - - goodVS := headerManipVsBuilder.Clone(). - WithName("good"). - WithDomain("custom-domain.com"). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{HeaderManipulation: &headers.HeaderManipulation{ - RequestHeadersToAdd: []*envoycore_sk.HeaderValueOption{{HeaderOption: &envoycore_sk.HeaderValueOption_HeaderSecretRef{HeaderSecretRef: allowedSecret.GetMetadata().Ref()}, - Append: &wrappers.BoolValue{Value: true}}}, - }}). - Build() - badVS := headerManipVsBuilder.Clone(). - WithName("bad"). - WithDomain("another-domain.com"). - WithVirtualHostOptions( - &gloov1.VirtualHostOptions{HeaderManipulation: &headers.HeaderManipulation{ - RequestHeadersToAdd: []*envoycore_sk.HeaderValueOption{{HeaderOption: &envoycore_sk.HeaderValueOption_HeaderSecretRef{HeaderSecretRef: forbiddenSecret.GetMetadata().Ref()}, - Append: &wrappers.BoolValue{Value: true}}}, - }}). - Build() - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{goodVS, badVS} - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{forbiddenSecret, allowedSecret} - }) - - AfterEach(func() { - os.Unsetenv(api_conversion.MatchingNamespaceEnv) - }) - - Context("With matching not enforced", func() { - - BeforeEach(func() { - os.Setenv(api_conversion.MatchingNamespaceEnv, "false") - }) - - It("Accepts all virtual services", func() { - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, "bad", clients.ReadOpts{}) - return vs, err - }) - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testContext.TestClients().VirtualServiceClient.Read(writeNamespace, "good", clients.ReadOpts{}) - }) - }) - - }) - Context("With matching enforced", func() { - - BeforeEach(func() { - os.Setenv(api_conversion.MatchingNamespaceEnv, "true") - }) - - It("rejects the virtual service where the secret is in another namespace and accepts virtual service with a matching namespace", func() { - helpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - return testContext.TestClients().VirtualServiceClient.Read(writeNamespace, "bad", clients.ReadOpts{}) - }) - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testContext.TestClients().VirtualServiceClient.Read(writeNamespace, "good", clients.ReadOpts{}) - }) - }) - - }) - }) - - Context("Validates forbidden headers", func() { - var headerManipVsBuilder *helpers.VirtualServiceBuilder - - BeforeEach(func() { - headerManipVsBuilder = helpers.NewVirtualServiceBuilder(). - WithNamespace(writeNamespace). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/endpoint"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream) - - allowedHeaderManipulationVS := headerManipVsBuilder.Clone(). - WithName("allowed-header-manipulation"). - WithDomain("another-domain.com"). - WithVirtualHostOptions( - &gloov1.VirtualHostOptions{HeaderManipulation: &headers.HeaderManipulation{ - RequestHeadersToAdd: []*envoycore_sk.HeaderValueOption{ - {HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{Key: "some-header", Value: "some-value"}}, - Append: &wrappers.BoolValue{Value: true}}}, - }}). - Build() - - forbiddenHeaderManipulationVS := headerManipVsBuilder.Clone(). - WithName("forbidden-header-manipulation"). - WithDomain("yet-another-domain.com"). - WithVirtualHostOptions( - &gloov1.VirtualHostOptions{HeaderManipulation: &headers.HeaderManipulation{ - RequestHeadersToAdd: []*envoycore_sk.HeaderValueOption{ - {HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{Key: ":path", Value: "some-value"}}, - Append: &wrappers.BoolValue{Value: true}}}, - }}). - Build() - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{allowedHeaderManipulationVS, forbiddenHeaderManipulationVS} - }) - - It("Allows non forbidden headers", func() { - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, "allowed-header-manipulation", clients.ReadOpts{}) - return vs, err - }) - }) - - It("Does not allow forbidden headers", func() { - helpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, "forbidden-header-manipulation", clients.ReadOpts{}) - return vs, err - }) - }) - }) - - Context("Header mutation order (most_specific_header_mutations_wins)", func() { - const ( - responseHeader = "Response-Header" - requestHeader = "Request-Header" - ) - - rtWithOptions := func(shouldAppend bool) *v1.RouteTable { - rtName := "append" - if !shouldAppend { - rtName = "overwrite" - } - - opts := &gloov1.RouteOptions{ - HeaderManipulation: &headers.HeaderManipulation{ - RequestHeadersToAdd: []*envoycore_sk.HeaderValueOption{{ - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{Key: requestHeader, Value: "route-header"}, - }, - Append: &wrappers.BoolValue{Value: shouldAppend}, - }}, - ResponseHeadersToAdd: []*headers.HeaderValueOption{{ - Header: &headers.HeaderValue{Key: responseHeader, Value: "route-header"}, - Append: &wrappers.BoolValue{Value: shouldAppend}, - }}, - }, - } - route := helpers.NewRouteBuilder(). - WithName(fmt.Sprintf("%s-route", rtName)). - WithPrefixMatcher(fmt.Sprintf("/%s", rtName)). - WithRouteOptions(opts). - WithRouteActionToUpstreamRef(testContext.TestUpstream().Upstream.GetMetadata().Ref()). - Build() - - return helpers.NewRouteTableBuilder(). - WithName(fmt.Sprintf("%s-rt", rtName)). - WithNamespace(writeNamespace). - WithRoute("route", route). - Build() - } - - vsWithOptions := func(shouldAppend bool, rtRef []*coreV1.ResourceRef) *v1.VirtualService { - vsName := "append" - if !shouldAppend { - vsName = "overwrite" - } - - opts := &gloov1.VirtualHostOptions{HeaderManipulation: &headers.HeaderManipulation{ - RequestHeadersToAdd: []*envoycore_sk.HeaderValueOption{{ - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{Key: requestHeader, Value: "vs-header"}, - }, - Append: &wrappers.BoolValue{Value: shouldAppend}, - }}, - ResponseHeadersToAdd: []*headers.HeaderValueOption{{ - Header: &headers.HeaderValue{Key: responseHeader, Value: "vs-header"}, - Append: &wrappers.BoolValue{Value: shouldAppend}, - }}, - }} - vsBuilder := helpers.NewVirtualServiceBuilder(). - WithName(fmt.Sprintf("%s-vs", vsName)). - WithNamespace(writeNamespace). - WithDomain(fmt.Sprintf("%s.com", vsName)). - WithVirtualHostOptions(opts) - - for _, ref := range rtRef { - routeRef := ref - vsBuilder.WithRouteDelegateActionRef(fmt.Sprintf("%s-route", ref.GetName()), routeRef) - } - - return vsBuilder.Build() - } - - // headerCheck checks that the request and response headers are as expected for a given host and path. - headerCheck := func(appendVs, appendRoute bool, expectedReqHeaders, expectedResHeaders []string) { - // The `append.com` host is configured to append headers, while the `overwrite.com` domain is configured to overwrite headers. - host := "append.com" - if !appendVs { - host = "overwrite.com" - } - - // The `append` path/route is configured to append headers, while the `overwrite` route is configured to overwrite headers. - path := "append" - if !appendRoute { - path = "overwrite" - } - - Eventually(func(g Gomega) { - requestBuilder := testContext.GetHttpRequestBuilder().WithHost(host).WithPath(path) - res, err := testutils.DefaultHttpClient.Do(requestBuilder.Build()) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(res).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Custom: testmatchers.ConsistOfHeaders(http.Header{responseHeader: expectedResHeaders}), - })) - - select { - case req := <-testContext.TestUpstream().C: - g.Expect(req.Headers).To(HaveKeyWithValue(requestHeader, ConsistOf(expectedReqHeaders))) - case <-time.After(time.Second * 5): - Fail("request didn't make it upstream") - } - }, "5s", "0.5s").Should(Succeed()) - } - - BeforeEach(func() { - appendRT := rtWithOptions(true) - overwriteRT := rtWithOptions(false) - - overwriteVS := vsWithOptions(false, []*coreV1.ResourceRef{appendRT.Metadata.Ref(), overwriteRT.Metadata.Ref()}) - appendVS := vsWithOptions(true, []*coreV1.ResourceRef{appendRT.Metadata.Ref(), overwriteRT.Metadata.Ref()}) - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{overwriteVS, appendVS} - testContext.ResourcesToCreate().RouteTables = v1.RouteTableList{overwriteRT, appendRT} - }) - - When("most_specific_header_mutations_wins is nil", func() { - DescribeTable("appends headers correctly", headerCheck, - Entry("appends route and vhost level headers", true, true, []string{"route-header", "vs-header"}, []string{"vs-header", "route-header"}), - Entry("appends route and vhost level headers when route is set to overwrite headers", true, false, []string{"route-header", "vs-header"}, []string{"route-header", "vs-header"}), - Entry("vhost level header overwrites route level header", false, true, []string{"vs-header"}, []string{"vs-header"}), - Entry("vhost level header overwrites route level header when route is set to overwrite headers", false, false, []string{"vs-header"}, []string{"vs-header"}), - ) - }) - - When("most_specific_header_mutations_wins is false", func() { - BeforeEach(func() { - gw := defaults.DefaultGateway(writeNamespace) - gw.RouteOptions = &gloov1.RouteConfigurationOptions{ - MostSpecificHeaderMutationsWins: &wrappers.BoolValue{Value: false}, - } - testContext.ResourcesToCreate().Gateways = v1.GatewayList{gw} - }) - - DescribeTable("appends headers correctly", headerCheck, - Entry("appends route and vhost level headers", true, true, []string{"route-header", "vs-header"}, []string{"route-header", "vs-header"}), - Entry("appends route and vhost level headers when route is set to overwrite headers", true, false, []string{"route-header", "vs-header"}, []string{"route-header", "vs-header"}), - Entry("vhost level header overwrites route level header", false, true, []string{"vs-header"}, []string{"vs-header"}), - Entry("vhost level header overwrites route level header when route is set to overwrite headers", false, false, []string{"vs-header"}, []string{"vs-header"}), - ) - }) - - When("most_specific_header_mutations_wins is true", func() { - BeforeEach(func() { - gw := defaults.DefaultGateway(writeNamespace) - gw.RouteOptions = &gloov1.RouteConfigurationOptions{ - MostSpecificHeaderMutationsWins: &wrappers.BoolValue{Value: true}, - } - testContext.ResourcesToCreate().Gateways = v1.GatewayList{gw} - }) - - DescribeTable("appends headers correctly", headerCheck, - Entry("appends route and vhost level headers", true, true, []string{"route-header", "vs-header"}, []string{"route-header", "vs-header"}), - Entry("appends route and vhost level headers when vhost is set to overwrite headers", false, true, []string{"route-header", "vs-header"}, []string{"route-header", "vs-header"}), - Entry("route level header overwrites vhost level header", true, false, []string{"route-header"}, []string{"route-header"}), - Entry("route level header overwrites vhost level header when vhost is set to overwrite headers", false, false, []string{"route-header"}, []string{"route-header"}), - ) - }) - }) - - Describe("Early Header Manipulation", func() { - var ( - httpClient *http.Client - requestBuilder *testutils.HttpRequestBuilder - ) - - type HeadersResponse struct { - Headers map[string][]string `json:"headers"` - } - - prepareEHMTestContext := func(testContext *e2e.TestContext, ehm *headers.EarlyHeaderManipulation) { - getHCMSettings(testContext).EarlyHeaderManipulation = ehm - } - - makeHeadersRequest := func(inHeaders map[string]string) (*HeadersResponse, error) { - req := requestBuilder. - WithHeader("Accept", "application/json"). - WithPath("headers"). - WithHeaders(inHeaders). - Build() - - res, err := httpClient.Do(req) - if err != nil { - return &HeadersResponse{}, err - } - - if res.StatusCode != http.StatusOK { - return &HeadersResponse{}, fmt.Errorf("unexpected status code %d", res.StatusCode) - } - - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return &HeadersResponse{}, err - } - - var headerResponse HeadersResponse - err = json.Unmarshal(body, &headerResponse) - if err != nil { - return &HeadersResponse{}, err - } - - return &headerResponse, nil - } - - Context("No mutations", func() { - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.SetUpstreamGenerator(v1helpers.NewTestHttpUpstreamWithHttpbin) - testContext.BeforeEach() - - prepareEHMTestContext(testContext, nil) - - httpClient = testutils.DefaultClientBuilder().Build() - requestBuilder = testContext.GetHttpRequestBuilder() - }) - - It("Should do nothing if no mutations are present", func() { - Eventually(func(g Gomega) { - headersResponse, err := makeHeadersRequest(map[string]string{ - "X-Keep": "foo", - "X-Drop": "bar", - }) - g.Expect(err).NotTo(HaveOccurred()) - - // the headers should be unchanged - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-Keep", ConsistOf("foo")), - "Expected header X-Keep to be present") - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-Drop", ConsistOf("bar")), - "Expected header X-Drop to be present when no mutations are present") - g.Expect(headersResponse.Headers).NotTo(HaveKey("X-Add"), - "Expected header X-Add to not be present when no mutations are present") - }, "5s", "0.5s").Should(Succeed()) - }) - }) - - Context("Add/remove manipulations", func() { - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.SetUpstreamGenerator(v1helpers.NewTestHttpUpstreamWithHttpbin) - testContext.BeforeEach() - - prepareEHMTestContext(testContext, &headers.EarlyHeaderManipulation{ - HeadersToAdd: []*envoycore_sk.HeaderValueOption{ - { - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{ - Key: "X-Add", - Value: "baz", - }, - }, - Append: &wrappers.BoolValue{Value: true}, - }, - { - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{ - Key: "X-Append", - Value: "baz", - }, - }, - Append: &wrappers.BoolValue{Value: true}, - }, - { - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{ - Key: "X-Overwrite", - Value: "baz", - }, - }, - Append: &wrappers.BoolValue{Value: false}, - }, - }, - HeadersToRemove: []string{"X-Drop"}, - }) - - httpClient = testutils.DefaultClientBuilder().Build() - requestBuilder = testContext.GetHttpRequestBuilder() - }) - - It("Should append as expected", func() { - Eventually(func(g Gomega) { - headersResponse, err := makeHeadersRequest(map[string]string{ - "X-Append": "foo", - "X-Overwrite": "bar", - "X-Drop": "baz", - }) - g.Expect(err).NotTo(HaveOccurred()) - - // the headers should be as expected - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-Add", ConsistOf("baz")), - "Expected header X-Add to be present") - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-Append", ConsistOf("foo", "baz")), - "Expected header X-Add to be present") - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-Overwrite", ConsistOf("baz")), - "Expected header X-Exists two have two values") - g.Expect(headersResponse.Headers).NotTo(HaveKey("X-Drop"), - "Expected header X-Drop to be removed") - }, "5s", "0.5s").Should(Succeed()) - }) - }) - - Context("Manipulation with secrets", func() { - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.SetUpstreamGenerator(v1helpers.NewTestHttpUpstreamWithHttpbin) - testContext.BeforeEach() - - prepareEHMTestContext(testContext, &headers.EarlyHeaderManipulation{ - HeadersToAdd: []*envoycore_sk.HeaderValueOption{ - { - HeaderOption: &envoycore_sk.HeaderValueOption_HeaderSecretRef{ - HeaderSecretRef: &coreV1.ResourceRef{ - Name: "secret", - Namespace: writeNamespace, - }, - }, - Append: &wrappers.BoolValue{Value: true}, - }, - }, - }) - - secret := &gloov1.Secret{ - Kind: &gloov1.Secret_Header{ - Header: &gloov1.HeaderSecret{ - Headers: map[string]string{ - "X-Secret": "something super secret", - }, - }, - }, - Metadata: &coreV1.Metadata{ - Name: "secret", - Namespace: writeNamespace, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{secret} - - httpClient = testutils.DefaultClientBuilder().Build() - requestBuilder = testContext.GetHttpRequestBuilder() - }) - - It("Should have the secret", func() { - Eventually(func(g Gomega) { - headersResponse, err := makeHeadersRequest(map[string]string{ - "X-Keep": "foo", - "X-Drop": "bar", - }) - g.Expect(err).NotTo(HaveOccurred()) - - // the headers should be as expected - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-Secret", - ConsistOf("something super secret")), - "Expected header X-Secret to be present with secret value") - }, "5s", "0.5s").Should(Succeed()) - }) - }) - - // A customer reported that normal header manipulation was happening after the tracing headers were added. - // They desired that they be able to override the tracing headers with their own headers. - // Setting the tracing headers earlier allows for the override to happen. - // This test ensures that the interaction between the two works as expected. - Context("Interaction with Zipkin tracing", func() { - var ( - zipkinInstance *envoy.Instance - //zipkinUpstream *v1helpers.TestUpstream - ) - - prepareZipkinTracingTestContext := func(testContext *e2e.TestContext, upstream *gloov1.Upstream) { - getHCMSettings(testContext).Tracing = &tracing.ListenerTracingSettings{ - ProviderConfig: &tracing.ListenerTracingSettings_ZipkinConfig{ - ZipkinConfig: &envoytrace_gloo.ZipkinConfig{ - CollectorCluster: &envoytrace_gloo.ZipkinConfig_CollectorUpstreamRef{ - CollectorUpstreamRef: &core.ResourceRef{ - Name: upstream.GetMetadata().GetName(), - Namespace: writeNamespace, - }, - }, - CollectorEndpoint: zipkinCollectionPath, - CollectorEndpointVersion: envoytrace_gloo.ZipkinConfig_HTTP_JSON, - }, - }, - } - } - - startCancellableTracingServer := func(serverContext context.Context, address string) { - // Start a dummy server listening on 9411 for tracing requests - tracingCollectorHandler := http.NewServeMux() - tracingCollectorHandler.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - Expect(r.URL.Path).To(Equal(zipkinCollectionPath)) - fmt.Fprintf(w, "Dummy tracing Collector received request on - %q", html.EscapeString(r.URL.Path)) - })) - - tracingServer := &http.Server{ - Addr: address, - Handler: tracingCollectorHandler, - } - - // Start a goroutine to handle requests - go func() { - defer GinkgoRecover() - if err := tracingServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - } - }() - - // Start a goroutine to shutdown the server - go func(serverCtx context.Context) { - defer GinkgoRecover() - - <-serverCtx.Done() - // tracingServer.Shutdown hangs with opentelemetry tests, probably - // because the agent leaves the connection open. There's no need for a - // graceful shutdown anyway, so just force it using Close() instead - tracingServer.Close() - }(serverContext) - } - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.SetUpstreamGenerator(v1helpers.NewTestHttpUpstreamWithHttpbin) - testContext.BeforeEach() - - httpClient = testutils.DefaultClientBuilder().Build() - requestBuilder = testContext.GetHttpRequestBuilder() - - // create Zipkin tracing collector - // the tracing extension expects the zipkin collector to be on port 9411 - // which makes this whole process a bit more complicated - zipkinInstance = envoyFactory.NewInstance() - startCancellableTracingServer(testContext.Ctx(), - fmt.Sprintf("%s:%d", zipkinInstance.LocalAddr(), tracingCollectorPort)) - - // create tracing collector upstream - zipkinUpstream := &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: tracingCollectorUpstreamName, - Namespace: writeNamespace, - }, - UpstreamType: &gloov1.Upstream_Static{ - Static: &static_plugin_gloo.UpstreamSpec{ - Hosts: []*static_plugin_gloo.Host{ - { - Addr: zipkinInstance.LocalAddr(), - Port: tracingCollectorPort, - }, - }, - }, - }, - } - - testContext.ResourcesToCreate().Upstreams = gloov1.UpstreamList{ - testContext.TestUpstream().Upstream, - zipkinUpstream, - } - - // add the zipkin tracing configuration to the test context - prepareZipkinTracingTestContext(testContext, zipkinUpstream) - }) - - Context("Zipkin/B3 headers without transforms", func() { - BeforeEach(func() { - prepareEHMTestContext(testContext, nil) - }) - - It("Zipkin/B3 headers should make without transforms", func() { - Eventually(func(g Gomega) { - headersResponse, err := makeHeadersRequest(map[string]string{}) - g.Expect(err).NotTo(HaveOccurred()) - - // the headers should be as expected - g.Expect(headersResponse.Headers).To(HaveKey("X-B3-Traceid"), - "Expected header X-B3-Traceid to be present") - g.Expect(headersResponse.Headers).To(HaveKey("X-B3-Spanid"), - "Expected header X-B3-Spanid to be present") - }, "5s", "0.5s").Should(Succeed()) - }) - }) - - Context("Zipkin/B3 headers with transforms", func() { - BeforeEach(func() { - prepareEHMTestContext(testContext, &headers.EarlyHeaderManipulation{ - HeadersToAdd: []*envoycore_sk.HeaderValueOption{ - { - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{ - Key: "X-B3-Traceid", - Value: "%REQ(x-override-traceid)%", - }, - }, - }, - { - HeaderOption: &envoycore_sk.HeaderValueOption_Header{ - Header: &envoycore_sk.HeaderValue{ - Key: "X-B3-Spanid", - Value: "%REQ(x-override-spanid)%", - }, - }, - }, - }, - }) - }) - - It("Should be able to override Zipkin/B3 headers", func() { - Eventually(func(g Gomega) { - headersResponse, err := makeHeadersRequest(map[string]string{ - "x-override-traceid": "traceid", - "x-override-spanid": "spanid", - }) - g.Expect(err).NotTo(HaveOccurred()) - - // the headers should be as expected - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-B3-Traceid", ConsistOf("traceid")), - "Expected header X-B3-Traceid to be present with overridden value") - g.Expect(headersResponse.Headers).To(HaveKeyWithValue("X-B3-Spanid", ConsistOf("spanid")), - "Expected header X-B3-Spanid to be present with overridden value") - }, "5s", "0.5s").Should(Succeed()) - }) - }) - }) - }) -}) - -func getHCMSettings(testContext *e2e.TestContext) *hcm.HttpConnectionManagerSettings { - gateway := testContext.ResourcesToCreate().Gateways[0] - Expect(gateway).NotTo(BeNil()) - httpGateway := gateway.GetHttpGateway() - Expect(httpGateway).NotTo(BeNil()) - - listenerOptions := httpGateway.GetOptions() - if listenerOptions == nil { - listenerOptions = &gloov1.HttpListenerOptions{} - httpGateway.Options = listenerOptions - } - - hcmSettings := listenerOptions.GetHttpConnectionManagerSettings() - if hcmSettings == nil { - hcmSettings = &hcm.HttpConnectionManagerSettings{} - listenerOptions.HttpConnectionManagerSettings = hcmSettings - } - - return hcmSettings -} diff --git a/test/e2e/health_checks_test.go b/test/e2e/health_checks_test.go deleted file mode 100644 index 611fbf50dd6..00000000000 --- a/test/e2e/health_checks_test.go +++ /dev/null @@ -1,384 +0,0 @@ -package e2e_test - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "regexp" - "time" - - "github.com/solo-io/gloo/test/services/envoy" - - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - "github.com/solo-io/gloo/test/testutils" - - v3 "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/core/v3" - - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - "github.com/golang/protobuf/ptypes/wrappers" - - envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - "github.com/golang/protobuf/ptypes/duration" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - "github.com/solo-io/gloo/pkg/utils/api_conversion" - gwdefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/translator" - . "github.com/solo-io/gloo/test/gomega" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" -) - -var _ = Describe("Health Checks", func() { - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - tu *v1helpers.TestUpstream - ) - - BeforeEach(func() { - testutils.ValidateRequirementsAndNotifyGinkgo( - testutils.LinuxOnly("Relies on FDS"), - ) - - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - ro := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableGateway: false, - DisableUds: true, - // test relies on FDS to discover the grpc spec via reflection - DisableFds: false, - }, - Settings: &gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - // https://github.com/solo-io/gloo/issues/7577 - RemoveUnusedFilters: &wrappers.BoolValue{Value: false}, - }, - Discovery: &gloov1.Settings_DiscoveryOptions{ - FdsMode: gloov1.Settings_DiscoveryOptions_BLACKLIST, - }, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - err := envoyInstance.RunWithRole(writeNamespace+"~"+gwdefaults.GatewayProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - err = helpers.WriteDefaultGateways(writeNamespace, testClients.GatewayClient) - Expect(err).NotTo(HaveOccurred(), "Should be able to write default gateways") - }) - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - basicReq := func(b []byte) func() (string, error) { - return func() (string, error) { - // send a request with a body - var buf bytes.Buffer - buf.Write(b) - res, err := http.Post(fmt.Sprintf("http://%s:%d/test", "localhost", envoyInstance.HttpPort), "application/json", &buf) - if err != nil { - return "", err - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - return string(body), err - } - } - - Context("regression for config", func() { - - BeforeEach(func() { - - tu = v1helpers.NewTestGRPCUpstream(ctx, envoyInstance.LocalAddr(), 1) - _, err := testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - }) - - tests := []struct { - Name string - Check *envoy_config_core_v3.HealthCheck - }{ - { - Name: "http", - Check: &envoy_config_core_v3.HealthCheck{ - HealthChecker: &envoy_config_core_v3.HealthCheck_HttpHealthCheck_{ - HttpHealthCheck: &envoy_config_core_v3.HealthCheck_HttpHealthCheck{ - Path: "xyz", - }, - }, - }, - }, - { - Name: "tcp", - Check: &envoy_config_core_v3.HealthCheck{ - HealthChecker: &envoy_config_core_v3.HealthCheck_TcpHealthCheck_{ - TcpHealthCheck: &envoy_config_core_v3.HealthCheck_TcpHealthCheck{ - Send: &envoy_config_core_v3.HealthCheck_Payload{ - Payload: &envoy_config_core_v3.HealthCheck_Payload_Text{ - Text: "AAAA", - }, - }, - Receive: []*envoy_config_core_v3.HealthCheck_Payload{ - { - Payload: &envoy_config_core_v3.HealthCheck_Payload_Text{ - Text: "AAAA", - }, - }, - }, - }, - }, - }, - }, - } - - for _, envoyHealthCheckTest := range tests { - envoyHealthCheckTest := envoyHealthCheckTest - - It(envoyHealthCheckTest.Name, func() { - // by default we disable panic mode - // this purpose of this test is to verify panic modes behavior so we need to enable it - envoyInstance.EnablePanicMode() - - // get the upstream - us, err := testClients.UpstreamClient.Read(tu.Upstream.Metadata.Namespace, tu.Upstream.Metadata.Name, clients.ReadOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // update the health check configuration - envoyHealthCheckTest.Check.Timeout = translator.DefaultHealthCheckTimeout - envoyHealthCheckTest.Check.Interval = translator.DefaultHealthCheckInterval - envoyHealthCheckTest.Check.HealthyThreshold = translator.DefaultThreshold - envoyHealthCheckTest.Check.UnhealthyThreshold = translator.DefaultThreshold - - // persist the health check configuration - us.HealthChecks, err = api_conversion.ToGlooHealthCheckList([]*envoy_config_core_v3.HealthCheck{envoyHealthCheckTest.Check}) - Expect(err).NotTo(HaveOccurred()) - - _, err = testClients.UpstreamClient.Write(us, clients.WriteOpts{OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - - vs := getGrpcTranscoderVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // ensure that a request fails the health check but is handled by the upstream anyway - testRequest := basicReq([]byte(`"foo"`)) - Eventually(testRequest, 30, 1).Should(Equal(`{"str":"foo"}`)) - - Eventually(tu.C, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - } - - It("outlier detection", func() { - us, err := testClients.UpstreamClient.Read(tu.Upstream.Metadata.Namespace, tu.Upstream.Metadata.Name, clients.ReadOpts{}) - Expect(err).NotTo(HaveOccurred()) - us.OutlierDetection = api_conversion.ToGlooOutlierDetection(&envoy_config_cluster_v3.OutlierDetection{ - Interval: &duration.Duration{Seconds: 1}, - }) - - _, err = testClients.UpstreamClient.Write(us, clients.WriteOpts{ - OverwriteExisting: true, - }) - Expect(err).NotTo(HaveOccurred()) - - vs := getGrpcTranscoderVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - body := []byte(`"foo"`) - - testRequest := basicReq(body) - - Eventually(testRequest, 30, 1).Should(Equal(`{"str":"foo"}`)) - - Eventually(tu.C, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Receive(PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPCRequest": PointTo(MatchFields(IgnoreExtras, Fields{"Str": Equal("foo")})), - })))) - }) - }) - - // This test can be run locally by setting INVALID_TEST_REQS=run, to bypass this ValidateRequirements method in the BeforeEach - Context("translates and persists health checkers", func() { - var healthCheck *envoy_config_core_v3.HealthCheck - - getUpstreamWithMethod := func(method v3.RequestMethod) *v1helpers.TestUpstream { - upstream := v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - healthCheck = &envoy_config_core_v3.HealthCheck{ - Timeout: translator.DefaultHealthCheckTimeout, - Interval: translator.DefaultHealthCheckInterval, - HealthyThreshold: translator.DefaultThreshold, - UnhealthyThreshold: translator.DefaultThreshold, - HealthChecker: &envoy_config_core_v3.HealthCheck_HttpHealthCheck_{ - HttpHealthCheck: &envoy_config_core_v3.HealthCheck_HttpHealthCheck{ - Path: "health", - Method: envoy_config_core_v3.RequestMethod(method), - }, - }, - } - var err error - upstream.Upstream.HealthChecks, err = api_conversion.ToGlooHealthCheckList([]*envoy_config_core_v3.HealthCheck{healthCheck}) - Expect(err).To(Not(HaveOccurred())) - return upstream - } - - //Patch the upstream with a given http method then check for expected envoy config - patchUpstreamAndCheckConfig := func(method v3.RequestMethod, expectedConfig string) { - err := helpers.PatchResource(ctx, tu.Upstream.Metadata.Ref(), func(resource resources.Resource) resources.Resource { - upstream := resource.(*gloov1.Upstream) - upstream.GetHealthChecks()[0].GetHttpHealthCheck().Method = method - return upstream - }, testClients.UpstreamClient.BaseClient()) - Expect(err).ToNot(HaveOccurred()) - - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.UpstreamClient.Read(tu.Upstream.Metadata.Namespace, tu.Upstream.Metadata.Name, clients.ReadOpts{}) - }) - - Eventually(func(g Gomega) { - envoyConfig, err := envoyInstance.ConfigDump() - g.Expect(err).To(Not(HaveOccurred())) - - // Get "http_health_check" and its contents out of the envoy config dump - http_health_check := regexp.MustCompile(`(?sU)("http_health_check": {).*(})`).FindString(envoyConfig) - g.Expect(http_health_check).To(ContainSubstring(expectedConfig)) - }, "10s", "1s").ShouldNot(HaveOccurred()) - } - It("with different methods", func() { - tu = getUpstreamWithMethod(v3.RequestMethod_METHOD_UNSPECIFIED) - - _, err := testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - _, err = testClients.VirtualServiceClient.Write(getTrivialVirtualServiceForUpstream(writeNamespace, tu.Upstream.Metadata.Ref()), clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - helpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.ProxyClient.Read(writeNamespace, gwdefaults.GatewayProxyName, clients.ReadOpts{}) - }) - - By("default", func() { patchUpstreamAndCheckConfig(v3.RequestMethod_METHOD_UNSPECIFIED, `"path": "health`) }) - By("POST", func() { patchUpstreamAndCheckConfig(v3.RequestMethod_POST, `"method": "POST"`) }) - By("GET", func() { patchUpstreamAndCheckConfig(v3.RequestMethod_GET, `"method": "GET"`) }) - - //We expect a health checker with the CONNECT method to be rejected and the prior health check to be retained - By("CONNECT", func() { patchUpstreamAndCheckConfig(v3.RequestMethod_CONNECT, `"method": "GET"`) }) - }) - }) - - Context("e2e + GRPC", func() { - - BeforeEach(func() { - - tu = v1helpers.NewTestGRPCUpstream(ctx, envoyInstance.LocalAddr(), 5) - _, err := testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func() error { return envoyInstance.DisablePanicMode() }, time.Second*5, time.Second/4).Should(BeNil()) - - tu = v1helpers.NewTestGRPCUpstream(ctx, envoyInstance.LocalAddr(), 5) - _, err = testClients.UpstreamClient.Write(tu.Upstream, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - us, err := testClients.UpstreamClient.Read(tu.Upstream.Metadata.Namespace, tu.Upstream.Metadata.Name, clients.ReadOpts{}) - Expect(err).NotTo(HaveOccurred()) - - us.HealthChecks, err = api_conversion.ToGlooHealthCheckList([]*envoy_config_core_v3.HealthCheck{ - { - Timeout: translator.DefaultHealthCheckTimeout, - Interval: translator.DefaultHealthCheckInterval, - UnhealthyThreshold: translator.DefaultThreshold, - HealthyThreshold: translator.DefaultThreshold, - HealthChecker: &envoy_config_core_v3.HealthCheck_GrpcHealthCheck_{ - GrpcHealthCheck: &envoy_config_core_v3.HealthCheck_GrpcHealthCheck{ - ServiceName: "TestService", - }, - }, - }, - }) - Expect(err).NotTo(HaveOccurred()) - - _, err = testClients.UpstreamClient.Write(us, clients.WriteOpts{ - OverwriteExisting: true, - }) - Expect(err).NotTo(HaveOccurred()) - - vs := getGrpcTranscoderVs(writeNamespace, tu.Upstream.Metadata.Ref()) - _, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - }) - - It("Fail all but one GRPC health check", func() { - liveService := tu.FailGrpcHealthCheck() - body := []byte(`"foo"`) - testRequest := basicReq(body) - - numRequests := 5 - - for range numRequests { - Eventually(testRequest, 30, 1).Should(Equal(`{"str":"foo"}`)) - } - - for range numRequests { - select { - case v := <-tu.C: - Expect(v.Port).To(Equal(liveService.Port)) - case <-time.After(5 * time.Second): - Fail("channel did not receive proper response in time") - } - } - }) - }) - -}) - -func getGrpcTranscoderVs(writeNamespace string, usRef *core.ResourceRef) *gatewayv1.VirtualService { - return &gatewayv1.VirtualService{ - Metadata: &core.Metadata{ - Name: "default", - Namespace: writeNamespace, - }, - VirtualHost: &gatewayv1.VirtualHost{ - Routes: []*gatewayv1.Route{ - { - Matchers: []*matchers.Matcher{{ - PathSpecifier: &matchers.Matcher_Prefix{ - // the grpc_json transcoding filter clears the cache so it no longer would match on /test (this can be configured) - Prefix: "/", - }, - }}, - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: usRef, - }, - }, - }, - }, - }, - }, - }, - }, - } -} diff --git a/test/e2e/http_tunneling_test.go b/test/e2e/http_tunneling_test.go deleted file mode 100644 index 91b347b5812..00000000000 --- a/test/e2e/http_tunneling_test.go +++ /dev/null @@ -1,505 +0,0 @@ -package e2e_test - -import ( - "bufio" - "bytes" - "context" - "crypto/tls" - "encoding/base64" - "errors" - "fmt" - "io" - "net" - "net/http" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/testutils" - - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/solo-io/gloo/test/v1helpers" - - "github.com/golang/protobuf/ptypes/wrappers" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - static_plugin_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - testhelpers "github.com/solo-io/gloo/test/helpers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" -) - -var _ = Describe("tunneling", func() { - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - envoyInstance *envoy.Instance - up *gloov1.Upstream - tuPort uint32 - vs *gatewayv1.VirtualService - tlsRequired v1helpers.UpstreamTlsRequired = v1helpers.NO_TLS - tlsHttpConnect bool - ) - - checkProxy := func() { - // ensure the proxy is created - Eventually(func() (*gloov1.Proxy, error) { - return testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{}) - }, "5s", "0.1s").ShouldNot(BeNil()) - } - - checkVirtualService := func(testVs *gatewayv1.VirtualService) { - Eventually(func() (*gatewayv1.VirtualService, error) { - var err error - vs, err = testClients.VirtualServiceClient.Read(testVs.Metadata.GetNamespace(), testVs.Metadata.GetName(), clients.ReadOpts{}) - return vs, err - }, "5s", "0.1s").ShouldNot(BeNil()) - } - - BeforeEach(func() { - testutils.ValidateRequirementsAndNotifyGinkgo( - testutils.LinuxOnly("Relies on using an in-memory pipe to ourselves"), - ) - - tlsRequired = v1helpers.NO_TLS - tlsHttpConnect = false - var err error - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - - // run gloo - ro := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableFds: true, - DisableUds: true, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - - // write gateways and wait for them to be created - err = testhelpers.WriteDefaultGateways(writeNamespace, testClients.GatewayClient) - Expect(err).NotTo(HaveOccurred(), "Should be able to write default gateways") - Eventually(func() (gatewayv1.GatewayList, error) { - return testClients.GatewayClient.List(writeNamespace, clients.ListOpts{}) - }, "10s", "0.1s").Should(HaveLen(2), "Gateways should be present") - - // run envoy - err = envoyInstance.RunWithRoleAndRestXds(writeNamespace+"~"+gatewaydefaults.GatewayProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - }) - - JustBeforeEach(func() { - // start http proxy and setup upstream that points to it - port := startHttpProxy(ctx, tlsHttpConnect) - - tu := v1helpers.NewTestHttpUpstreamWithTls(ctx, envoyInstance.LocalAddr(), tlsRequired) - tuPort = tu.Upstream.UpstreamType.(*gloov1.Upstream_Static).Static.Hosts[0].Port - - up = &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: "local-1", - Namespace: "default", - }, - UpstreamType: &gloov1.Upstream_Static{ - Static: &static_plugin_gloo.UpstreamSpec{ - Hosts: []*static_plugin_gloo.Host{ - { - Addr: envoyInstance.LocalAddr(), - Port: uint32(port), - }, - }, - }, - }, - HttpProxyHostname: &wrappers.StringValue{Value: fmt.Sprintf("%s:%d", envoyInstance.LocalAddr(), tuPort)}, // enable HTTP tunneling, - } - - // write a virtual service so we have a proxy to our test upstream - vs = getTrivialVirtualServiceForUpstream(writeNamespace, up.Metadata.Ref()) - vs, err := testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - checkVirtualService(vs) - }) - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - expectResponseBodyOnRequest := func(requestJsonBody string, expectedResponseStatusCode int, expectedResponseBody interface{}) { - EventuallyWithOffset(1, func(g Gomega) { - var json = []byte(requestJsonBody) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("http://%s:%d/test", "localhost", envoyInstance.HttpPort), bytes.NewBuffer(json)) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(http.DefaultClient.Do(req)).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: expectedResponseStatusCode, - Body: expectedResponseBody, - })) - }, "10s", "0.5s").Should(Succeed()) - } - - Context("plaintext", func() { - - JustBeforeEach(func() { - _, err := testClients.UpstreamClient.Write(up, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - - checkProxy() - }) - - It("should proxy http", func() { - // the request path here is envoy -> local HTTP proxy (HTTP CONNECT) -> test upstream - // and back. The HTTP proxy is sending unencrypted HTTP bytes over - // TCP to the test upstream (an echo server) - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - }) - - Context("with TLS", func() { - - JustBeforeEach(func() { - - secret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: testhelpers.Certificate(), - PrivateKey: testhelpers.PrivateKey(), - RootCa: testhelpers.Certificate(), - }, - }, - } - - // set mTLS certs to be used by Envoy so we can talk to mTLS test server - if tlsRequired == v1helpers.MTLS { - secret.GetTls().CertChain = testhelpers.MtlsCertificate() - secret.GetTls().PrivateKey = testhelpers.MtlsPrivateKey() - secret.GetTls().RootCa = testhelpers.MtlsCertificate() - } - - _, err := testClients.SecretClient.Write(secret, clients.WriteOpts{OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - - sslCfg := &ssl.UpstreamSslConfig{ - SslSecrets: &ssl.UpstreamSslConfig_SecretRef{ - SecretRef: &core.ResourceRef{Name: "secret", Namespace: "default"}, - }, - } - - if tlsRequired > v1helpers.NO_TLS { - up.SslConfig = sslCfg - } - up.HttpProxyHostname = &wrappers.StringValue{Value: fmt.Sprintf("%s:%d", envoyInstance.LocalAddr(), tuPort)} // enable HTTP tunneling, - if tlsHttpConnect { - up.HttpConnectSslConfig = sslCfg - } - _, err = testClients.UpstreamClient.Write(up, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - - checkProxy() - }) - - Context("with front TLS", func() { - - BeforeEach(func() { - tlsHttpConnect = true - }) - - It("should proxy plaintext bytes over encrypted HTTP Connect", func() { - // the request path here is [envoy] -- encrypted --> [local HTTP Connect proxy] -- plaintext --> TLS upstream - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - }) - - Context("with back TLS", func() { - - BeforeEach(func() { - tlsRequired = v1helpers.TLS - }) - - It("should proxy encrypted bytes over plaintext HTTP Connect", func() { - // the request path here is [envoy] -- plaintext --> [local HTTP Connect proxy] -- encrypted --> TLS upstream - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - - Context("with multiple routes to one upstream", func() { - JustBeforeEach(func() { - vs.GetVirtualHost().Routes = append(vs.GetVirtualHost().Routes, &gatewayv1.Route{ - Matchers: []*matchers.Matcher{ - { - PathSpecifier: &matchers.Matcher_Prefix{Prefix: "/1"}, - }, - }, - Action: &gatewayv1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: up.Metadata.Ref(), - }, - }, - }, - }, - }, - }) - err := testClients.VirtualServiceClient.Delete(vs.GetMetadata().Namespace, vs.GetMetadata().Name, clients.DeleteOpts{}) - vs, err = testClients.VirtualServiceClient.Write(vs, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - checkVirtualService(vs) - }) - It("should allow multiple routes to TLS upstream", func() { - // the request path here is [envoy] -- plaintext --> [local HTTP Connect proxy] -- encrypted --> TLS upstream - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - }) - }) - - Context("with back mTLS", func() { - BeforeEach(func() { - tlsRequired = v1helpers.MTLS - }) - - It("should proxy encrypted bytes over plaintext HTTP Connect", func() { - // the request path here is [envoy] -- plaintext --> [local HTTP Connect proxy] -- encrypted --> mTLS upstream - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - }) - - Context("with front and back TLS", func() { - - BeforeEach(func() { - tlsRequired = v1helpers.TLS - }) - - It("should proxy encrypted bytes over encrypted HTTP Connect", func() { - // the request path here is [envoy] -- encrypted --> [local HTTP Connect proxy] -- encrypted --> TLS upstream - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - }) - }) - - Context("with Proxy Authorization", func() { - var ( - proxyAuthorizationUsername string - proxyAuthorizationPassword string - ) - JustBeforeEach(func() { - up.HttpConnectHeaders = []*gloov1.HeaderValue{ - { - Key: "Proxy-Authorization", - Value: "Basic " + base64.StdEncoding.EncodeToString([]byte(proxyAuthorizationUsername+":"+proxyAuthorizationPassword)), - }, - } - - _, err := testClients.UpstreamClient.Write(up, clients.WriteOpts{Ctx: ctx, OverwriteExisting: true}) - Expect(err).NotTo(HaveOccurred()) - - checkProxy() - }) - - When("using invalid credentials", func() { - BeforeEach(func() { - proxyAuthorizationUsername = "somebody" - proxyAuthorizationPassword = "wrong" - }) - - It("should not proxy", func() { - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusServiceUnavailable, "upstream connect error or disconnect/reset before headers. reset reason: connection termination") - }) - }) - - When("using valid credentials", func() { - BeforeEach(func() { - proxyAuthorizationUsername = "test" - proxyAuthorizationPassword = "secret" - }) - - It("should proxy", func() { - jsonStr := `{"value":"Hello, world!"}` - expectResponseBodyOnRequest(jsonStr, http.StatusOK, ContainSubstring(jsonStr)) - }) - }) - }) -}) - -func startHttpProxy(ctx context.Context, useTLS bool) int { - listener, err := net.Listen("tcp", ":0") - Expect(err).ToNot(HaveOccurred()) - - addr := listener.Addr().String() - _, portStr, err := net.SplitHostPort(addr) - Expect(err).ToNot(HaveOccurred()) - - port, err := strconv.Atoi(portStr) - Expect(err).ToNot(HaveOccurred()) - - fmt.Fprintln(GinkgoWriter, "go proxy addr", addr) - - go func(useTLS bool) { - defer GinkgoRecover() - server := &http.Server{Addr: addr, Handler: http.HandlerFunc(connectProxy)} - if useTLS { - cert := []byte(testhelpers.Certificate()) - key := []byte(testhelpers.PrivateKey()) - cer, err := tls.X509KeyPair(cert, key) - Expect(err).NotTo(HaveOccurred()) - - tlsCfg := &tls.Config{ - GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { - return &cer, nil - }, - } - tlsListener := tls.NewListener(listener, tlsCfg) - server.Serve(tlsListener) - } else { - server.Serve(listener) - } - <-ctx.Done() - server.Close() - }(useTLS) - - return port -} - -func isEof(r *bufio.Reader) bool { - _, err := r.Peek(1) - if err == io.EOF { - return true - } - return false -} - -func connectProxy(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodConnect { - http.Error(w, "not connect", http.StatusBadRequest) - return - } - - if r.TLS != nil { - fmt.Fprintf(GinkgoWriter, "handshake complete %v\n", r.TLS.HandshakeComplete) - fmt.Fprintf(GinkgoWriter, "tls version %v\n", r.TLS.Version) - fmt.Fprintf(GinkgoWriter, "cipher suite %v\n", r.TLS.CipherSuite) - fmt.Fprintf(GinkgoWriter, "negotiated protocol %v\n", r.TLS.NegotiatedProtocol) - } - - if proxyAuth := r.Header.Get("Proxy-Authorization"); proxyAuth != "" { - fmt.Fprintf(GinkgoWriter, "proxy authorization: %s\n", proxyAuth) - if username, password := parseBasicAuth(proxyAuth); username != "test" || password != "secret" { - w.WriteHeader(http.StatusProxyAuthRequired) - return - } - } - - hij, ok := w.(http.Hijacker) - if !ok { - Fail("no hijacker") - } - host := r.URL.Host - - targetConn, err := net.Dial("tcp", host) - if err != nil { - http.Error(w, "can't connect", http.StatusInternalServerError) - return - } - defer targetConn.Close() - - conn, buf, err := hij.Hijack() - if err != nil { - Expect(err).ToNot(HaveOccurred()) - } - defer conn.Close() - - fmt.Fprintf(GinkgoWriter, "Accepting CONNECT to %s\n", host) - // note to devs! will only work with HTTP 1.1 request from envoy! - conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) - - // now just copy: - var wg sync.WaitGroup - wg.Add(2) - - go func() { - defer GinkgoRecover() - for { - // read bytes from buf.Reader until EOF - bts := []byte{1} - _, err := targetConn.Read(bts) - if errors.Is(err, io.EOF) { - break - } - Expect(err).NotTo(HaveOccurred()) - _, err = conn.Write(bts) - if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, syscall.EPIPE) { - fmt.Fprintf(GinkgoWriter, "error writing from upstream to envoy %v\n", err) - Fail("error writing from upstream to envoy") - } - } - err = buf.Flush() - Expect(err).NotTo(HaveOccurred()) - wg.Done() - }() - go func() { - defer GinkgoRecover() - for !isEof(buf.Reader) { - // read bytes from buf.Reader until EOF - bts := []byte{1} - _, err := buf.Read(bts) - Expect(err).NotTo(HaveOccurred()) - _, err = targetConn.Write(bts) - if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, syscall.EPIPE) { - fmt.Fprintf(GinkgoWriter, "error writing from envoy to upstream %v\n", err) - Fail("error writing from envoy to upstream") - } - } - wg.Done() - }() - - wg.Wait() - fmt.Fprintf(GinkgoWriter, "done proxying\n") -} - -func parseBasicAuth(auth string) (string, string) { - const basicPrefix = "Basic " - if !strings.HasPrefix(auth, basicPrefix) { - return "", "" - } - decodedAuth, err := base64.StdEncoding.DecodeString(auth[len(basicPrefix):]) - if err != nil { - return "", "" - } - decodedAuthString := string(decodedAuth) - username, password, ok := strings.Cut(decodedAuthString, ":") - if !ok { - return "", "" - } - return username, password -} diff --git a/test/e2e/hybrid_test.go b/test/e2e/hybrid_test.go deleted file mode 100644 index 7a1d3bc4892..00000000000 --- a/test/e2e/hybrid_test.go +++ /dev/null @@ -1,677 +0,0 @@ -package e2e_test - -import ( - "errors" - "fmt" - "io" - "net" - "net/http" - "syscall" - - "math/rand" - - "github.com/solo-io/gloo/test/gomega/matchers" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/testutils" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - v3 "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/core/v3" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/headers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/proxy_protocol" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - "github.com/solo-io/gloo/test/e2e" -) - -type ClientConnectionProperties struct { - SrcIp net.IP - SNI string -} - -type GwTester struct { - secret *gloov1.Secret - testContext *e2e.TestContext -} - -const NoMatch = "nothing matched" - -// Configure the gateway with the provided `matchers`, then send a request -// against the gateway using the information in ClientConnectionProperties and -// return the matcher that is matched. -func (gt *GwTester) getMatchedMatcher(cp ClientConnectionProperties, matchers map[string]*v1.Matcher) string { - gt.configureEnvoy(matchers) - - // no need for an Eventually block since envoy is configured at this point - var stringBody string - resp, err := gt.makeARequest(gt.testContext, cp.SrcIp, cp.SNI) - if errors.Is(err, syscall.ECONNRESET) { - // connection properties does not match any of the matchers - return NoMatch - } - Expect(err).NotTo(HaveOccurred()) - - defer resp.Body.Close() - bBody, err := io.ReadAll(resp.Body) - Expect(err).NotTo(HaveOccurred()) - stringBody = string(bBody) - - return stringBody -} - -func (gt *GwTester) configureEnvoy(requestMatchers map[string]*v1.Matcher) { - // create a magic servername value to ensure that envoy is configured - // then we send a request against this magic servername to make sure - // envoy has been fully configured - magicServerName := fmt.Sprintf("%d", rand.Uint32()) + ".com" - requestMatchers[magicServerName] = &v1.Matcher{ - SslConfig: &ssl.SslConfig{ - SniDomains: []string{magicServerName}, - }, - } - vss, gw := gt.getGwWithMatches(magicServerName, requestMatchers) - - writeOptions := clients.WriteOpts{ - Ctx: gt.testContext.Ctx(), - OverwriteExisting: true, - } - c := gt.testContext.TestClients() - - By("writing snapshot with updated gw config") - for _, vs := range vss { - _, err := c.VirtualServiceClient.Write(vs, writeOptions) - Expect(err).NotTo(HaveOccurred()) - } - _, err := c.GatewayClient.Write(gw, writeOptions) - Expect(err).NotTo(HaveOccurred()) - - // use magicservername to ensure envoy has latest config - Eventually(func(g Gomega) { - resp, err := gt.makeARequest(gt.testContext, net.ParseIP("127.0.0.1"), magicServerName) - g.Expect(err).NotTo(HaveOccurred()) - defer resp.Body.Close() - g.Expect(resp).To(matchers.HaveOkResponse()) - }, "5s", "0.1s").Should(Succeed()) -} - -func (gt *GwTester) getGwWithMatches(configver string, matches map[string]*v1.Matcher) ([]*v1.VirtualService, *v1.Gateway) { - gw := gatewaydefaults.DefaultHybridGateway(writeNamespace) - var virtualServices []*v1.VirtualService - gw.Options = &gloov1.ListenerOptions{ - ProxyProtocol: &proxy_protocol.ProxyProtocol{}, - } - vsopts := &gloov1.VirtualHostOptions{ - HeaderManipulation: &headers.HeaderManipulation{ - ResponseHeadersToAdd: []*headers.HeaderValueOption{{ - Header: &headers.HeaderValue{ - Key: "x-gloo-configver", - Value: configver, - }, - Append: &wrappers.BoolValue{ - Value: true, - }, - }}, - }, - } - var matchedGw []*v1.MatchedGateway - - i := 0 - for name, m := range matches { - i++ - curVs := gatewaydefaults.DirectResponseVirtualService(gw.Metadata.Namespace, fmt.Sprintf("vs-%s-%d", configver, i), name) - curVs.VirtualHost.Options = vsopts - virtualServices = append(virtualServices, curVs) - matchedGw = append(matchedGw, &v1.MatchedGateway{ - Matcher: m, - GatewayType: &v1.MatchedGateway_HttpGateway{ - HttpGateway: &v1.HttpGateway{ - VirtualServices: []*core.ResourceRef{ - curVs.Metadata.Ref(), - }, - }, - }, - }) - } - - for _, v := range virtualServices { - v.SslConfig = &ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: gt.secret.Metadata.Ref(), - }, - } - } - - gw.GetHybridGateway().MatchedGateways = matchedGw - return virtualServices, gw -} - -func (gt *GwTester) makeARequest(testContext *e2e.TestContext, srcip net.IP, sni string) (*http.Response, error) { - if srcip == nil { - srcip = net.ParseIP("127.0.0.1") - } - requestBuilder := testContext.GetHttpsRequestBuilder().WithPort(testContext.EnvoyInstance().HybridPort) - proxyProtocolBytes = []byte("PROXY TCP4 " + srcip.String() + " 1.2.3.4 123 123\r\n") - client := testutils.DefaultClientBuilder(). - WithTLSRootCa(gloohelpers.Certificate()). - WithProxyProtocolBytes(proxyProtocolBytes). - WithTLSServerName(sni). - Build() - - // skip ssl verification as it would not work for test.com - // Get "https://localhost:8087/": tls: failed to verify certificate: x509: certificate is valid for gateway-proxy, knative-proxy, ingress-proxy, not test.com - client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true - - return client.Do(requestBuilder.Build()) -} - -var _ = Describe("Hybrid Gateway", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - // limit default gateway to the default vs, so it doesn't catch the new vs we generate - testContext.ResourcesToCreate().Gateways[0].GetHttpGateway().VirtualServices = []*core.ResourceRef{ - testContext.ResourcesToCreate().VirtualServices[0].GetMetadata().Ref(), - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("catchall match for http", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultHybridGateway(writeNamespace) - gw.GetHybridGateway().MatchedGateways = []*v1.MatchedGateway{ - // HttpGateway gets a catchall matcher - { - GatewayType: &v1.MatchedGateway_HttpGateway{ - HttpGateway: &v1.HttpGateway{}, - }, - }, - - // TcpGateway gets a matcher our request *will not* hit - { - Matcher: &v1.Matcher{ - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - GatewayType: &v1.MatchedGateway_TcpGateway{ - TcpGateway: &v1.TcpGateway{}, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("http request works as expected", func() { - requestBuilder := testContext.GetHttpRequestBuilder().WithPort(testContext.EnvoyInstance().HybridPort) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - }, "5s", "0.5s").Should(Succeed()) - }) - - }) - - Context("SourcePrefixRanges match for http", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultHybridGateway(writeNamespace) - gw.GetHybridGateway().MatchedGateways = []*v1.MatchedGateway{ - // HttpGateway gets a matcher our request will hit - { - Matcher: &v1.Matcher{ - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "255.0.0.0", - PrefixLen: &wrappers.UInt32Value{ - Value: 1, - }, - }, - { - AddressPrefix: "0.0.0.0", - PrefixLen: &wrappers.UInt32Value{ - Value: 1, - }, - }, - }, - }, - GatewayType: &v1.MatchedGateway_HttpGateway{ - HttpGateway: &v1.HttpGateway{}, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("http request works as expected", func() { - requestBuilder := testContext.GetHttpRequestBuilder().WithPort(testContext.EnvoyInstance().HybridPort) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - }, "5s", "0.5s").Should(Succeed()) - }) - - }) - - Context("SourcePrefixRanges miss for tcp", func() { - - BeforeEach(func() { - gw := gatewaydefaults.DefaultHybridGateway(writeNamespace) - - gw.GetHybridGateway().MatchedGateways = []*v1.MatchedGateway{ - // HttpGateway gets a filter our request *will not* hit - { - Matcher: &v1.Matcher{ - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - GatewayType: &v1.MatchedGateway_HttpGateway{ - HttpGateway: &v1.HttpGateway{}, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("http request fails", func() { - requestBuilder := testContext.GetHttpRequestBuilder().WithPort(testContext.EnvoyInstance().HybridPort) - Consistently(func(g Gomega) { - _, err := testutils.DefaultHttpClient.Do(requestBuilder.Build()) - g.Expect(err).Should(HaveOccurred()) - }, "3s", "0.5s").Should(Succeed()) - }) - - }) - - Context("permutations of servername and SourcePrefixRanges", func() { - /* - Currently, gloo exposes 2 fields that are used in filter chain - matchers: SNI servername, and SourcePrefixRanges. When these values are - set, Envoy's behaviour (using the old filter chain match API) is to (1) - match on the *most specific* servername first, then (2) see if a - matching value is present for SourcePrefixRanges - - This set of tests tries to comprehensively test all possible - permutations of the outcomes of these 2 matchers to ensure that our - implemented use of the new API does not create any regressions. - */ - - var ( - secret *gloov1.Secret - tester *GwTester - ) - - BeforeEach(func() { - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - secret, - } - tester = &GwTester{ - secret: secret, - testContext: testContext, - } - }) - - // Table test: - // Each entry contains a connection properties struct that is used to - // create a request and a map of named matchers (name is arbitrary) - // to configure envoy with. - // The last argument is the name of the matcher that should match, - // or `NoMatch` if nothing should match. - Context("Table test", func() { - BeforeEach(func() { - // these tests only seem to work on linux; on other platforms, - // requests mysteriously close with an EOF and no further - // diagnostic information. unable to investigate in greater - // depth at this time - testutils.ValidateRequirementsAndNotifyGinkgo(testutils.LinuxOnly("Unknown (possibly uses 127.0.0.1?)")) - }) - DescribeTable("SetResource[Invalid|Valid] works as expected", - func(cp ClientConnectionProperties, matches map[string]*v1.Matcher, expected string) { - // uncomment to dump envoy config - // defer func() { - // config, _ := testContext.EnvoyInstance().ConfigDump() - // fmt.Println(config) - // }() - Expect(tester.getMatchedMatcher(cp, matches)).To(Equal(expected)) - }, - Entry("no match", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "test.com", - }, - map[string]*v1.Matcher{ - "sni-star": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.foo.com"}, - }, - }, - }, NoMatch), - Entry("ip non-match (half ip address) without sni", - ClientConnectionProperties{ - SrcIp: net.ParseIP("2.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "ip-matcher": { - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 16, - }, - }, - }, - }, - }, NoMatch), - // for the next 2 entries, no filter chain match is recorded - // because the filter chain translator aborts translation if there - // are no network filters (ie. virtual hosts) - // https://github.com/solo-io/gloo/blob/d3879f282da00dc0cb6c8c9366a87b48ca1a382b/projects/gloo/pkg/translator/filter_chain.go#L94-L96 - // so even though the ip matches, we expect the request to fail - // similar to the above test. - // there is a workaround for this: by setting SniDomains to '*.', a - // virtual host *will* be created that matches all sni domains - - // see the tests a bit further below. - Entry("ip matcher (full ip address) without sni", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "ip-matcher": { - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - }, NoMatch), - Entry("ip matcher (half ip address) without sni", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.5"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "ip-matcher": { - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 16, - }, - }, - }, - }, - }, NoMatch), - Entry("ip matcher (half ip address) with full wildcard sni (client SNI empty)", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.5"), - // SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "ip-matcher": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*."}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 16, - }, - }, - }, - }, - }, NoMatch), - Entry("ip matcher (half ip address) with full wildcard sni", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.5"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "ip-matcher": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*."}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 16, - }, - }, - }, - }, - }, NoMatch), - Entry("sni match", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "sni-star": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.test.com"}, - }, - }, - }, "sni-star"), - Entry("sni and ip match", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "sni-star": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.test.com"}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - }, "sni-star"), - Entry("sni match, ip non-match", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "sni-star": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.test.com"}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "2.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - }, NoMatch), - Entry("sni non-match, ip match", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "test.com", - }, - map[string]*v1.Matcher{ - "sni-star": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.foo.com"}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - }, NoMatch), - Entry("most specific sni matcher", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "less-specific": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.test.com"}, - }, - }, - "more-specific": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"foo.test.com", "*.com"}, - }, - }, - }, "more-specific"), - Entry("most specific sni matcher with invalid source ip", - // envoy parses sni domain first - it matches 'more-specific', - // and then ignores all other matchers at the same level (ie. - // the 'less-specific' matcher). as envoy descends through the - // 'more-specific' branch, it finds no matching - // SourcePrefixRanges values, so it returns no filter chain - // found - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "less-specific": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"*.test.com"}, - }, - }, - "more-specific": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"foo.test.com"}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "2.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - }, NoMatch), - Entry("sni matcher with multiple source ip", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "more-specific": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"foo.test.com"}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "2.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - { - AddressPrefix: "1.2.3.4", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - }, - }, - }, "more-specific"), - Entry("sni matcher with multiple source ip, less precise CIDR range match", - ClientConnectionProperties{ - SrcIp: net.ParseIP("1.2.3.4"), - SNI: "foo.test.com", - }, - map[string]*v1.Matcher{ - "more-specific": { - SslConfig: &ssl.SslConfig{ - SniDomains: []string{"foo.test.com"}, - }, - SourcePrefixRanges: []*v3.CidrRange{ - { - AddressPrefix: "2.3.4.5", - PrefixLen: &wrappers.UInt32Value{ - Value: 32, - }, - }, - { - AddressPrefix: "1.2.0.0", - PrefixLen: &wrappers.UInt32Value{ - Value: 16, - }, - }, - }, - }, - }, "more-specific"), - ) - }) - - }) -}) diff --git a/test/e2e/loadbalancer_plugin_test.go b/test/e2e/loadbalancer_plugin_test.go deleted file mode 100644 index da8d19aabd3..00000000000 --- a/test/e2e/loadbalancer_plugin_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package e2e_test - -import ( - "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/testutils" - - envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" - envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - glooV1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/helpers" -) - -// setupLBPluginTest sets up a test context with a virtual service that uses the provided load balancer config -func setupLBPluginTest(testContext *e2e.TestContext, lbConfig *glooV1.LoadBalancerConfig) { - upstream := testContext.TestUpstream().Upstream - upstream.LoadBalancerConfig = lbConfig - - customVS := helpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain("custom-domain.com"). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/endpoint"). - WithRouteActionToUpstream(e2e.DefaultRouteName, upstream). - Build() - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{ - customVS, - } -} - -var _ = Describe("Load Balancer Plugin", Label(), func() { - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - var testRequirements []testutils.Requirement - - testContext = testContextFactory.NewTestContext(testRequirements...) - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Maglev LoadBalancer", func() { - BeforeEach(func() { - setupLBPluginTest(testContext, &glooV1.LoadBalancerConfig{ - Type: &glooV1.LoadBalancerConfig_Maglev_{ - Maglev: &glooV1.LoadBalancerConfig_Maglev{}, - }, - }) - }) - - It("can route traffic", func() { - requestBuilder := testContext.GetHttpRequestBuilder(). - WithHost("custom-domain.com"). - WithPath("endpoint") - - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(matchers.HaveOkResponse()) - }, "5s", ".5s").Should(Succeed()) - }) - - It("should have expected envoy config", func() { - Eventually(func(g Gomega) { - dump, err := testContext.EnvoyInstance().StructuredConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - dacs, err := findDynamicActiveClusters(dump) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(dacs).NotTo(BeEmpty()) - g.Expect(dacs).To(HaveLen(1)) - - g.Expect(dacs[0].LbPolicy).To(Equal(envoy_config_cluster_v3.Cluster_MAGLEV)) - g.Expect(dacs[0].CommonLbConfig).To(BeNil()) - }, "5s", ".5s").Should(Succeed()) - }) - }) - - Context("Maglev LB w/ close connections on set change", func() { - BeforeEach(func() { - setupLBPluginTest(testContext, &glooV1.LoadBalancerConfig{ - Type: &glooV1.LoadBalancerConfig_Maglev_{ - Maglev: &glooV1.LoadBalancerConfig_Maglev{}, - }, - CloseConnectionsOnHostSetChange: true, - }) - }) - - It("should have expected envoy config", func() { - Eventually(func(g Gomega) { - dump, err := testContext.EnvoyInstance().StructuredConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - - dacs, err := findDynamicActiveClusters(dump) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(dacs).NotTo(BeEmpty()) - g.Expect(dacs).To(HaveLen(1)) - - g.Expect(dacs[0].LbPolicy).To(Equal(envoy_config_cluster_v3.Cluster_MAGLEV), dacs[0]) - g.Expect(dacs[0].CommonLbConfig).ToNot(BeNil()) - g.Expect(dacs[0].CommonLbConfig.CloseConnectionsOnHostSetChange).To(BeTrue()) - }, "5s", ".5s").Should(Succeed()) - }) - }) -}) - -// findDynamicActiveClusters finds the dynamic active clusters in the config dump -func findDynamicActiveClusters(dump *envoy_admin_v3.ConfigDump) ([]*envoy_config_cluster_v3.Cluster, error) { - clusters := []*envoy_config_cluster_v3.Cluster{} - - var found []*envoy_admin_v3.ClustersConfigDump_DynamicCluster - for _, cfg := range dump.Configs { - if cfg.TypeUrl == "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" { - clusterConfigDump := &envoy_admin_v3.ClustersConfigDump{} - err := cfg.UnmarshalTo(clusterConfigDump) - if err != nil { - return nil, err - } - - found = clusterConfigDump.DynamicActiveClusters - } - } - - if found == nil { - return clusters, nil - } - - for _, clusterDump := range found { - cluster := envoy_config_cluster_v3.Cluster{} - err := clusterDump.Cluster.UnmarshalTo(&cluster) - if err != nil { - return nil, err - } - - clusters = append(clusters, &cluster) - } - - return clusters, nil -} diff --git a/test/e2e/local_ratelimit_test.go b/test/e2e/local_ratelimit_test.go deleted file mode 100644 index 399f31467b2..00000000000 --- a/test/e2e/local_ratelimit_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package e2e_test - -import ( - "fmt" - "net/http" - - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - gloo_matchers "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/ratelimit" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/local_ratelimit" - local_ratelimit_plugin "github.com/solo-io/gloo/projects/gloo/pkg/plugins/local_ratelimit" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/testutils" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/wrapperspb" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Local Rate Limit", func() { - - const ( - defaultLimit = 3 - vsLimit = 2 - routeLimit = 1 - ) - - var ( - testContext *e2e.TestContext - - httpClient *http.Client - requestBuilder *testutils.HttpRequestBuilder - expectNotRateLimitedWithOutXRateLimitHeader func() - expectNotRateLimitedWithXRateLimitHeader func() - expectRateLimitedWithXRateLimitHeader func(int) - validateRateLimits func(int) - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - - httpClient = testutils.DefaultHttpClient - requestBuilder = testContext.GetHttpRequestBuilder() - - expectNotRateLimited := func() *http.Response { - response, err := httpClient.Do(requestBuilder.Build()) - ExpectWithOffset(1, response).To(matchers.HaveOkResponse()) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "The connection should not be rate limited") - return response - } - - expectNotRateLimitedWithOutXRateLimitHeader = func() { - response := expectNotRateLimited() - // Since the values of the x-rate-limit headers change with time, we only check the presence of these header keys and not match their value - ExpectWithOffset(1, response).ToNot(matchers.ContainHeaderKeys([]string{"x-ratelimit-limit", "x-ratelimit-remaining"}), - "x-ratelimit headers should not be present for non rate limited requests") - } - - expectNotRateLimitedWithXRateLimitHeader = func() { - response := expectNotRateLimited() - ExpectWithOffset(2, response).To(matchers.ContainHeaderKeys([]string{"x-ratelimit-limit", "x-ratelimit-remaining"}), - "x-ratelimit headers should be present") - } - - expectRateLimitedWithXRateLimitHeader = func(limit int) { - response, err := httpClient.Do(requestBuilder.Build()) - ExpectWithOffset(2, response).To(matchers.HaveHttpResponse(&matchers.HttpResponse{ - StatusCode: http.StatusTooManyRequests, - Body: "local_rate_limited", - }), "should rate limit") - // Since the request should be rate limited at this point, the values of the following x-rate-limit headers should be consistent - ExpectWithOffset(1, response).To(matchers.ContainHeaders(http.Header{ - "x-ratelimit-limit": []string{fmt.Sprint(limit)}, - "x-ratelimit-remaining": []string{"0"}, - }), "x-ratelimit headers should be present") - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "There should be no error when rate limited") - } - - validateRateLimits = func(limit int) { - for range limit { - expectNotRateLimitedWithXRateLimitHeader() - } - expectRateLimitedWithXRateLimitHeader(limit) - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Filter not configured", func() { - It("Should not add the filter to the list of HCM filters", func() { - // Since the value of RemoveUnusedFilters is set to true in e2e tests, and this filter is not configured in gloo, - // The filter should not be present in the envoy config, and requests should not be rate limited - cfg, err := testContext.EnvoyInstance().ConfigDump() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).ToNot(ContainSubstring(local_ratelimit_plugin.NetworkFilterStatPrefix)) - Expect(cfg).ToNot(ContainSubstring(local_ratelimit_plugin.HTTPFilterStatPrefix)) - - // Since the filter is not defined, the custom X-RateLimit headers should not be present - expectNotRateLimitedWithOutXRateLimitHeader() - }) - }) - - Context("L4 Local Rate Limit", func() { - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - NetworkLocalRatelimit: &local_ratelimit.TokenBucket{ - MaxTokens: 1, - TokensPerFill: &wrapperspb.UInt32Value{ - Value: 1, - }, - FillInterval: &durationpb.Duration{ - Seconds: 100, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - // This test has flaked before with the following envoy error : - // [error][envoy_bug] [external/envoy/source/common/http/conn_manager_impl.cc:527] envoy bug failure: !local_close_reason.empty(). Details: Local Close Reason was not set! - // This has been fixed in envoy v1.27.0 but since we still use v1.26.gitx, this issue intermittently occurs. - // Since this bug doesn't affect the functionally of the ConnectionLimit filter (requests still do not cross the limit), we're adding FlakeAttempts until we move to a version of envoy with this fix. - // More info can be found here : https://github.com/solo-io/gloo/issues/8531 - It("Should rate limit at the l4 layer", FlakeAttempts(5), func() { - expectNotRateLimited := func() { - // We use a new client every time as TCP connections are cached and this needs to be avoided in order to test L4 rate limiting - httpClient := testutils.DefaultClientBuilder().Build() - response, err := httpClient.Do(requestBuilder.Build()) - ExpectWithOffset(1, response).To(matchers.HaveOkResponse()) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "The connection should not be rate limited") - } - - expectRateLimited := func() { - // We use a new client every time as TCP connections are cached and this needs to be avoided in order to test L4 rate limiting - httpClient := testutils.DefaultClientBuilder().Build() - _, err := httpClient.Do(requestBuilder.Build()) - ExpectWithOffset(1, err).Should(MatchError(ContainSubstring("EOF")), "The connection be limited") - } - - // The rate limit is 1 - expectNotRateLimited() - expectRateLimited() - }) - }) - - Context("HTTP Local Rate Limit", func() { - Context("With the gateway level default rate limit set", func() { - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpLocalRatelimit: &local_ratelimit.Settings{ - EnableXRatelimitHeaders: &wrapperspb.BoolValue{Value: true}, - DefaultLimit: &local_ratelimit.TokenBucket{ - MaxTokens: defaultLimit, - TokensPerFill: &wrapperspb.UInt32Value{ - Value: defaultLimit, - }, - FillInterval: &durationpb.Duration{ - Seconds: 100, - }, - }, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("Should rate limit the default value config when nothing else overrides it", func() { - validateRateLimits(defaultLimit) - }) - - It("Should override the default rate limit with the virtual service rate limit", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - RateLimitConfigType: &gloov1.VirtualHostOptions_Ratelimit{ - Ratelimit: &ratelimit.RateLimitVhostExtension{ - LocalRatelimit: &local_ratelimit.TokenBucket{ - MaxTokens: vsLimit, - TokensPerFill: &wrapperspb.UInt32Value{ - Value: vsLimit, - }, - FillInterval: &durationpb.Duration{ - Seconds: 100, - }, - }, - }, - }, - } - return vs - }) - - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(cfg).To(ContainSubstring("typed_per_filter_config")) - }, "5s", ".5s").Should(Succeed()) - - validateRateLimits(vsLimit) - }) - - It("Should override the default rate limit with the route level rate limit", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - RateLimitConfigType: &gloov1.VirtualHostOptions_Ratelimit{ - Ratelimit: &ratelimit.RateLimitVhostExtension{ - LocalRatelimit: &local_ratelimit.TokenBucket{ - MaxTokens: vsLimit, - TokensPerFill: &wrapperspb.UInt32Value{ - Value: vsLimit, - }, - FillInterval: &durationpb.Duration{ - Seconds: 100, - }, - }, - }, - }, - } - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - RateLimitConfigType: &gloov1.RouteOptions_Ratelimit{ - Ratelimit: &ratelimit.RateLimitRouteExtension{ - LocalRatelimit: &local_ratelimit.TokenBucket{ - MaxTokens: routeLimit, - TokensPerFill: &wrapperspb.UInt32Value{ - Value: routeLimit, - }, - FillInterval: &durationpb.Duration{ - Seconds: 100, - }, - }, - }, - }, - } - return vs - }) - - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(cfg).To(ContainSubstring("typed_per_filter_config")) - }, "5s", ".5s").Should(Succeed()) - - validateRateLimits(routeLimit) - }) - - Context("No gateway level default rate limit set", func() { - BeforeEach(func() { - gw := gatewaydefaults.DefaultGateway(writeNamespace) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpLocalRatelimit: &local_ratelimit.Settings{ - EnableXRatelimitHeaders: &wrapperspb.BoolValue{Value: true}, - }, - } - - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gw, - } - }) - - It("Should not rate limit if there is no gateway, vhost or route limit specified", func() { - // If the default is not specified and neither the vHost or Route are RL, the filter should not be applied - cfg, err := testContext.EnvoyInstance().ConfigDump() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).ToNot(ContainSubstring(local_ratelimit_plugin.HTTPFilterStatPrefix)) - - // Since the filter defined, but not configured with any limits, the X-RateLimit headers should not be present - expectNotRateLimitedWithOutXRateLimitHeader() - }) - - It("Should rate limit only the route that has an rate limit specified", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - routes := vs.GetVirtualHost().GetRoutes() - routes[0].Options = &gloov1.RouteOptions{ - RateLimitConfigType: &gloov1.RouteOptions_Ratelimit{ - Ratelimit: &ratelimit.RateLimitRouteExtension{ - LocalRatelimit: &local_ratelimit.TokenBucket{ - MaxTokens: routeLimit, - TokensPerFill: &wrapperspb.UInt32Value{ - Value: routeLimit, - }, - FillInterval: &durationpb.Duration{ - Seconds: 100, - }, - }, - }, - }, - } - unlimitedRoute := &v1.Route{ - Matchers: []*gloo_matchers.Matcher{ - { - PathSpecifier: &gloo_matchers.Matcher_Prefix{ - Prefix: "/unlimited", - }, - }, - }, - Action: &v1.Route_DirectResponseAction{ - DirectResponseAction: &gloov1.DirectResponseAction{ - Status: 200, - Body: "unlimited", - }, - }, - } - routes = append([]*v1.Route{ - unlimitedRoute, - }, routes...) - vs.VirtualHost.Routes = routes - return vs - }) - - // The default is not specified and only the Route is RL - Eventually(func(g Gomega) { - cfg, err := testContext.EnvoyInstance().ConfigDump() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(cfg).To(ContainSubstring("typed_per_filter_config")) - }, "5s", ".5s").Should(Succeed()) - - validateRateLimits(routeLimit) - - // Since the filter is not configured on the /unlimited path, the X-RateLimit headers should not be present - requestBuilder = requestBuilder.WithPath("unlimited") - expectNotRateLimitedWithOutXRateLimitHeader() - }) - - }) - }) - }) -}) diff --git a/test/e2e/proxy_protocol_test.go b/test/e2e/proxy_protocol_test.go deleted file mode 100644 index 9512655ed5d..00000000000 --- a/test/e2e/proxy_protocol_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package e2e_test - -import ( - "net/http" - - "github.com/solo-io/gloo/test/testutils" - - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - "github.com/solo-io/gloo/test/e2e" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -var ( - // https://www.haproxy.org/download/1.9/doc/proxy-protocol.txt - proxyProtocolBytes = []byte("PROXY TCP4 1.2.3.4 1.2.3.4 123 123\r\n") -) - -var _ = Describe("Proxy Protocol", func() { - - var ( - testContext *e2e.TestContext - - secret *gloov1.Secret - requestBuilder *testutils.HttpRequestBuilder - rootCACert string - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - - // prepare default resources - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - secret, - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("HttpGateway", func() { - - Context("without TLS", func() { - - BeforeEach(func() { - requestBuilder = testContext.GetHttpRequestBuilder() - rootCACert = "" - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gatewaydefaults.DefaultGateway(writeNamespace), - } - }) - - Context("without PROXY protocol", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].UseProxyProto = &wrappers.BoolValue{Value: false} - }) - - It("works", func() { - client := getHttpClientWithoutProxyProtocol(rootCACert) - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - }) - - Context("with PROXY protocol", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].UseProxyProto = &wrappers.BoolValue{Value: true} - }) - - It("works", func() { - client := getHttpClientWithProxyProtocol(rootCACert, proxyProtocolBytes) - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - }) - - }) - - Context("with TLS", func() { - - BeforeEach(func() { - requestBuilder = testContext.GetHttpsRequestBuilder() - rootCACert = gloohelpers.Certificate() - - secureVsToTestUpstream := gloohelpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithSslConfig(&ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: secret.Metadata.Ref(), - }, - }). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gatewaydefaults.DefaultSslGateway(writeNamespace), - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - secureVsToTestUpstream, - } - }) - - Context("without PROXY protocol", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].UseProxyProto = &wrappers.BoolValue{Value: false} - }) - - It("works", func() { - client := getHttpClientWithoutProxyProtocol(rootCACert) - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - }) - - Context("with PROXY protocol", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].UseProxyProto = &wrappers.BoolValue{Value: true} - }) - - It("works", func() { - client := getHttpClientWithProxyProtocol(rootCACert, proxyProtocolBytes) - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - }) - - Context("with PROXY protocol and SNI", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].UseProxyProto = &wrappers.BoolValue{Value: true} - - secureVsToTestUpstream := gloohelpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithSslConfig(&ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: secret.Metadata.Ref(), - }, - SniDomains: []string{"gateway-proxy"}, - }). - Build() - - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - secureVsToTestUpstream, - } - }) - - It("works", func() { - client := getHttpClientWithProxyProtocol(rootCACert, proxyProtocolBytes) - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - }) - - }) - - }) - -}) - -func getHttpClientWithoutProxyProtocol(rootCACert string) *http.Client { - return testutils.DefaultClientBuilder().WithTLSRootCa(rootCACert).Build() -} - -func getHttpClientWithProxyProtocol(rootCACert string, proxyProtocolBytes []byte) *http.Client { - return testutils.DefaultClientBuilder(). - WithTLSRootCa(rootCACert). - WithProxyProtocolBytes(proxyProtocolBytes). - Build() -} diff --git a/test/e2e/ratelimit_test.go b/test/e2e/ratelimit_test.go deleted file mode 100644 index 9b0f2d58594..00000000000 --- a/test/e2e/ratelimit_test.go +++ /dev/null @@ -1,437 +0,0 @@ -package e2e_test - -import ( - "context" - "fmt" - "net" - "net/http" - "strings" - "time" - - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/gomega/matchers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - pb "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v3" - structpb "github.com/golang/protobuf/ptypes/struct" - "github.com/golang/protobuf/ptypes/wrappers" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/ratelimit" - gloov1static "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/gloo/projects/gloo/pkg/defaults" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/go-utils/contextutils" - rltypes "github.com/solo-io/solo-apis/pkg/api/ratelimit.solo.io/v1alpha1" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -type acceptOrDenyRateLimitServer struct { - acceptAll bool -} - -func (s *acceptOrDenyRateLimitServer) ShouldRateLimit(_ context.Context, req *pb.RateLimitRequest) (*pb.RateLimitResponse, error) { - // users could implement their own logic in custom rate limit servers here - // the request descriptors are present in the rate limit request object, e.g. - Expect(req.Descriptors[0].Entries[0].Key).To(Equal("generic_key")) - Expect(req.Descriptors[0].Entries[0].Value).To(Equal("test")) - if s.acceptAll { - return &pb.RateLimitResponse{ - OverallCode: pb.RateLimitResponse_OK, - }, nil - } else { - return &pb.RateLimitResponse{ - OverallCode: pb.RateLimitResponse_OVER_LIMIT, - }, nil - } -} - -// Returns the actions that should be used to generate the descriptors expected by the server. -func (s *acceptOrDenyRateLimitServer) getActionsForServer() []*rltypes.RateLimitActions { - return []*rltypes.RateLimitActions{ - { - Actions: []*rltypes.Action{{ - ActionSpecifier: &rltypes.Action_GenericKey_{ - GenericKey: &rltypes.Action_GenericKey{DescriptorValue: "test"}, - }, - }}, - }, - } -} - -type metadataCheckingRateLimitServer struct { - descriptorKey string - defaultDescriptorValue string - metadataKey string - pathSegmentKey string - expectedMetadataValue string -} - -func (s *metadataCheckingRateLimitServer) ShouldRateLimit(ctx context.Context, req *pb.RateLimitRequest) (*pb.RateLimitResponse, error) { - contextutils.LoggerFrom(ctx).Infow("rate limit request", zap.Any("req", req)) - - Expect(req.Descriptors).To(HaveLen(1)) - Expect(req.Descriptors[0].Entries).To(HaveLen(1)) - - descriptorEntry := req.Descriptors[0].Entries[0] - Expect(descriptorEntry.GetKey()).To(Equal(s.descriptorKey)) - Expect(descriptorEntry.GetValue()).To(Or(Equal(s.expectedMetadataValue), Equal(s.defaultDescriptorValue))) - - if descriptorEntry.GetValue() == s.expectedMetadataValue { - return &pb.RateLimitResponse{ - OverallCode: pb.RateLimitResponse_OVER_LIMIT, - }, nil - } - - return &pb.RateLimitResponse{ - OverallCode: pb.RateLimitResponse_OK, - }, nil -} - -// Returns the actions that should be used to generate the descriptors expected by the server. -func (s *metadataCheckingRateLimitServer) getActionsForServer() []*rltypes.RateLimitActions { - return []*rltypes.RateLimitActions{ - { - Actions: []*rltypes.Action{ - { - ActionSpecifier: &rltypes.Action_Metadata{ - Metadata: &rltypes.MetaData{ - DescriptorKey: s.descriptorKey, - MetadataKey: &rltypes.MetaData_MetadataKey{ - Key: s.metadataKey, - Path: []*rltypes.MetaData_MetadataKey_PathSegment{ - { - Segment: &rltypes.MetaData_MetadataKey_PathSegment_Key{ - Key: s.pathSegmentKey, - }, - }, - }, - }, - DefaultValue: s.defaultDescriptorValue, - Source: rltypes.MetaData_ROUTE_ENTRY, - }, - }, - }, - }, - }, - } -} - -var _ = Describe("Rate Limit", Serial, func() { - - // These tests use the Serial decorator because they rely on a hard-coded port for the RateLimit server (18081) - - var ( - ctx context.Context - cancel context.CancelFunc - testClients services.TestClients - ) - - const ( - rlPort = uint32(18081) - ) - - Context("with envoy", func() { - - var ( - envoyInstance *envoy.Instance - testUpstream *v1helpers.TestUpstream - envoyPort uint32 - srv *grpc.Server - ) - - BeforeEach(func() { - envoyInstance = envoyFactory.NewInstance() - envoyPort = envoyInstance.HttpPort - - // add the rl service as a static upstream - rlserver := &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: "rl-server", - Namespace: "default", - }, - UseHttp2: &wrappers.BoolValue{Value: true}, - UpstreamType: &gloov1.Upstream_Static{ - Static: &gloov1static.UpstreamSpec{ - Hosts: []*gloov1static.Host{{ - Addr: envoyInstance.GlooAddr, - Port: rlPort, - }}, - }, - }, - } - ref := rlserver.Metadata.Ref() - rlSettings := &ratelimit.Settings{ - RatelimitServerRef: ref, - EnableXRatelimitHeaders: true, - } - - ctx, cancel = context.WithCancel(context.Background()) - ro := &services.RunOptions{ - NsToWrite: defaults.GlooSystem, - NsToWatch: []string{"default", defaults.GlooSystem}, - WhatToRun: services.What{ - DisableGateway: true, - DisableUds: true, - DisableFds: true, - }, - Settings: &gloov1.Settings{ - RatelimitServer: rlSettings, - }, - } - - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - _, err := testClients.UpstreamClient.Write(rlserver, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - err = helpers.WriteDefaultGateways(defaults.GlooSystem, testClients.GatewayClient) - Expect(err).NotTo(HaveOccurred(), "Should be able to write the default gateways") - - err = envoyInstance.RunWithRoleAndRestXds(envoy.DefaultProxyName, testClients.GlooPort, testClients.RestXdsPort) - Expect(err).NotTo(HaveOccurred()) - - testUpstream = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - var opts clients.WriteOpts - up := testUpstream.Upstream - _, err = testClients.UpstreamClient.Write(up, opts) - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - envoyInstance.Clean() - srv.GracefulStop() - if cancel != nil { - cancel() - } - }) - - It("should rate limit", func() { - rlService := &acceptOrDenyRateLimitServer{acceptAll: false} - srv = startRateLimitServer(rlService, rlPort) - - hosts := map[string]*configForHost{"host1": {actionsForHost: rlService.getActionsForServer()}} - proxy := getProxy(envoyPort, testUpstream.Upstream.Metadata.Ref(), hosts) - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - EventuallyRateLimited("host1", envoyPort) - }) - - It("shouldn't rate limit", func() { - rlService := &acceptOrDenyRateLimitServer{acceptAll: true} - srv = startRateLimitServer(rlService, rlPort) - - hosts := map[string]*configForHost{"host1": {actionsForHost: rlService.getActionsForServer()}} - proxy := getProxy(envoyPort, testUpstream.Upstream.Metadata.Ref(), hosts) - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - ConsistentlyNotRateLimited("host1", envoyPort) - }) - - It("should rate limit based on route metadata", func() { - rlService := &metadataCheckingRateLimitServer{ - descriptorKey: "md-desc", - defaultDescriptorValue: "default-value", - metadataKey: "io.solo.test", - pathSegmentKey: "foo", - expectedMetadataValue: "bar", - } - - srv = startRateLimitServer(rlService, rlPort) - - hosts := map[string]*configForHost{ - "host1": { - actionsForHost: rlService.getActionsForServer(), - routeMetadata: map[string]*structpb.Struct{ - rlService.metadataKey: { - Fields: map[string]*structpb.Value{ - rlService.pathSegmentKey: { - Kind: &structpb.Value_StringValue{ - StringValue: rlService.expectedMetadataValue, - }, - }, - }, - }, - }, - }, - "host2": { - actionsForHost: rlService.getActionsForServer(), - // no metadata here, so we should send the `defaultDescriptorValue` to the server - }, - } - proxy := getProxy(envoyPort, testUpstream.Upstream.Metadata.Ref(), hosts) - _, err := testClients.ProxyClient.Write(proxy, clients.WriteOpts{}) - Expect(err).NotTo(HaveOccurred()) - - // Host 1 should be rate limited - EventuallyRateLimited("host1", envoyPort) - - // Host 2 should never be rate limited - ConsistentlyNotRateLimited("host2", envoyPort) - }) - }) -}) - -func startRateLimitServer(service pb.RateLimitServiceServer, rlport uint32) *grpc.Server { - srv := grpc.NewServer() - pb.RegisterRateLimitServiceServer(srv, service) - reflection.Register(srv) - addr := fmt.Sprintf(":%d", rlport) - lis, err := net.Listen("tcp", addr) - Expect(err).NotTo(HaveOccurred()) - go func() { - defer GinkgoRecover() - err := srv.Serve(lis) - Expect(err).ToNot(HaveOccurred()) - }() - return srv -} - -func EventuallyOk(hostname string, port uint32) { - // wait for three seconds so gloo race can be waited out - // it's possible gloo upstreams hit after the proxy does - // (gloo resyncs once per second) - time.Sleep(3 * time.Second) - EventuallyWithOffset(1, func(g Gomega) { - resp, err := get(hostname, port) - g.Expect(err).NotTo(HaveOccurred()) - defer resp.Body.Close() - g.Expect(resp).To(matchers.HaveOkResponse()) - }, "5s", ".1s").Should(Succeed()) -} - -func ConsistentlyNotRateLimited(hostname string, port uint32) { - // waiting for envoy to start, so that consistently works - EventuallyOk(hostname, port) - - ConsistentlyWithOffset(1, func(g Gomega) { - resp, err := get(hostname, port) - g.Expect(err).NotTo(HaveOccurred()) - defer resp.Body.Close() - g.Expect(resp).To(matchers.HaveOkResponse()) - }, "5s", ".1s").Should(Succeed()) -} - -func EventuallyRateLimited(hostname string, port uint32) { - EventuallyWithOffset(1, func(g Gomega) { - resp, err := get(hostname, port) - g.Expect(err).NotTo(HaveOccurred()) - defer resp.Body.Close() - g.Expect(resp).To(matchers.HaveStatusCode(http.StatusTooManyRequests)) - }, "5s", ".1s").Should(Succeed()) -} - -func get(hostname string, port uint32) (*http.Response, error) { - parts := strings.SplitN(hostname, "/", 2) - hostname = parts[0] - path := "1" - if len(parts) > 1 { - path = parts[1] - } - - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d/"+path, "localhost", port), nil) - Expect(err).NotTo(HaveOccurred()) - - // remove password part if exists - parts = strings.SplitN(hostname, "@", 2) - if len(parts) > 1 { - hostname = parts[1] - auth := strings.Split(parts[0], ":") - req.SetBasicAuth(auth[0], auth[1]) - } - - req.Host = hostname - return http.DefaultClient.Do(req) -} - -func getProxy(envoyPort uint32, upstream *core.ResourceRef, hostsToRateLimits map[string]*configForHost) *gloov1.Proxy { - - rlb := RlProxyBuilder{ - envoyPort: envoyPort, - upstream: upstream, - hostsToRateLimits: hostsToRateLimits, - } - return rlb.getProxy() -} - -type configForHost struct { - actionsForHost []*rltypes.RateLimitActions - routeMetadata map[string]*structpb.Struct -} - -type RlProxyBuilder struct { - upstream *core.ResourceRef - hostsToRateLimits map[string]*configForHost - envoyPort uint32 -} - -func (b *RlProxyBuilder) getProxy() *gloov1.Proxy { - var vhosts []*gloov1.VirtualHost - - for hostname, hostConfig := range b.hostsToRateLimits { - vhost := &gloov1.VirtualHost{ - Name: "gloo-system.virt" + hostname, - Domains: []string{hostname}, - Routes: []*gloov1.Route{ - { - Action: &gloov1.Route_RouteAction{ - RouteAction: &gloov1.RouteAction{ - Destination: &gloov1.RouteAction_Single{ - Single: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: b.upstream, - }, - }, - }, - }, - }, - }, - }, - } - - if len(hostConfig.routeMetadata) > 0 { - vhost.Routes[0].Options = &gloov1.RouteOptions{ - EnvoyMetadata: hostConfig.routeMetadata, - } - } - - if len(hostConfig.actionsForHost) > 0 { - vhost.Options = &gloov1.VirtualHostOptions{ - RateLimitConfigType: &gloov1.VirtualHostOptions_Ratelimit{ - Ratelimit: &ratelimit.RateLimitVhostExtension{ - RateLimits: hostConfig.actionsForHost, - }, - }, - } - } - vhosts = append(vhosts, vhost) - } - - p := &gloov1.Proxy{ - Metadata: &core.Metadata{ - Name: "proxy", - Namespace: "default", - }, - Listeners: []*gloov1.Listener{{ - Name: "listener", - BindAddress: "0.0.0.0", - BindPort: b.envoyPort, - ListenerType: &gloov1.Listener_HttpListener{ - HttpListener: &gloov1.HttpListener{ - VirtualHosts: vhosts, - }, - }, - }}, - } - - return p -} diff --git a/test/e2e/route_transformation_test.go b/test/e2e/route_transformation_test.go deleted file mode 100644 index 91755a2065b..00000000000 --- a/test/e2e/route_transformation_test.go +++ /dev/null @@ -1,505 +0,0 @@ -package e2e_test - -import ( - "encoding/json" - "net/http" - - "github.com/solo-io/gloo/test/testutils" - "google.golang.org/protobuf/types/known/wrapperspb" - - "github.com/golang/protobuf/ptypes/wrappers" - "github.com/onsi/gomega/gstruct" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - "github.com/solo-io/gloo/test/e2e" - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/gomega/transforms" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/v1helpers" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/transformation" -) - -var _ = Describe("Transformations", func() { - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Parsing valid json", func() { - - var transform *transformation.Transformations - - BeforeEach(func() { - transform = &transformation.Transformations{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{body}}", - }, - }, - Headers: map[string]*transformation.InjaTemplate{ - "content-type": { - Text: "text/html", - }, - }, - }, - }, - }, - } - }) - - defaultPostBody := `{"body":"test"}` - defaultOutput := "test" - - // EventuallyResponseTransformed returns an Asynchronous Assertion which - // validates that a request with a body will return the requested content. - // This will only work if a transformation is applied to the response - EventuallyResponseTransformed := func(postBody, expectedOutput string) AsyncAssertion { - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(postBody) - return Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(testmatchers.HaveExactResponseBody(expectedOutput)) - }, "5s", ".5s") - } - - It("should fail if no transform defined", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: nil, - } - return vs - }) - - EventuallyResponseTransformed(defaultPostBody, defaultOutput).Should(HaveOccurred()) - }) - - It("should should transform json to html response on vhost", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(defaultPostBody, defaultOutput).Should(Succeed()) - }) - - It("should should transform json to html response on route", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(defaultPostBody, defaultOutput).Should(Succeed()) - }) - - It("should should transform json to html response on route", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithRouteActionToMultiDestination(e2e.DefaultRouteName, &gloov1.MultiDestination{ - Destinations: []*gloov1.WeightedDestination{{ - Weight: &wrappers.UInt32Value{Value: 1}, - Options: &gloov1.WeightedDestinationOptions{ - Transformations: transform, - }, - Destination: &gloov1.Destination{ - DestinationType: &gloov1.Destination_Upstream{ - Upstream: testContext.TestUpstream().Upstream.GetMetadata().Ref(), - }, - }, - }}, - }) - - return vsBuilder.Build() - }) - - EventuallyResponseTransformed(defaultPostBody, defaultOutput).Should(Succeed()) - }) - - When("constructing JSON body", func() { - complexPostBody := `{"foo":"{\"nestedbar\":\"{\\\"deeply\\\":\\\"nested\\\"}\"}","bar":"\"bie\"","bas":"[\"eball\",\"ketball\"]"}` - complexOutput := `{"FOO":"{\"nestedbar\":\"{\\\"deeply\\\":\\\"nested\\\"}\"}","BARBAS":["\"bie\"","[\"eball\",\"ketball\"]"]}` - When("using escape_characters", func() { - BeforeEach(func() { - transform = &transformation.Transformations{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - EscapeCharacters: &wrapperspb.BoolValue{Value: true}, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: `{"FOO":"{{foo}}","BARBAS":["{{bar}}","{{bas}}"]}`, - }, - }, - }, - }, - }, - } - }) - - When("setting escape_characters globally on settings", func() { - BeforeEach(func() { - testContext.SetRunSettings(&gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - TransformationEscapeCharacters: &wrapperspb.BoolValue{Value: true}, - }, - }) - - transform.GetResponseTransformation().GetTransformationTemplate().EscapeCharacters = nil - }) - - It("should should transform json to json response on vhost", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(complexPostBody, complexOutput).Should(Succeed()) - }) - - It("should should transform json to json response on route", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(complexPostBody, complexOutput).Should(Succeed()) - }) - }) - It("should should transform json to json response on vhost", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(complexPostBody, complexOutput).Should(Succeed()) - }) - - It("should should transform json to json response on route", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(complexPostBody, complexOutput).Should(Succeed()) - }) - - }) - - When("using raw_string", func() { - - BeforeEach(func() { - transform = &transformation.Transformations{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: `{"FOO":"{{raw_string(foo)}}","BARBAS":["{{raw_string(bar)}}","{{raw_string(bas)}}"]}`, - }, - }, - }, - }, - }, - } - }) - It("should should transform json to json response on vhost", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(complexPostBody, complexOutput).Should(Succeed()) - }) - - It("should should transform json to json response on route", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().GetRoutes()[0].Options = &gloov1.RouteOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyResponseTransformed(complexPostBody, complexOutput).Should(Succeed()) - }) - - }) - }) - }) - - Context("parsing non-valid JSON", func() { - - var transform *transformation.Transformations - - BeforeEach(func() { - htmlResponse := "" - htmlEchoUpstream := v1helpers.NewTestHttpUpstreamWithReply(testContext.Ctx(), testContext.EnvoyInstance().LocalAddr(), htmlResponse) - - // This is a bit of a trick - // We use the Default VirtualService name and then remove all VirtualServices in the ResourcesToCreate - // This makes the vsToHtmlUpstream the "default" and tests can use PatchVirtualService to modify it - vsToHtmlUpstream := helpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/html"). - WithRouteActionToUpstream(e2e.DefaultRouteName, htmlEchoUpstream.Upstream). - Build() - - testContext.ResourcesToCreate().Upstreams = gloov1.UpstreamList{htmlEchoUpstream.Upstream} - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{vsToHtmlUpstream} - - transform = &transformation.Transformations{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - Headers: map[string]*transformation.InjaTemplate{ - "x-solo-resp-hdr1": { - Text: "{{ request_header(\"x-solo-hdr-1\") }}", - }, - }, - }, - }, - }, - } - }) - - // EventuallyHtmlResponseTransformed returns an Asynchronous Assertion which - // validates that a request with a header will return a response header with the same - // value, and the body of the response is non-json - // This will only work if the above transformation is applied to the request - EventuallyHtmlResponseTransformed := func() AsyncAssertion { - htmlRequestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("html"). - WithHeader("x-solo-hdr-1", "test") - - return Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(htmlRequestBuilder.Build())).To(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: WithTransform(func(b []byte) error { - var body map[string]interface{} - return json.Unmarshal(b, &body) - }, HaveOccurred()), // attempt to read body as json to confirm that it was not parsed - Headers: map[string]interface{}{ - "x-solo-resp-hdr1": Equal("test"), // inspect response headers to confirm transformation was applied - }, - })) - }, "5s", ".5s") - } - - It("should error on non-json body when ignoreErrorOnParse/parseBodyBehavior/passthrough is disabled", func() { - transform.ResponseTransformation.GetTransformationTemplate().IgnoreErrorOnParse = false - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - htmlRequestBuilder := testContext.GetHttpRequestBuilder(). - WithPath("html"). - WithHeader("x-solo-hdr-1", "test") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(htmlRequestBuilder.Build())).To(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusBadRequest, - Body: gstruct.Ignore(), // We don't care about the body, which will contain an error message - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("should transform response with non-json body when ignoreErrorOnParse is enabled", func() { - transform.ResponseTransformation.GetTransformationTemplate().IgnoreErrorOnParse = true - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyHtmlResponseTransformed().Should(Succeed()) - }) - - It("should transform response with non-json body when ParseBodyBehavior is set to DontParse", func() { - transform.ResponseTransformation.GetTransformationTemplate().ParseBodyBehavior = transformation.TransformationTemplate_DontParse - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyHtmlResponseTransformed().Should(Succeed()) - }) - - It("should transform response with non-json body when passthrough is enabled", func() { - transform.ResponseTransformation.GetTransformationTemplate().BodyTransformation = &transformation.TransformationTemplate_Passthrough{ - Passthrough: &transformation.Passthrough{}, - } - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vs.GetVirtualHost().Options = &gloov1.VirtualHostOptions{ - Transformations: transform, - } - return vs - }) - - EventuallyHtmlResponseTransformed().Should(Succeed()) - }) - }) - - Context("requestTransformation", func() { - - // send the given request and assert that the response matches the given expected response - eventuallyRequestMatches := func(req *http.Request, expectedResponse *testmatchers.HttpResponse) AsyncAssertion { - return Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(req)).To(testmatchers.HaveHttpResponse(expectedResponse)) - }, "10s", ".5s") - } - - BeforeEach(func() { - // create a virtual host with a route to the upstream - vsToEchoUpstream := helpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - RequestTransforms: []*transformation.RequestMatch{ - { - RequestTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_HeaderBodyTransform{ - HeaderBodyTransform: &transformation.HeaderBodyTransform{ - AddRequestMetadata: true, - }, - }, - }, - }, - }, - }, - }, - }). - Build() - - testContext.ResourcesToCreate().VirtualServices = v1.VirtualServiceList{vsToEchoUpstream} - }) - - It("should handle queryStringParameters and multiValueQueryStringParameters", func() { - // form request - req := testContext.GetHttpRequestBuilder(). - WithPath("?foo=bar&multiple=1&multiple=2"). - Build() - // form matcher - matcher := &testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: WithTransform(transforms.WithJsonBody(), - And( - HaveKeyWithValue("queryStringParameters", HaveKeyWithValue("foo", "bar")), - HaveKeyWithValue("queryStringParameters", HaveKeyWithValue("multiple", "2")), - HaveKeyWithValue("multiValueQueryStringParameters", HaveKeyWithValue("multiple", ConsistOf("1", "2"))), - ), - ), - } - - eventuallyRequestMatches(req, matcher).Should(Succeed()) - }) - - It("should handle 3 and 4 values in multiValueQueryStringParameters", func() { - By("populating MultiValueQueryStringParameters with 3 values", func() { - // form request - req := testContext.GetHttpRequestBuilder(). - WithPath("?foo=bar&multiple=1&multiple=2&multiple=3"). - Build() - // form matcher - matcher := &testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: WithTransform(transforms.WithJsonBody(), - And( - HaveKeyWithValue("queryStringParameters", HaveKeyWithValue("foo", "bar")), - HaveKeyWithValue("queryStringParameters", HaveKeyWithValue("multiple", "3")), - HaveKeyWithValue("multiValueQueryStringParameters", HaveKeyWithValue("multiple", ConsistOf("1", "2", "3"))), - ), - ), - } - - eventuallyRequestMatches(req, matcher).Should(Succeed()) - }) - - By("populating MultiValueQueryStringParameters with 4 values", func() { - // form request - req := testContext.GetHttpRequestBuilder(). - WithPath("?foo=bar&multiple=1&multiple=2&multiple=3&multiple=4"). - Build() - // form matcher - matcher := &testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: WithTransform(transforms.WithJsonBody(), - And( - HaveKeyWithValue("queryStringParameters", HaveKeyWithValue("foo", "bar")), - HaveKeyWithValue("queryStringParameters", HaveKeyWithValue("multiple", "4")), // last value - HaveKeyWithValue("multiValueQueryStringParameters", HaveKeyWithValue("multiple", ConsistOf("1", "2", "3", "4"))), - ), - ), - } - - eventuallyRequestMatches(req, matcher).Should(Succeed()) - }) - }) - - It("should handle headers and multiValueHeaders", func() { - // form request - req := testContext.GetHttpRequestBuilder(). - WithHeader("foo", "bar"). - WithHeader("multiple", "1,2"). - Build() - // form matcher - matcher := &testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: WithTransform(transforms.WithJsonBody(), - And( - HaveKeyWithValue("headers", HaveKeyWithValue("foo", "bar")), - HaveKeyWithValue("headers", HaveKeyWithValue("multiple", "2")), - HaveKeyWithValue("multiValueHeaders", HaveKeyWithValue("multiple", ConsistOf("1", "2"))), - ), - ), - } - - eventuallyRequestMatches(req, matcher).Should(Succeed()) - }) - }) -}) diff --git a/test/e2e/staged_transformation_test.go b/test/e2e/staged_transformation_test.go deleted file mode 100644 index 0f262221b18..00000000000 --- a/test/e2e/staged_transformation_test.go +++ /dev/null @@ -1,1083 +0,0 @@ -package e2e_test - -import ( - "encoding/base64" - "encoding/json" - - "github.com/solo-io/gloo/test/testutils" - "go.uber.org/zap/zapcore" - "google.golang.org/protobuf/types/known/wrapperspb" - - structpb "github.com/golang/protobuf/ptypes/struct" - "github.com/golang/protobuf/ptypes/wrappers" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - extauthv1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/extauth/v1" - gloov1static "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/solo-io/gloo/test/e2e" - - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/core/matchers" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/transformation" -) - -var _ = Describe("Staged Transformation", FlakeAttempts(3), func() { - // We added the FlakeAttempts decorator to try to reduce the impact of the flakes outlined in: - // https://github.com/solo-io/gloo/issues/9292 - - var ( - testContext *e2e.TestContext - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - - // This test relies on running the gateway-proxy with debug logging enabled - testContext.EnvoyInstance().LogLevel = zapcore.DebugLevel.String() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("no auth", func() { - - It("should transform response", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Early: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - Matchers: []*matchers.HeaderMatcher{ - { - Name: ":status", - Value: "200", - }, - }, - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "early-transformed", - }, - }, - }, - }, - }, - }}, - }, - // add regular response to see that the early one overrides it - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - Matchers: []*matchers.HeaderMatcher{ - { - Name: ":status", - Value: "200", - }, - }, - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "regular-transformed", - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("test") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "early-transformed", - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("should allow multiple header values for the same header when using HeadersToAppend", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - Headers: map[string]*transformation.InjaTemplate{ - "x-custom-header": {Text: "original header"}, - }, - HeadersToAppend: []*transformation.TransformationTemplate_HeaderToAppend{ - { - Key: "x-custom-header", - Value: &transformation.InjaTemplate{Text: "{{upper(\"appended header 1\")}}"}, - }, - { - Key: "x-custom-header", - Value: &transformation.InjaTemplate{Text: "{{upper(\"appended header 2\")}}"}, - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: BeEmpty(), - // The default Header matcher only works with single headers, so we supply a custom matcher in this case - Custom: testmatchers.ContainHeaders(http.Header{ - "X-Custom-Header": []string{"original header", "APPENDED HEADER 1", "APPENDED HEADER 2"}, - }), - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("Should be able to base64 encode the body", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{base64_encode(body())}}", - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - // send a request, expect that the response body is base64 encoded - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("test") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: base64.StdEncoding.EncodeToString([]byte("test")), - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("Should be able to base64 decode the body", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{base64_decode(body())}}", - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - // send a request, expect that the response body is base64 decoded - body := "test" - encodedBody := base64.StdEncoding.EncodeToString([]byte(body)) - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(encodedBody) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: body, - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("Can extract a substring from the body", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{substring(body(), 0, 4)}}", - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - // send a request, expect that the response body contains only the first 4 characters - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("123456789") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "1234", - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("Can add endpoint metadata to headers using postRouting transformation", func() { - testContext.PatchDefaultUpstream(func(u *gloov1.Upstream) *gloov1.Upstream { - static := u.GetStatic() - if static == nil { - return u - } - // Set a metadata key in the transformation namespace - static.Hosts[0].Metadata = map[string]*structpb.Struct{ - "io.solo.transformation": { - Fields: map[string]*structpb.Value{ - "key": { - Kind: &structpb.Value_StringValue{ - StringValue: "value", - }, - }, - }, - }, - } - return u - }) - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - PostRouting: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{Text: "{{host_metadata(\"key\")}}"}, - }, - HeadersToAppend: []*transformation.TransformationTemplate_HeaderToAppend{ - { - Key: "x-custom-header", - Value: &transformation.InjaTemplate{Text: "{{host_metadata(\"key\")}}"}, - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - // send a request, expect that: - // 1. The body will contain the metadata value - // 2. The header `x-custom-header` will contain the metadata value - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("123456789") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "value", - Headers: map[string]interface{}{ - "x-custom-header": "value", - }, - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("Can modify specific body keys using MergeJsonKeys", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - PostRouting: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - BodyTransformation: &transformation.TransformationTemplate_MergeJsonKeys{ - MergeJsonKeys: &transformation.MergeJsonKeys{ - JsonKeys: map[string]*transformation.MergeJsonKeys_OverridableTemplate{ - "key2": { - Tmpl: &transformation.InjaTemplate{Text: "\"new value\""}, - }, - "key3": { - Tmpl: &transformation.InjaTemplate{Text: "\"value3\""}, - }, - }, - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - // send a request, expect that: - // 1. The body will contain the metadata value - // 2. The header `x-custom-header` will contain the metadata value - jsnBody := map[string]any{ - "key1": "value1", - "key2": "value2", - } - bdyByt, err := json.Marshal(jsnBody) - Expect(err).NotTo(HaveOccurred()) - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(string(bdyByt)) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "{\"key1\":\"value1\",\"key2\":\"new value\",\"key3\":\"value3\"}", - })) - }, "15s", ".5s").Should(Succeed()) - }) - - It("Can base64 decode and transform headers", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - Headers: map[string]*transformation.InjaTemplate{ - // decode the x-custom-header header and then extract a substring - "x-new-custom-header": {Text: `{{substring(base64_decode(request_header("x-custom-header")), 6, 5)}}`}, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - requestBuilder := testContext.GetHttpRequestBuilder(). - WithPostBody(""). - WithHeader("x-custom-header", base64.StdEncoding.EncodeToString([]byte("test1.test2"))) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponseWithHeaders(map[string]interface{}{ - "X-New-Custom-Header": ContainSubstring("test2"), - })) - }, "15s", ".5s").Should(Succeed()) - }) - - Context("Extractors", func() { - var ( - extraction *transformation.Extraction - transformationTemplate *transformation.TransformationTemplate - vHostOpts *gloov1.VirtualHostOptions - ) - - BeforeEach(func() { - extraction = &transformation.Extraction{ - Source: &transformation.Extraction_Body{}, - Regex: ".*", - } - - transformationTemplate = &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{ foo }}", - }, - }, - Extractors: map[string]*transformation.Extraction{"foo": extraction}, - } - - vHostOpts = &gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - RequestTransforms: []*transformation.RequestMatch{{ - RequestTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: transformationTemplate, - }, - }, - }}, - }, - }, - } - }) - - Describe("Extract mode", func() { - It("Can extract text from a subset of the input", func() { - extraction.Mode = transformation.Extraction_EXTRACT - extraction.Regex = ".*(test).*" - extraction.Subgroup = 1 - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "test", - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Defaults to extract mode", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: body, - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Doesn't extract if regex doesn't match", func() { - extraction.Mode = transformation.Extraction_EXTRACT - extraction.Regex = "will not match" - extraction.Subgroup = 0 - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "", - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Doesn't extract if regex doesn't match entire input", func() { - extraction.Mode = transformation.Extraction_EXTRACT - extraction.Regex = "is a test" - extraction.Subgroup = 0 - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "", - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Rejects config if replacement_text is set", func() { - extraction.Mode = transformation.Extraction_EXTRACT - extraction.ReplacementText = &wrapperspb.StringValue{Value: "test"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - helpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, e2e.DefaultVirtualServiceName, clients.ReadOpts{}) - return vs, err - }) - }) - - }) - Describe("Single Replace mode", func() { - It("Can extract a substring from the body and replace it in the response", func() { - extraction.Mode = transformation.Extraction_SINGLE_REPLACE - extraction.Regex = ".*(test).*" - extraction.Subgroup = 1 - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "this is a replaced", - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Can replace from subgroup 0 when regex matches entire body", func() { - extraction.Mode = transformation.Extraction_SINGLE_REPLACE - extraction.Regex = "this is a (test)" - extraction.Subgroup = 0 - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "replaced", - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Returns input if regex doesn't match", func() { - extraction.Mode = transformation.Extraction_SINGLE_REPLACE - extraction.Regex = "will not match" - extraction.Subgroup = 0 - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: body, - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Returns input if regex doesn't match entire input", func() { - extraction.Mode = transformation.Extraction_SINGLE_REPLACE - extraction.Regex = "is a test" - extraction.Subgroup = 0 - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "this is a test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: body, - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Rejects config if replacement_text is not set", func() { - extraction.Mode = transformation.Extraction_SINGLE_REPLACE - extraction.Regex = ".*(test).*" - extraction.Subgroup = 1 - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - helpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, e2e.DefaultVirtualServiceName, clients.ReadOpts{}) - return vs, err - }) - }) - }) - Describe("Replace ALL mode", func() { - It("Can replace multiple instances of the regex in the body", func() { - extraction.Mode = transformation.Extraction_REPLACE_ALL - extraction.Regex = "test" - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "test test test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "replaced replaced replaced", - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Returns input if regex doesn't match", func() { - extraction.Mode = transformation.Extraction_REPLACE_ALL - extraction.Regex = "will not match" - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - body := "test test test" - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody(body) - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: body, - })) - }, "5s", ".5s").Should(Succeed()) - }) - - It("Rejects config if replacement_text is not set", func() { - extraction.Mode = transformation.Extraction_REPLACE_ALL - extraction.Regex = "test" - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - helpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, e2e.DefaultVirtualServiceName, clients.ReadOpts{}) - return vs, err - }) - }) - - It("Rejects config if subgroup is set", func() { - extraction.Mode = transformation.Extraction_REPLACE_ALL - extraction.Regex = "test" - extraction.Subgroup = 1 - extraction.ReplacementText = &wrapperspb.StringValue{Value: "replaced"} - - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(vHostOpts) - return vsBuilder.Build() - }) - - helpers.EventuallyResourceRejected(func() (resources.InputResource, error) { - vs, err := testContext.TestClients().VirtualServiceClient.Read(writeNamespace, e2e.DefaultVirtualServiceName, clients.ReadOpts{}) - return vs, err - }) - }) - }) - }) - - // helper function for the "can enable enhanced logging" table test - // this function checks that both a regular stage and early stage response transformation - // generate the expected logs - containsAllEnhancedLoggingSubstrings := func(logs string) { - Expect(logs).To(And( - ContainSubstring(`body before transformation: test`), - ContainSubstring(`body after transformation: regular-transformed`), - ContainSubstring(`body before transformation: regular-transformed`), - ContainSubstring(`body after transformation: regular-transformed-early-transformed`), - )) - } - - // helper function for the "can enable enhanced logging" table test - // this function checks that an early stage transformation and not a regular stage transformation - // generate the expected logs - containsEarlyEnhancedLoggingSubstrings := func(logs string) { - Expect(logs).To(And( - Not(ContainSubstring(`body before transformation: test`)), - // we need to escape the newline here because we do expect to see regular-transformed-early-transformed - Not(ContainSubstring(`body after transformation: regular-transformed\n`)), - ContainSubstring(`body before transformation: regular-transformed`), - ContainSubstring(`body after transformation: regular-transformed-early-transformed`), - )) - } - - // helper function for the "can enable enhanced logging" table test - // this function checks that neither a regular stage transformation nor an early stage transformation - // generate enhanced logs - containsNoEnhancedLoggingSubstrings := func(logs string) { - Expect(logs).To(And( - Not(ContainSubstring(`body before transformation: test`)), - Not(ContainSubstring(`body after transformation: regular-transformed`)), - Not(ContainSubstring(`body before transformation: regular-transformed`)), - Not(ContainSubstring(`body after transformation: regular-transformed-early-transformed`)), - )) - } - - DescribeTable("can enable enhanced logging", func(logRequestResponseInfoStaged bool, logRequestResponseInfoIndividual bool, expectedLogSubstrings func(string)) { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - LogRequestResponseInfo: &wrapperspb.BoolValue{Value: logRequestResponseInfoStaged}, - Early: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{ - { - Matchers: []*matchers.HeaderMatcher{ - { - Name: ":status", - Value: "200", - }, - }, - ResponseTransformation: &transformation.Transformation{ - LogRequestResponseInfo: logRequestResponseInfoIndividual, - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{body()}}-early-transformed", - }, - }, - }, - }, - }, - }}, - }, - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{ - { - Matchers: []*matchers.HeaderMatcher{ - { - Name: ":status", - Value: "200", - }, - }, - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "regular-transformed", - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("test") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "regular-transformed-early-transformed", - })) - }, "15s", ".5s").Should(Succeed()) - - // get the logs from the gateway-proxy container - logs, err := testContext.EnvoyInstance().Logs() - Expect(err).NotTo(HaveOccurred()) - - expectedLogSubstrings(logs) - }, - Entry("staged logging enabled, individual logging enabled", true, true, containsAllEnhancedLoggingSubstrings), - Entry("staged logging enabled, individual logging disabled", true, false, containsAllEnhancedLoggingSubstrings), - Entry("staged logging disabled, individual logging enabled", false, true, containsEarlyEnhancedLoggingSubstrings), - Entry("staged logging disabled, individual logging disabled", false, false, containsNoEnhancedLoggingSubstrings), - ) - - Context("Enhanced logging in global settings", func() { - BeforeEach(func() { - testContext.SetRunSettings(&gloov1.Settings{ - Gloo: &gloov1.GlooOptions{ - LogTransformationRequestResponseInfo: &wrapperspb.BoolValue{Value: true}, - }, - }) - }) - - It("can enable enhanced logging from global settings object", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Early: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{ - { - Matchers: []*matchers.HeaderMatcher{ - { - Name: ":status", - Value: "200", - }, - }, - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "{{body()}}-early-transformed", - }, - }, - }, - }, - }, - }}, - }, - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{ - { - Matchers: []*matchers.HeaderMatcher{ - { - Name: ":status", - Value: "200", - }, - }, - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "regular-transformed", - }, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("test") - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).Should(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusOK, - Body: "regular-transformed-early-transformed", - })) - }, "15s", ".5s").Should(Succeed()) - - // get the logs from the gateway-proxy container - logs, err := testContext.EnvoyInstance().Logs() - Expect(err).NotTo(HaveOccurred()) - - containsAllEnhancedLoggingSubstrings(logs) - }) - }) - - It("should apply transforms from most specific level only", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - Headers: map[string]*transformation.InjaTemplate{ - "x-solo-1": {Text: "vhost header"}, - }, - }, - }, - }, - }}, - }, - }, - }) - vsBuilder.WithRouteOptions(e2e.DefaultRouteName, &gloov1.RouteOptions{ - StagedTransformations: &transformation.TransformationStages{ - Regular: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - Headers: map[string]*transformation.InjaTemplate{ - "x-solo-2": {Text: "route header"}, - }, - }, - }, - }, - }}, - }, - }, - }) - return vsBuilder.Build() - }) - - requestBuilder := testContext.GetHttpRequestBuilder().WithPostBody("") - Eventually(func(g Gomega) { - // Only route level transformations should be applied here due to the nature of envoy choosing - // the most specific config (weighted cluster > route > vhost) - // This behaviour can be overridden (in the control plane) by using `inheritableTransformations` to merge - // transformations down to the route level. - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponseWithHeaders(map[string]interface{}{ - "x-solo-2": Equal("route header"), - "x-solo-1": BeEmpty(), - })) - }, "15s", ".5s").Should(Succeed()) - }) - }) - - Context("with auth", func() { - - BeforeEach(func() { - // this upstream doesn't need to exist - in fact, we want ext auth to fail. - extAuthUpstream := &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: "extauth-server", - Namespace: "default", - }, - UseHttp2: &wrappers.BoolValue{Value: true}, - UpstreamType: &gloov1.Upstream_Static{ - Static: &gloov1static.UpstreamSpec{ - Hosts: []*gloov1static.Host{{ - Addr: "127.2.3.4", - Port: 1234, - }}, - }, - }, - } - - testContext.ResourcesToCreate().Upstreams = append(testContext.ResourcesToCreate().Upstreams, extAuthUpstream) - - testContext.SetRunSettings(&gloov1.Settings{Extauth: &extauthv1.Settings{ - ExtauthzServerRef: extAuthUpstream.GetMetadata().Ref(), - }}) - }) - - It("should transform response code details", func() { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder.WithVirtualHostOptions(&gloov1.VirtualHostOptions{ - StagedTransformations: &transformation.TransformationStages{ - Early: &transformation.RequestResponseTransformations{ - ResponseTransforms: []*transformation.ResponseMatch{{ - ResponseCodeDetails: "ext_authz_error", - ResponseTransformation: &transformation.Transformation{ - TransformationType: &transformation.Transformation_TransformationTemplate{ - TransformationTemplate: &transformation.TransformationTemplate{ - ParseBodyBehavior: transformation.TransformationTemplate_DontParse, - BodyTransformation: &transformation.TransformationTemplate_Body{ - Body: &transformation.InjaTemplate{ - Text: "early-transformed", - }, - }, - }, - }, - }, - }}, - }, - }, - Extauth: &extauthv1.ExtAuthExtension{ - Spec: &extauthv1.ExtAuthExtension_CustomAuth{ - CustomAuth: &extauthv1.CustomAuth{}, - }, - }, - }) - return vsBuilder.Build() - }) - - // send a request and expect it transformed! - requestBuilder := testContext.GetHttpRequestBuilder() - Eventually(func(g Gomega) { - g.Expect(testutils.DefaultHttpClient.Do(requestBuilder.Build())).To(testmatchers.HaveHttpResponse(&testmatchers.HttpResponse{ - StatusCode: http.StatusForbidden, - Body: "early-transformed", - })) - }, "15s", ".5s").Should(Succeed()) - }) - - }) - -}) diff --git a/test/e2e/tcp_stats_test.go b/test/e2e/tcp_stats_test.go deleted file mode 100644 index 719c004a549..00000000000 --- a/test/e2e/tcp_stats_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package e2e_test - -import ( - "github.com/solo-io/gloo/test/testutils" - - testmatchers "github.com/solo-io/gloo/test/gomega/matchers" - - "github.com/golang/protobuf/ptypes/wrappers" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - "github.com/solo-io/gloo/test/e2e" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" -) - -var _ = Describe("TCP Stats transport_socket", func() { - - var ( - testContext *e2e.TestContext - - secret *gloov1.Secret - requestBuilder *testutils.HttpRequestBuilder - rootCACert string - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - - // prepare default resources - secret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "secret", - Namespace: "default", - }, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: gloohelpers.Certificate(), - PrivateKey: gloohelpers.PrivateKey(), - }, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - secret, - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("HttpGateway", func() { - - Context("without any existing transport socket defined in filter chain", func() { - - BeforeEach(func() { - requestBuilder = testContext.GetHttpRequestBuilder() - rootCACert = "" - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gatewaydefaults.DefaultGateway(writeNamespace), - } - }) - - Context("without outer tcp_stats transport socket wrapper", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].Options = &gloov1.ListenerOptions{ - TcpStats: &wrappers.BoolValue{Value: false}, - } - }) - - It("listener functions", func() { - client := testutils.DefaultClientBuilder().WithTLSRootCa(rootCACert).Build() - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - It("does not emit tcp_stats", func() { - Eventually(func(g Gomega) { - stats, err := testContext.EnvoyInstance().Statistics() - g.Expect(err).NotTo(HaveOccurred()) - - // We expect the Envoy statistics to contain detailed TCP stats - g.Expect(stats).ToNot(ContainSubstring("tcp_stats.cx_rx")) - }, "5s", ".5s").Should(Succeed()) - }) - - }) - - Context("with outer tcp_stats transport socket wrapper", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].Options = &gloov1.ListenerOptions{ - TcpStats: &wrappers.BoolValue{Value: true}, - } - }) - - It("listener functions", func() { - client := testutils.DefaultClientBuilder().WithTLSRootCa(rootCACert).Build() - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - It("emits tcp_stats", func() { - Eventually(func(g Gomega) { - stats, err := testContext.EnvoyInstance().Statistics() - g.Expect(err).NotTo(HaveOccurred()) - - // We expect the Envoy statistics to contain detailed TCP stats - g.Expect(stats).To(ContainSubstring("tcp_stats.cx_rx")) - }, "5s", ".5s").Should(Succeed()) - }) - - }) - - }) - - // Any kind of preexisting transport socket can be wrapped, TLS is picked here - // because it's used a lot, and easy to test/verify in the rig with a client. - Context("with existing TLS transport socket defined in filter chain", func() { - - BeforeEach(func() { - requestBuilder = testContext.GetHttpsRequestBuilder() - rootCACert = gloohelpers.Certificate() - - secureVsToTestUpstream := gloohelpers.NewVirtualServiceBuilder(). - WithName(e2e.DefaultVirtualServiceName). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher(e2e.DefaultRouteName, "/"). - WithRouteActionToUpstream(e2e.DefaultRouteName, testContext.TestUpstream().Upstream). - WithSslConfig(&ssl.SslConfig{ - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: secret.Metadata.Ref(), - }, - }). - Build() - - testContext.ResourcesToCreate().Gateways = gatewayv1.GatewayList{ - gatewaydefaults.DefaultSslGateway(writeNamespace), - } - testContext.ResourcesToCreate().VirtualServices = gatewayv1.VirtualServiceList{ - secureVsToTestUpstream, - } - }) - - Context("without outer tcp_stats transport socket wrapper", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].Options = &gloov1.ListenerOptions{ - TcpStats: &wrappers.BoolValue{Value: false}, - } - }) - - It("listener functions", func() { - client := testutils.DefaultClientBuilder().WithTLSRootCa(rootCACert).Build() - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - It("does not emit tcp_stats", func() { - Eventually(func(g Gomega) { - stats, err := testContext.EnvoyInstance().Statistics() - g.Expect(err).NotTo(HaveOccurred()) - - // We expect the Envoy statistics to contain detailed TCP stats - g.Expect(stats).ToNot(ContainSubstring("tcp_stats.cx_rx")) - }, "5s", ".5s").Should(Succeed()) - }) - - }) - - Context("with outer tcp_stats transport socket wrapper", func() { - - BeforeEach(func() { - testContext.ResourcesToCreate().Gateways[0].Options = &gloov1.ListenerOptions{ - TcpStats: &wrappers.BoolValue{Value: true}, - } - }) - - It("listener functions", func() { - client := testutils.DefaultClientBuilder().WithTLSRootCa(rootCACert).Build() - - Eventually(func(g Gomega) { - g.Expect(client.Do(requestBuilder.Build())).To(testmatchers.HaveOkResponse()) - }, "15s", "1s").Should(Succeed()) - }) - - It("emits tcp_stats", func() { - Eventually(func(g Gomega) { - stats, err := testContext.EnvoyInstance().Statistics() - g.Expect(err).NotTo(HaveOccurred()) - - // We expect the Envoy statistics to contain detailed TCP stats - g.Expect(stats).To(ContainSubstring("tcp_stats.cx_rx")) - }, "5s", ".5s").Should(Succeed()) - }) - - }) - - }) - - }) - -}) diff --git a/test/e2e/tls_ocsp_test.go b/test/e2e/tls_ocsp_test.go deleted file mode 100644 index 47c5e5cccfd..00000000000 --- a/test/e2e/tls_ocsp_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package e2e_test - -import ( - "net/http" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/ssl" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/gomega/matchers" - "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/testutils" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - "golang.org/x/crypto/ocsp" -) - -var _ = Describe("TLS OCSP e2e", func() { - var ( - testContext *e2e.TestContext - clientCert, clientKey string - fakeOcspResponder *helpers.FakeOcspResponder - tlsSecretWithNoOcsp = &core.Metadata{Name: "tls-no-ocsp", Namespace: writeNamespace} - tlsSecretWithOcsp = &core.Metadata{Name: "tls-with-ocsp", Namespace: writeNamespace} - tlsSecretWithExpiredOcsp = &core.Metadata{Name: "tls-with-expired-ocsp", Namespace: writeNamespace} - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContext() - testContext.BeforeEach() - - // Create SSL Gateway - testContext.ResourcesToCreate().Gateways = v1.GatewayList{ - gatewaydefaults.DefaultSslGateway(writeNamespace), - } - - rootCaX509 := helpers.GetCertificateFromString(helpers.Certificate()) - rootKeyRSA := helpers.GetPrivateKeyRSAFromString(helpers.PrivateKey()) - - // create ocsp responses - fakeOcspResponder = helpers.NewFakeOcspResponder(rootCaX509, rootKeyRSA) - - // Generate certificates signed by root CA - clientCert, clientKey = helpers.GetCerts(helpers.Params{ - Hosts: e2e.DefaultHost, - IsCA: false, - IssuerKey: rootKeyRSA, - }) - - clientX509 := helpers.GetCertificateFromString(clientCert) - - // Generate ocsp responses for the client certificate - ocspResponse := fakeOcspResponder.GetOcspResponse(clientX509, 60*time.Minute, false, ocsp.Response{}) - ocspResponseExpired := fakeOcspResponder.GetOcspResponse(clientX509, 0, false, ocsp.Response{}) - - secretWithoutOcspResponse := &gloov1.Secret{ - Metadata: tlsSecretWithNoOcsp, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: clientCert, - PrivateKey: clientKey, - }, - }, - } - secretWithExpiredOcspResponse := &gloov1.Secret{ - Metadata: tlsSecretWithExpiredOcsp, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: clientCert, - PrivateKey: clientKey, - OcspStaple: ocspResponseExpired, - }, - }, - } - secretWithValidOcspResponse := &gloov1.Secret{ - Metadata: tlsSecretWithOcsp, - Kind: &gloov1.Secret_Tls{ - Tls: &gloov1.TlsSecret{ - CertChain: clientCert, - PrivateKey: clientKey, - OcspStaple: ocspResponse, - }, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - secretWithoutOcspResponse, secretWithExpiredOcspResponse, secretWithValidOcspResponse, - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - eventualConsistent := func(fn func(g Gomega)) { - // Setting an offset of 2 as this function is called from within a helper function - EventuallyWithOffset(2, fn, "10s", "1s").Should(Succeed()) - ConsistentlyWithOffset(2, fn, "2s", ".5s").Should(Succeed()) - } - - // expectConsistentResponseStatus expects a response status to be consistent. - expectConsistentResponseStatus := func(httpClient *http.Client, httpRequestBuilder *testutils.HttpRequestBuilder, expectedStatusCode int) { - eventualConsistent(func(g Gomega) { - resp, err := httpClient.Do(httpRequestBuilder.Build()) - g.ExpectWithOffset(1, err).NotTo(HaveOccurred()) - g.ExpectWithOffset(1, resp).To(matchers.HaveStatusCode(expectedStatusCode)) - }) - } - - // expectConsistentError expects an error to happen consistently. - // If expectedError is empty, it expects any error to occur. If set, it expects the error to contain the expectedError string. - expectConsistentError := func(httpClient *http.Client, httpRequestBuilder *testutils.HttpRequestBuilder, expectedError string) { - eventualConsistent(func(g Gomega) { - _, err := httpClient.Do(httpRequestBuilder.Build()) - g.ExpectWithOffset(1, err).To(HaveOccurred()) - if expectedError != "" { - g.ExpectWithOffset(1, err).To(MatchError(ContainSubstring(expectedError))) - } - }) - } - - // updateVirtualService updates the default virtual service with the given sslRef and ocspStaplePolicy. - updateVirtualService := func(sslRef *core.ResourceRef, ocspStaplePolicy ssl.SslConfig_OcspStaplePolicy) { - testContext.PatchDefaultVirtualService(func(vs *v1.VirtualService) *v1.VirtualService { - vsBuilder := helpers.BuilderFromVirtualService(vs) - vsBuilder. - WithSslConfig(&ssl.SslConfig{ - OcspStaplePolicy: ocspStaplePolicy, - SslSecrets: &ssl.SslConfig_SecretRef{ - SecretRef: sslRef, - }, - }) - return vsBuilder.Build() - }) - } - - // buildHttpsRequestClient builds an http client and request builder. - // It uses default values for all fields, as the default is used for all tests. - buildHttpsRequestClient := func() (*http.Client, *testutils.HttpRequestBuilder) { - httpClient := testutils.DefaultClientBuilder(). - WithTLSRootCa(helpers.Certificate()). - WithTLSServerName(e2e.DefaultHost). - Build() - - httpRequestBuilder := testContext.GetHttpsRequestBuilder() - - return httpClient, httpRequestBuilder - } - - Context("with OCSP Staple Policy set to LENIENT_STAPLING", func() { - DescribeTable("should successfully contact upstream", func(sslRef *core.ResourceRef, expectedStatusCode int) { - updateVirtualService(sslRef, ssl.SslConfig_LENIENT_STAPLING) - httpClient, httpRequestBuilder := buildHttpsRequestClient() - - expectConsistentResponseStatus(httpClient, httpRequestBuilder, expectedStatusCode) - }, - Entry("OK with no ocsp staple", tlsSecretWithNoOcsp.Ref(), http.StatusOK), - Entry("OK with valid ocsp staple", tlsSecretWithOcsp.Ref(), http.StatusOK), - Entry("OK with expired ocsp staple", tlsSecretWithExpiredOcsp.Ref(), http.StatusOK), - ) - }) - - Context("with OCSP Staple Policy set to STRICT_STAPLING", func() { - DescribeTable("should successfully contact upstream", func(sslRef *core.ResourceRef, expectedStatusCode int) { - updateVirtualService(sslRef, ssl.SslConfig_STRICT_STAPLING) - httpClient, httpRequestBuilder := buildHttpsRequestClient() - - expectConsistentResponseStatus(httpClient, httpRequestBuilder, expectedStatusCode) - }, - Entry("with no ocsp staple", tlsSecretWithNoOcsp.Ref(), http.StatusOK), - Entry("with valid ocsp staple", tlsSecretWithOcsp.Ref(), http.StatusOK), - ) - - It("fails handshake with expired ocsp staple", func() { - updateVirtualService(tlsSecretWithExpiredOcsp.Ref(), ssl.SslConfig_STRICT_STAPLING) - httpClient, httpRequestBuilder := buildHttpsRequestClient() - - expectConsistentError(httpClient, httpRequestBuilder, "handshake failure") - }) - - }) - - Context("with OCSP Staple Policy set to MUST_STAPLE", func() { - It("fails with no ocsp staple", func() { - updateVirtualService(tlsSecretWithNoOcsp.Ref(), ssl.SslConfig_MUST_STAPLE) - httpClient, httpRequestBuilder := buildHttpsRequestClient() - - // TODO (fabian): figure out the proper way to test this test exactly. - // When doing this through an Envoy bootstrap Envoy fails to start, or at least resulted in many errors logs in Envoy, since the `MUST_STAPLE` requires an ocsp staple. - // Getting an error makes sense, as the downstream/resource would, at the very least, not be created. - // The specific error is nondeterministic. Locally, it was `EOF`, but in CI there was a `SyscallError`. - expectConsistentError(httpClient, httpRequestBuilder, "") - }) - - It("successfully contacts upstream with valid ocsp staple", func() { - updateVirtualService(tlsSecretWithOcsp.Ref(), ssl.SslConfig_MUST_STAPLE) - httpClient, httpRequestBuilder := buildHttpsRequestClient() - - expectConsistentResponseStatus(httpClient, httpRequestBuilder, http.StatusOK) - }) - - It("fails handshake with expired ocsp staple", func() { - updateVirtualService(tlsSecretWithExpiredOcsp.Ref(), ssl.SslConfig_MUST_STAPLE) - httpClient, httpRequestBuilder := buildHttpsRequestClient() - - expectConsistentError(httpClient, httpRequestBuilder, "handshake failure") - }) - }) -}) diff --git a/test/e2e/vault_aws_test.go b/test/e2e/vault_aws_test.go deleted file mode 100644 index f74d8a0511d..00000000000 --- a/test/e2e/vault_aws_test.go +++ /dev/null @@ -1,233 +0,0 @@ -package e2e_test - -import ( - "time" - - "github.com/aws/aws-sdk-go/aws/credentials" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/extauth/v1" - bootstrap "github.com/solo-io/gloo/projects/gloo/pkg/bootstrap/clients" - "github.com/solo-io/gloo/projects/gloo/pkg/bootstrap/clients/vault" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/ginkgo/decorators" - "github.com/solo-io/gloo/test/gomega/assertions" - "github.com/solo-io/gloo/test/testutils" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - "go.opencensus.io/stats/view" -) - -const ( - // These tests run using the following AWS ARN for the Vault Role - // If you want to run these tests locally, ensure that your local AWS credentials match, - // or use another role - // https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html - // Please note that although this is used as a "role" in vault (the value is written to "auth/aws/role/vault-role") - // it is actually an aws user so if running locally *user* and not the role that gets created during manual setup - vaultAwsRole = "arn:aws:iam::802411188784:user/gloo-edge-e2e-user" - vaultAwsRegion = "us-east-1" - - vaultRole = "vault-role" -) - -var _ = Describe("Vault Secret Store (AWS Auth)", decorators.Vault, func() { - - var ( - testContext *e2e.TestContextWithVault - vaultSecretSettings *gloov1.Settings_VaultSecrets - oauthSecret *gloov1.Secret - ) - - BeforeEach(func() { - resetViews() - testContext = testContextFactory.NewTestContextWithVault(testutils.AwsCredentials()) - testContext.BeforeEach() - - oauthSecret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "oauth-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Oauth{ - Oauth: &v1.OauthSecret{ - ClientSecret: "test", - }, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - oauthSecret, - } - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.SetRunSettings(&gloov1.Settings{ - SecretSource: &gloov1.Settings_VaultSecretSource{ - VaultSecretSource: vaultSecretSettings, - }, - }) - testContext.RunVault() - - // We need to turn on Vault AWS Auth after it has started running - err := testContext.VaultInstance().EnableAWSCredentialsAuthMethod(vaultSecretSettings, vaultAwsRole, []string{"default_ttl=10s", "max_ttl=10s"}) - Expect(err).NotTo(HaveOccurred()) - - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Vault Credentials", func() { - BeforeEach(func() { - localAwsCredentials := credentials.NewSharedCredentials("", "") - v, err := localAwsCredentials.Get() - Expect(err).NotTo(HaveOccurred(), "can load AWS shared credentials") - - vaultSecretSettings = &gloov1.Settings_VaultSecrets{ - Address: testContext.VaultInstance().Address(), - AuthMethod: &gloov1.Settings_VaultSecrets_Aws{ - Aws: &gloov1.Settings_VaultAwsAuth{ - VaultRole: vaultRole, - Region: vaultAwsRegion, - AccessKeyId: v.AccessKeyID, - SecretAccessKey: v.SecretAccessKey, - LeaseIncrement: 5, - }, - }, - PathPrefix: vault.DefaultPathPrefix, - RootKey: bootstrap.DefaultRootKey, - } - }) - - It("can read secret using resource client throughout the token lifecycle", func() { - - var ( - secret *gloov1.Secret - err error - ) - - getSecret := func(g Gomega) { - secret, err = testContext.TestClients().SecretClient.Read( - oauthSecret.GetMetadata().GetNamespace(), - oauthSecret.GetMetadata().GetName(), - clients.ReadOpts{ - Ctx: testContext.Ctx(), - }) - g.ExpectWithOffset(1, err).NotTo(HaveOccurred()) - g.ExpectWithOffset(1, secret.GetOauth().GetClientSecret()).To(Equal("test")) - } - - // TEST CASE: We can read with a token - Eventually(getSecret, "5s", ".5s").Should(Succeed()) - - // Check the metrics - we should have one login success and one renewal beacuse the LifetimeWatcher renews as soon as it is started - assertions.ExpectStatLastValueMatches(vault.MLastLoginFailure, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastLoginSuccess, Not(BeZero())) - assertions.ExpectStatSumMatches(vault.MLoginFailures, BeZero()) - assertions.ExpectStatSumMatches(vault.MLoginSuccesses, Equal(1)) - assertions.ExpectStatLastValueMatches(vault.MLastRenewFailure, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastRenewSuccess, Not(BeZero())) - assertions.ExpectStatSumMatches(vault.MRenewFailures, BeZero()) - assertions.ExpectStatSumMatches(vault.MRenewSuccesses, Equal(1)) - - // TEST CASE: We can read the secret with a renewed token - // We have used up (0-5] seconds of the 10 second lease, if we sleep for 5 more seconds, we should - // have to renew the lease again without needed to re-login - time.Sleep(5 * time.Second) - - // Check the metrics - we should have an additional renewal - assertions.ExpectStatLastValueMatches(vault.MLastLoginFailure, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastLoginSuccess, Not(BeZero())) - assertions.ExpectStatSumMatches(vault.MLoginFailures, BeZero()) - assertions.ExpectStatSumMatches(vault.MLoginSuccesses, Equal(1)) - assertions.ExpectStatLastValueMatches(vault.MLastRenewSuccess, Not(BeZero())) - assertions.ExpectStatSumMatches(vault.MRenewFailures, BeZero()) - assertions.ExpectStatSumMatches(vault.MRenewSuccesses, Equal(2)) - - // Don't need the "Eventually" here, because everything is already up and running - getSecret(Default) - - // TEST CASE: we can read the secret after the token expires and login is re-run - // We have used up (5-10] seconds of the 10 second lease, if we sleep for 7 seconds, we should see a re-login - // Sleep a little extra to give login time to complete - time.Sleep(7 * time.Second) - - // Check the metrics - we should have an additional renewal failure, and an additional login success - // plus 2-3 more renewal successes. The uncertainty is due jitter used caculate the time to renew: - // "For a given lease duration, we want to allow 80-90% of that to elapse," - // This means that even though we have a lease interval of 4 seconds, the renewal will happen sooner - // This leaves room for the possibility of a 3rd renewal happening before the lease expires - assertions.ExpectStatLastValueMatches(vault.MLastLoginFailure, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastLoginSuccess, Not(BeZero())) - assertions.ExpectStatSumMatches(vault.MLoginFailures, BeZero()) - - assertions.ExpectStatLastValueMatches(vault.MLastRenewSuccess, Not(BeZero())) - assertions.ExpectStatSumMatches(vault.MRenewFailures, Equal(1)) - assertions.ExpectStatSumMatches(vault.MRenewSuccesses, BeNumerically("~", 4, 5)) - assertions.ExpectStatSumMatches(vault.MLoginSuccesses, Equal(2)) - - getSecret(Default) - }) - - It("can pick up new secrets created by vault client ", func() { - newSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "new-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Oauth{ - Oauth: &v1.OauthSecret{ - ClientSecret: "new-secret", - }, - }, - } - - err := testContext.VaultInstance().WriteSecret(newSecret) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func(g Gomega) { - secret, err := testContext.TestClients().SecretClient.Read( - newSecret.GetMetadata().GetNamespace(), - newSecret.GetMetadata().GetName(), - clients.ReadOpts{ - Ctx: testContext.Ctx(), - }) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(secret.GetOauth().GetClientSecret()).To(Equal("new-secret")) - }, "5s", ".5s").Should(Succeed()) - }) - - }) - -}) - -func resetViews() { - views := []*view.View{ - vault.MLastLoginSuccessView, - vault.MLoginFailuresView, - vault.MLoginSuccessesView, - vault.MLastLoginFailureView, - vault.MLastRenewFailureView, - vault.MLastRenewSuccessView, - vault.MRenewFailuresView, - vault.MRenewSuccessesView, - } - view.Unregister(views...) - _ = view.Register(views...) - assertions.ExpectStatLastValueMatches(vault.MLastLoginSuccess, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastLoginFailure, BeZero()) - assertions.ExpectStatSumMatches(vault.MLoginSuccesses, BeZero()) - assertions.ExpectStatSumMatches(vault.MLoginFailures, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastRenewFailure, BeZero()) - assertions.ExpectStatLastValueMatches(vault.MLastRenewSuccess, BeZero()) - assertions.ExpectStatSumMatches(vault.MRenewFailures, BeZero()) - assertions.ExpectStatSumMatches(vault.MRenewSuccesses, BeZero()) -} diff --git a/test/e2e/vault_test.go b/test/e2e/vault_test.go deleted file mode 100644 index 7436b57cf01..00000000000 --- a/test/e2e/vault_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package e2e_test - -import ( - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/enterprise/options/extauth/v1" - bootstrap "github.com/solo-io/gloo/projects/gloo/pkg/bootstrap/clients" - "github.com/solo-io/gloo/projects/gloo/pkg/bootstrap/clients/vault" - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/ginkgo/decorators" - "github.com/solo-io/gloo/test/services" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Vault Secret Store (Token Auth)", decorators.Vault, func() { - - var ( - testContext *e2e.TestContextWithVault - ) - - BeforeEach(func() { - testContext = testContextFactory.NewTestContextWithVault() - testContext.BeforeEach() - - testContext.SetRunSettings(&gloov1.Settings{ - SecretSource: &gloov1.Settings_VaultSecretSource{ - VaultSecretSource: &gloov1.Settings_VaultSecrets{ - Address: testContext.VaultInstance().Address(), - AuthMethod: &gloov1.Settings_VaultSecrets_AccessToken{ - AccessToken: services.DefaultVaultToken, - }, - PathPrefix: vault.DefaultPathPrefix, - RootKey: bootstrap.DefaultRootKey, - }, - }, - }) - - testContext.RunVault() - }) - - AfterEach(func() { - testContext.AfterEach() - }) - - JustBeforeEach(func() { - testContext.JustBeforeEach() - }) - - JustAfterEach(func() { - testContext.JustAfterEach() - }) - - Context("Oauth Secret", func() { - - var ( - oauthSecret *gloov1.Secret - ) - - BeforeEach(func() { - oauthSecret = &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "oauth-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Oauth{ - Oauth: &v1.OauthSecret{ - ClientSecret: "original-secret", - }, - }, - } - - testContext.ResourcesToCreate().Secrets = gloov1.SecretList{ - oauthSecret, - } - }) - - It("can read secret using resource client", func() { - Eventually(func(g Gomega) { - secret, err := testContext.TestClients().SecretClient.Read( - oauthSecret.GetMetadata().GetNamespace(), - oauthSecret.GetMetadata().GetName(), - clients.ReadOpts{ - Ctx: testContext.Ctx(), - }) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(secret.GetOauth().GetClientSecret()).To(Equal("original-secret")) - }, "5s", ".5s").Should(Succeed()) - }) - - It("can pick up new secrets created by vault client ", func() { - newSecret := &gloov1.Secret{ - Metadata: &core.Metadata{ - Name: "new-secret", - Namespace: writeNamespace, - }, - Kind: &gloov1.Secret_Oauth{ - Oauth: &v1.OauthSecret{ - ClientSecret: "new-secret", - }, - }, - } - - err := testContext.VaultInstance().WriteSecret(newSecret) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func(g Gomega) { - secret, err := testContext.TestClients().SecretClient.Read( - newSecret.GetMetadata().GetNamespace(), - newSecret.GetMetadata().GetName(), - clients.ReadOpts{ - Ctx: testContext.Ctx(), - }) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(secret.GetOauth().GetClientSecret()).To(Equal("new-secret")) - }, "5s", ".5s").Should(Succeed()) - }) - - }) - -}) diff --git a/test/e2e/zipkin_test.go b/test/e2e/zipkin_test.go deleted file mode 100644 index 2b52f1cd880..00000000000 --- a/test/e2e/zipkin_test.go +++ /dev/null @@ -1,507 +0,0 @@ -package e2e_test - -import ( - "context" - "fmt" - "html" - "io" - "net/http" - "time" - - "github.com/solo-io/gloo/test/e2e" - "github.com/solo-io/gloo/test/services/envoy" - - "github.com/solo-io/gloo/test/testutils" - - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/gloosnapshot" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/hcm" - "github.com/solo-io/gloo/projects/gloo/pkg/translator" - "github.com/solo-io/solo-kit/pkg/api/v1/resources" - - gatewayv1 "github.com/solo-io/gloo/projects/gateway/pkg/api/v1" - gatewaydefaults "github.com/solo-io/gloo/projects/gateway/pkg/defaults" - envoytrace_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/external/envoy/config/trace/v3" - gloov1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1" - static_plugin_gloo "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/static" - "github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/tracing" - gloohelpers "github.com/solo-io/gloo/test/helpers" - "github.com/solo-io/gloo/test/v1helpers" - "github.com/solo-io/solo-kit/pkg/api/v1/clients" - "github.com/solo-io/solo-kit/pkg/api/v1/resources/core" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/solo-io/gloo/test/gomega" - - "github.com/solo-io/gloo/test/services" -) - -const ( - tracingCollectorPort = 9411 - tracingCollectorUpstreamName = "tracing-collector" - openTelemetryCollectionPath = "/opentelemetry.proto.collector.trace.v1.TraceService/Export" - zipkinCollectionPath = "/api/v2/spans" -) - -var _ = Describe("Tracing config loading", Serial, func() { - - // These tests use the Serial decorator because they rely on a hard-coded port for the tracing collector (9411) - - var ( - ctx context.Context - cancel context.CancelFunc - envoyInstance *envoy.Instance - ) - - BeforeEach(func() { - ctx, cancel = context.WithCancel(context.Background()) - - envoyInstance = envoyFactory.NewInstance() - }) - - AfterEach(func() { - cancel() - }) - - Context("Tracing defined on Envoy bootstrap", func() { - - BeforeEach(func() { - testutils.ValidateRequirementsAndNotifyGinkgo( - testutils.LinuxOnly("Uses 127.0.0.1"), - ) - }) - - It("should send trace msgs to the zipkin server", func() { - err := envoyInstance.RunWithConfigFile(ctx, int(envoyInstance.HttpPort), "./envoyconfigs/zipkin-envoy-conf.yaml") - Expect(err).NotTo(HaveOccurred()) - - // Start a dummy server listening on 9411 for Zipkin requests - apiHit := make(chan bool, 1) - zipkinHandler := http.NewServeMux() - zipkinHandler.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - Expect(r.URL.Path).To(Equal(zipkinCollectionPath)) - fmt.Fprintf(w, "Dummy Zipkin Collector received request on - %q", html.EscapeString(r.URL.Path)) - apiHit <- true - })) - startCancellableTracingServer(ctx, fmt.Sprintf("%s:%d", envoyInstance.LocalAddr(), tracingCollectorPort), zipkinHandler) - - // Execute a request against the admin endpoint, as this should result in a trace - testRequest := createRequestWithTracingEnabled("127.0.0.1", 11082) - Eventually(func(g Gomega) { - res, err := testRequest() - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(res).To(ContainSubstring(`Envoy Admin`)) - - g.Eventually(apiHit, 1*time.Second).Should(Receive(BeTrue())) - }, "10s", ".5s").Should(Succeed(), "Admin endpoint request should result in trace") - - }) - - }) - - Context("Tracing defined on Gloo resources", func() { - - var ( - testClients services.TestClients - testUpstream *v1helpers.TestUpstream - - resourcesToCreate *gloosnapshot.ApiSnapshot - ) - - BeforeEach(func() { - // run gloo - ro := &services.RunOptions{ - NsToWrite: writeNamespace, - NsToWatch: []string{"default", writeNamespace}, - WhatToRun: services.What{ - DisableFds: true, - DisableUds: true, - }, - } - testClients = services.RunGlooGatewayUdsFds(ctx, ro) - - // run envoy - err := envoyInstance.RunWithRole(writeNamespace+"~"+gatewaydefaults.GatewayProxyName, testClients.GlooPort) - Expect(err).NotTo(HaveOccurred()) - - // this is the upstream that will handle requests - testUpstream = v1helpers.NewTestHttpUpstream(ctx, envoyInstance.LocalAddr()) - - vsToTestUpstream := gloohelpers.NewVirtualServiceBuilder(). - WithName("vs-test"). - WithNamespace(writeNamespace). - WithDomain(e2e.DefaultHost). - WithRoutePrefixMatcher("test", "/"). - WithRouteActionToUpstream("test", testUpstream.Upstream). - Build() - - // create tracing collector upstream - tracingCollectorUs := &gloov1.Upstream{ - Metadata: &core.Metadata{ - Name: tracingCollectorUpstreamName, - Namespace: writeNamespace, - }, - UpstreamType: &gloov1.Upstream_Static{ - Static: &static_plugin_gloo.UpstreamSpec{ - Hosts: []*static_plugin_gloo.Host{ - { - Addr: envoyInstance.LocalAddr(), - Port: tracingCollectorPort, - }, - }, - }, - }, - } - - // The set of resources that these tests will generate - resourcesToCreate = &gloosnapshot.ApiSnapshot{ - Gateways: gatewayv1.GatewayList{ - gatewaydefaults.DefaultGateway(writeNamespace), - }, - VirtualServices: gatewayv1.VirtualServiceList{ - vsToTestUpstream, - }, - Upstreams: gloov1.UpstreamList{ - tracingCollectorUs, - testUpstream.Upstream, - }, - } - }) - - AfterEach(func() { - envoyInstance.Clean() - cancel() - }) - - startTracingCollectionServer := func(collectorApiChannel chan bool, collectionURLPath string) { - // Start a dummy server listening on 9411 for tracing requests - tracingCollectorHandler := http.NewServeMux() - tracingCollectorHandler.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - Expect(r.URL.Path).To(Equal(collectionURLPath)) - fmt.Fprintf(w, "Dummy tracing Collector received request on - %q", html.EscapeString(r.URL.Path)) - collectorApiChannel <- true - })) - startCancellableTracingServer(ctx, fmt.Sprintf("%s:%d", envoyInstance.LocalAddr(), tracingCollectorPort), tracingCollectorHandler) - - // Create Resources - err := testClients.WriteSnapshot(ctx, resourcesToCreate) - Expect(err).NotTo(HaveOccurred()) - - // Wait for a proxy to be accepted - gloohelpers.EventuallyResourceAccepted(func() (resources.InputResource, error) { - return testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{Ctx: ctx}) - }) - - // Ensure the testUpstream is reachable - v1helpers.ExpectCurlWithOffset( - 0, - v1helpers.CurlRequest{ - RootCA: nil, - Port: envoyInstance.HttpPort, - Host: e2e.DefaultHost, // to match the vs-test - Path: "/", - Body: []byte("solo.io test"), - }, - v1helpers.CurlResponse{ - Status: http.StatusOK, - Message: "solo.io test", - }, - ) - } - - It("should send trace msgs with valid opentelemetry provider (collector_ref)", func() { - collectorApiHit := make(chan bool, 1) - startTracingCollectionServer(collectorApiHit, openTelemetryCollectionPath) - - err := gloohelpers.PatchResource( - ctx, - &core.ResourceRef{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: writeNamespace, - }, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - Tracing: &tracing.ListenerTracingSettings{ - ProviderConfig: &tracing.ListenerTracingSettings_OpenTelemetryConfig{ - OpenTelemetryConfig: &envoytrace_gloo.OpenTelemetryConfig{ - CollectorCluster: &envoytrace_gloo.OpenTelemetryConfig_CollectorUpstreamRef{ - CollectorUpstreamRef: &core.ResourceRef{ - Name: tracingCollectorUpstreamName, - Namespace: writeNamespace, - }, - }, - }, - }, - }, - }, - } - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - testRequest := createRequestWithTracingEnabled("localhost", envoyInstance.HttpPort) - Eventually(func(g Gomega) { - g.Eventually(testRequest, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(BeEmpty()) - g.Eventually(collectorApiHit, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Receive()) - }, time.Second*10, time.Second).Should(Succeed(), "tracing server should receive trace request") - }) - - It("should send trace msgs with valid opentelemetry provider (cluster_name)", func() { - collectorApiHit := make(chan bool, 1) - startTracingCollectionServer(collectorApiHit, openTelemetryCollectionPath) - - err := gloohelpers.PatchResource( - ctx, - &core.ResourceRef{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: writeNamespace, - }, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - Tracing: &tracing.ListenerTracingSettings{ - ProviderConfig: &tracing.ListenerTracingSettings_OpenTelemetryConfig{ - OpenTelemetryConfig: &envoytrace_gloo.OpenTelemetryConfig{ - CollectorCluster: &envoytrace_gloo.OpenTelemetryConfig_ClusterName{ - ClusterName: translator.UpstreamToClusterName(&core.ResourceRef{ - Name: tracingCollectorUpstreamName, - Namespace: writeNamespace, - }), - }, - }, - }, - }, - }, - } - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - testRequest := createRequestWithTracingEnabled("localhost", envoyInstance.HttpPort) - Eventually(func(g Gomega) { - g.Eventually(testRequest, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(BeEmpty()) - g.Eventually(collectorApiHit, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Receive()) - }, time.Second*10, time.Second).Should(Succeed(), "tracing server should receive trace request") - }) - - It("should not send trace msgs with nil provider", func() { - collectorApiHit := make(chan bool, 1) - startTracingCollectionServer(collectorApiHit, zipkinCollectionPath) - - err := gloohelpers.PatchResource( - ctx, - &core.ResourceRef{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: writeNamespace, - }, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - Tracing: nil, - }, - } - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - testRequest := createRequestWithTracingEnabled("localhost", envoyInstance.HttpPort) - Eventually(func(g Gomega) { - g.Eventually(testRequest, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(BeEmpty()) - g.Eventually(collectorApiHit, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Not(Receive())) - }, time.Second*5, time.Millisecond*250).Should(Succeed(), "zipkin server should not receive trace request") - }) - - It("should send trace msgs with valid zipkin provider (collector_ref)", func() { - collectorApiHit := make(chan bool, 1) - startTracingCollectionServer(collectorApiHit, zipkinCollectionPath) - - err := gloohelpers.PatchResource( - ctx, - &core.ResourceRef{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: writeNamespace, - }, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - Tracing: &tracing.ListenerTracingSettings{ - ProviderConfig: &tracing.ListenerTracingSettings_ZipkinConfig{ - ZipkinConfig: &envoytrace_gloo.ZipkinConfig{ - CollectorCluster: &envoytrace_gloo.ZipkinConfig_CollectorUpstreamRef{ - CollectorUpstreamRef: &core.ResourceRef{ - Name: tracingCollectorUpstreamName, - Namespace: writeNamespace, - }, - }, - CollectorEndpoint: zipkinCollectionPath, - CollectorEndpointVersion: envoytrace_gloo.ZipkinConfig_HTTP_JSON, - }, - }, - }, - }, - } - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - testRequest := createRequestWithTracingEnabled("localhost", envoyInstance.HttpPort) - Eventually(func(g Gomega) { - g.Eventually(testRequest, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(BeEmpty()) - g.Eventually(collectorApiHit, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Receive()) - }, time.Second*10, time.Second).Should(Succeed(), "tracing server should receive trace request") - }) - - It("should send trace msgs with valid zipkin provider (cluster_name)", func() { - collectorApiHit := make(chan bool, 1) - startTracingCollectionServer(collectorApiHit, zipkinCollectionPath) - - err := gloohelpers.PatchResource( - ctx, - &core.ResourceRef{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: writeNamespace, - }, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - Tracing: &tracing.ListenerTracingSettings{ - ProviderConfig: &tracing.ListenerTracingSettings_ZipkinConfig{ - ZipkinConfig: &envoytrace_gloo.ZipkinConfig{ - CollectorCluster: &envoytrace_gloo.ZipkinConfig_ClusterName{ - ClusterName: translator.UpstreamToClusterName(&core.ResourceRef{ - Name: tracingCollectorUpstreamName, - Namespace: writeNamespace, - }), - }, - CollectorEndpoint: zipkinCollectionPath, - CollectorEndpointVersion: envoytrace_gloo.ZipkinConfig_HTTP_JSON, - }, - }, - }, - }, - } - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - testRequest := createRequestWithTracingEnabled("localhost", envoyInstance.HttpPort) - Eventually(func(g Gomega) { - g.Eventually(testRequest, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(BeEmpty()) - g.Eventually(collectorApiHit, DefaultEventuallyTimeout, DefaultEventuallyPollingInterval).Should(Receive()) - }, time.Second*10, time.Second).Should(Succeed(), "zipkin server should receive trace request") - }) - - It("should error with invalid zipkin provider", func() { - collectorApiHit := make(chan bool, 1) - startTracingCollectionServer(collectorApiHit, zipkinCollectionPath) - - err := gloohelpers.PatchResource( - ctx, - &core.ResourceRef{ - Name: gatewaydefaults.GatewayProxyName, - Namespace: writeNamespace, - }, - func(resource resources.Resource) resources.Resource { - gw := resource.(*gatewayv1.Gateway) - gw.GetHttpGateway().Options = &gloov1.HttpListenerOptions{ - HttpConnectionManagerSettings: &hcm.HttpConnectionManagerSettings{ - Tracing: &tracing.ListenerTracingSettings{ - ProviderConfig: &tracing.ListenerTracingSettings_ZipkinConfig{ - ZipkinConfig: &envoytrace_gloo.ZipkinConfig{ - CollectorCluster: &envoytrace_gloo.ZipkinConfig_CollectorUpstreamRef{ - CollectorUpstreamRef: nil, - }, - CollectorEndpoint: zipkinCollectionPath, - CollectorEndpointVersion: envoytrace_gloo.ZipkinConfig_HTTP_JSON, - }, - }, - }, - }, - } - return gw - }, - testClients.GatewayClient.BaseClient(), - ) - Expect(err).NotTo(HaveOccurred()) - - // ensure the proxy is never updated with the invalid configuration - Consistently(func(g Gomega) int { - tracingConfigsFound := 0 - - proxy, err := testClients.ProxyClient.Read(writeNamespace, gatewaydefaults.GatewayProxyName, clients.ReadOpts{Ctx: ctx}) - g.Expect(err).NotTo(HaveOccurred()) - - for _, l := range proxy.GetListeners() { - if l.GetHttpListener().GetOptions().GetHttpConnectionManagerSettings().GetTracing() != nil { - tracingConfigsFound += 1 - } - } - return tracingConfigsFound - }, time.Second*3, time.Second).Should(Equal(0)) - }) - }) - -}) - -func startCancellableTracingServer(serverContext context.Context, address string, handler http.Handler) { - tracingServer := &http.Server{ - Addr: address, - Handler: handler, - } - - // Start a goroutine to handle requests - go func() { - defer GinkgoRecover() - if err := tracingServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - } - }() - - // Start a goroutine to shutdown the server - go func(serverCtx context.Context) { - defer GinkgoRecover() - - <-serverCtx.Done() - // tracingServer.Shutdown hangs with opentelemetry tests, probably - // because the agent leaves the connection open. There's no need for a - // graceful shutdown anyway, so just force it using Close() instead - tracingServer.Close() - }(serverContext) -} - -func createRequestWithTracingEnabled(address string, port uint32) func() (string, error) { - return func() (string, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d/", address, port), nil) - if err != nil { - return "", err - } - req.Header.Set("Content-Type", "application/json") - - // Set a random trace ID - req.Header.Set("x-client-trace-id", "test-trace-id-1234567890") - - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - return string(body), err - } -}