From 24cb858bda8e8a8b861adaa80ff496627d5db14d Mon Sep 17 00:00:00 2001 From: Ryan Lymburner Date: Tue, 2 Dec 2025 14:36:43 -0800 Subject: [PATCH] Correct HTTPS with ServiceExport --- docs/api-types/service-export.md | 39 +++ docs/api-types/target-group-policy.md | 36 +++ pkg/gateway/model_build_targetgroup.go | 21 +- pkg/gateway/model_build_targetgroup_test.go | 251 ++++++++++++++++++ .../targetgrouppolicy_serviceexport_test.go | 198 ++++++++++++++ 5 files changed, 542 insertions(+), 3 deletions(-) diff --git a/docs/api-types/service-export.md b/docs/api-types/service-export.md index a330e7ec..d6421e1f 100644 --- a/docs/api-types/service-export.md +++ b/docs/api-types/service-export.md @@ -16,6 +16,8 @@ instead AWS Gateway API Controller uses its own version of the resource for the ServiceExport resources can be targeted by [`TargetGroupPolicy`](target-group-policy.md) to configure protocol, protocol version, and health check settings. When a TargetGroupPolicy is applied to a ServiceExport, the configuration is automatically propagated to all target groups across all clusters that participate in the multi-cluster service mesh, ensuring consistent behavior regardless of which cluster contains the route resource. +**Protocol Override with ExportedPorts**: When using the `exportedPorts` field, TargetGroupPolicy protocol and protocolVersion settings will override the default protocol inferred from the `routeType`. For example, you can specify `routeType: HTTP` in the ServiceExport but use `protocol: HTTPS` in the TargetGroupPolicy to enable secure communication between VPC Lattice and your backend pods. + ### Annotations (Legacy Method) * `application-networking.k8s.aws/port` @@ -115,3 +117,40 @@ spec: ``` This configuration ensures that all target groups created for the `inventory-service` across all clusters will use the same health check configuration, providing consistent health monitoring in multi-cluster deployments. + +### Protocol Override Example + +The following example demonstrates how to use TargetGroupPolicy to override the protocol for a ServiceExport with exportedPorts: + +```yaml +# ServiceExport with HTTP routeType +apiVersion: application-networking.k8s.aws/v1alpha1 +kind: ServiceExport +metadata: + name: secure-service +spec: + exportedPorts: + - port: 443 + routeType: HTTP # Default would be HTTP/1 +--- +# TargetGroupPolicy overriding to HTTPS +apiVersion: application-networking.k8s.aws/v1alpha1 +kind: TargetGroupPolicy +metadata: + name: https-override +spec: + targetRef: + group: "application-networking.k8s.aws" + kind: ServiceExport + name: secure-service + protocol: HTTPS # Overrides HTTP from routeType + protocolVersion: HTTP2 # Overrides HTTP1 default + healthCheck: + enabled: true + path: "/health" + protocol: HTTPS + protocolVersion: HTTP2 + statusMatch: "200-299" +``` + +In this example, even though the ServiceExport specifies `routeType: HTTP`, the TargetGroupPolicy configures the target group to use HTTPS with HTTP/2, enabling secure communication between VPC Lattice and your backend pods. diff --git a/docs/api-types/target-group-policy.md b/docs/api-types/target-group-policy.md index 522aecc5..03699012 100644 --- a/docs/api-types/target-group-policy.md +++ b/docs/api-types/target-group-policy.md @@ -30,6 +30,42 @@ However, the policy will not take effect unless the target is valid. +### ServiceExport with ExportedPorts + +When using ServiceExport with the `exportedPorts` field, TargetGroupPolicy protocol and protocolVersion settings will override the default protocol inferred from the `routeType`. This allows you to configure HTTPS or other protocols even when the routeType is HTTP. + +Example: + +```yaml +apiVersion: application-networking.k8s.aws/v1alpha1 +kind: ServiceExport +metadata: + name: my-service +spec: + exportedPorts: + - port: 80 + routeType: HTTP # Default would be HTTP/1 +--- +apiVersion: application-networking.k8s.aws/v1alpha1 +kind: TargetGroupPolicy +metadata: + name: https-override +spec: + targetRef: + group: "application-networking.k8s.aws" + kind: ServiceExport + name: my-service + protocol: HTTPS # Overrides HTTP from routeType + protocolVersion: HTTP2 # Overrides HTTP1 default + healthCheck: + enabled: true + path: "/health" + protocol: HTTPS + protocolVersion: HTTP2 +``` + +In this example, even though the ServiceExport specifies `routeType: HTTP`, the TargetGroupPolicy will configure the target group to use HTTPS with HTTP/2, providing secure communication between the VPC Lattice service and your backend pods. + ### Limitations and Considerations - Attaching TargetGroupPolicy to an existing Service that is already referenced by a route will result in a replacement diff --git a/pkg/gateway/model_build_targetgroup.go b/pkg/gateway/model_build_targetgroup.go index e872c347..8ddcc53e 100644 --- a/pkg/gateway/model_build_targetgroup.go +++ b/pkg/gateway/model_build_targetgroup.go @@ -188,13 +188,13 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroupForExportedPort(ctx return nil, err } - // Get health check config from policy - _, _, healthCheckConfig, err := parseTargetGroupConfig(tgp) + // Get protocol, protocolVersion, and health check config from policy + policyProtocol, policyProtocolVersion, healthCheckConfig, err := parseTargetGroupConfig(tgp) if err != nil { return nil, err } - // Set protocol and protocolVersion based on routeType + // Set default protocol and protocolVersion based on routeType var protocol, protocolVersion string switch exportedPort.RouteType { case "HTTP": @@ -210,6 +210,21 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroupForExportedPort(ctx return nil, fmt.Errorf("unsupported route type: %s", exportedPort.RouteType) } + // Override with TargetGroupPolicy settings if specified + if tgp != nil { + if tgp.Spec.Protocol != nil { + protocol = policyProtocol + } + // Only override protocolVersion if protocol is not TCP + if tgp.Spec.ProtocolVersion != nil && protocol != vpclattice.TargetGroupProtocolTcp { + protocolVersion = policyProtocolVersion + } + // For TCP protocol, ensure protocolVersion is empty + if protocol == vpclattice.TargetGroupProtocolTcp { + protocolVersion = "" + } + } + spec := model.TargetGroupSpec{ Type: model.TargetGroupTypeIP, Port: exportedPort.Port, diff --git a/pkg/gateway/model_build_targetgroup_test.go b/pkg/gateway/model_build_targetgroup_test.go index b5db1955..8d7fc339 100644 --- a/pkg/gateway/model_build_targetgroup_test.go +++ b/pkg/gateway/model_build_targetgroup_test.go @@ -7,6 +7,7 @@ import ( "testing" gwv1 "sigs.k8s.io/gateway-api/apis/v1" + gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client" "github.com/aws/aws-application-networking-k8s/pkg/config" @@ -1274,3 +1275,253 @@ func Test_TGModelByHTTPRouteBuild_AdditionalTags(t *testing.T) { }) } } + +func Test_TGModelByServiceExportWithExportedPorts_TargetGroupPolicy(t *testing.T) { + config.VpcID = "vpc-id" + config.ClusterName = "cluster-name" + + tests := []struct { + name string + svcExport *anv1alpha1.ServiceExport + svc *corev1.Service + tgp *anv1alpha1.TargetGroupPolicy + wantErrIsNil bool + wantProtocol string + wantProtocolVersion string + wantHealthCheckProto *string + description string + }{ + { + name: "ServiceExport with HTTP routeType and HTTPS protocol override from TargetGroupPolicy", + svcExport: &anv1alpha1.ServiceExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: "https-override", + Namespace: "bar", + }, + Spec: anv1alpha1.ServiceExportSpec{ + ExportedPorts: []anv1alpha1.ExportedPort{ + { + Port: 80, + RouteType: "HTTP", + }, + }, + }, + }, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "https-override", + Namespace: "bar", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Port: 80}, + }, + IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, + }, + }, + tgp: &anv1alpha1.TargetGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "https-override", + Namespace: "bar", + }, + Spec: anv1alpha1.TargetGroupPolicySpec{ + TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{ + Group: "application-networking.k8s.aws", + Kind: "ServiceExport", + Name: "https-override", + }, + Protocol: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(), + ProtocolVersion: func() *string { s := vpclattice.TargetGroupProtocolVersionHttp2; return &s }(), + HealthCheck: &anv1alpha1.HealthCheckConfig{ + Enabled: func() *bool { b := true; return &b }(), + Protocol: func() *anv1alpha1.HealthCheckProtocol { p := anv1alpha1.HealthCheckProtocolHTTPS; return &p }(), + ProtocolVersion: func() *anv1alpha1.HealthCheckProtocolVersion { + v := anv1alpha1.HealthCheckProtocolVersionHTTP2 + return &v + }(), + Port: func() *int64 { p := int64(80); return &p }(), + Path: func() *string { s := "/env"; return &s }(), + IntervalSeconds: func() *int64 { i := int64(10); return &i }(), + TimeoutSeconds: func() *int64 { t := int64(5); return &t }(), + HealthyThresholdCount: func() *int64 { c := int64(2); return &c }(), + UnhealthyThresholdCount: func() *int64 { c := int64(3); return &c }(), + StatusMatch: func() *string { s := "200-299"; return &s }(), + }, + }, + }, + wantErrIsNil: true, + wantProtocol: vpclattice.TargetGroupProtocolHttps, + wantProtocolVersion: vpclattice.TargetGroupProtocolVersionHttp2, + wantHealthCheckProto: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(), + description: "should use HTTPS protocol and HTTP2 from TargetGroupPolicy when specified", + }, + { + name: "ServiceExport with GRPC routeType and no TargetGroupPolicy override", + svcExport: &anv1alpha1.ServiceExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: "grpc-default", + Namespace: "bar", + }, + Spec: anv1alpha1.ServiceExportSpec{ + ExportedPorts: []anv1alpha1.ExportedPort{ + { + Port: 8081, + RouteType: "GRPC", + }, + }, + }, + }, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "grpc-default", + Namespace: "bar", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Port: 8081}, + }, + IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, + }, + }, + wantErrIsNil: true, + wantProtocol: vpclattice.TargetGroupProtocolHttp, + wantProtocolVersion: vpclattice.TargetGroupProtocolVersionGrpc, + description: "should use default GRPC protocol when no TargetGroupPolicy", + }, + { + name: "ServiceExport with TLS routeType and TCP protocol (no protocolVersion)", + svcExport: &anv1alpha1.ServiceExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-tcp", + Namespace: "bar", + }, + Spec: anv1alpha1.ServiceExportSpec{ + ExportedPorts: []anv1alpha1.ExportedPort{ + { + Port: 443, + RouteType: "TLS", + }, + }, + }, + }, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-tcp", + Namespace: "bar", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Port: 443}, + }, + IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, + }, + }, + tgp: &anv1alpha1.TargetGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-tcp", + Namespace: "bar", + }, + Spec: anv1alpha1.TargetGroupPolicySpec{ + TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{ + Group: "application-networking.k8s.aws", + Kind: "ServiceExport", + Name: "tls-tcp", + }, + Protocol: func() *string { s := vpclattice.TargetGroupProtocolTcp; return &s }(), + }, + }, + wantErrIsNil: true, + wantProtocol: vpclattice.TargetGroupProtocolTcp, + wantProtocolVersion: "", + description: "should use TCP protocol with empty protocolVersion", + }, + { + name: "ServiceExport with HTTP routeType and HTTPS protocol with HTTP1 override", + svcExport: &anv1alpha1.ServiceExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: "https-http1", + Namespace: "bar", + }, + Spec: anv1alpha1.ServiceExportSpec{ + ExportedPorts: []anv1alpha1.ExportedPort{ + { + Port: 80, + RouteType: "HTTP", + }, + }, + }, + }, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "https-http1", + Namespace: "bar", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + {Port: 80}, + }, + IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol}, + }, + }, + tgp: &anv1alpha1.TargetGroupPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "https-http1", + Namespace: "bar", + }, + Spec: anv1alpha1.TargetGroupPolicySpec{ + TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{ + Group: "application-networking.k8s.aws", + Kind: "ServiceExport", + Name: "https-http1", + }, + Protocol: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(), + ProtocolVersion: func() *string { s := vpclattice.TargetGroupProtocolVersionHttp1; return &s }(), + }, + }, + wantErrIsNil: true, + wantProtocol: vpclattice.TargetGroupProtocolHttps, + wantProtocolVersion: vpclattice.TargetGroupProtocolVersionHttp1, + description: "should use HTTPS protocol with HTTP1 from TargetGroupPolicy", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + anv1alpha1.Install(k8sSchema) + k8sClient := testclient.NewClientBuilder().WithScheme(k8sSchema).Build() + + assert.NoError(t, k8sClient.Create(ctx, tt.svc.DeepCopy())) + + if tt.tgp != nil { + assert.NoError(t, k8sClient.Create(ctx, tt.tgp.DeepCopy())) + } + + builder := NewSvcExportTargetGroupBuilder(gwlog.FallbackLogger, k8sClient) + + stack, err := builder.Build(ctx, tt.svcExport) + if !tt.wantErrIsNil { + assert.NotNil(t, err, tt.description) + return + } + assert.Nil(t, err, tt.description) + + var targetGroups []*model.TargetGroup + err = stack.ListResources(&targetGroups) + assert.Nil(t, err) + assert.Equal(t, 1, len(targetGroups)) + + tg := targetGroups[0] + assert.Equal(t, tt.wantProtocol, tg.Spec.Protocol, tt.description) + assert.Equal(t, tt.wantProtocolVersion, tg.Spec.ProtocolVersion, tt.description) + + if tt.wantHealthCheckProto != nil { + assert.NotNil(t, tg.Spec.HealthCheckConfig, tt.description) + assert.Equal(t, *tt.wantHealthCheckProto, *tg.Spec.HealthCheckConfig.Protocol, tt.description) + } + }) + } +} diff --git a/test/suites/integration/targetgrouppolicy_serviceexport_test.go b/test/suites/integration/targetgrouppolicy_serviceexport_test.go index 7751bf9e..7426f700 100644 --- a/test/suites/integration/targetgrouppolicy_serviceexport_test.go +++ b/test/suites/integration/targetgrouppolicy_serviceexport_test.go @@ -971,3 +971,201 @@ func createServiceTargetGroupPolicy( }, } } + +func createServiceExportTargetGroupPolicy( + serviceExport *anv1alpha1.ServiceExport, + config *ServiceTargetGroupPolicyConfig, +) *anv1alpha1.TargetGroupPolicy { + return &anv1alpha1.TargetGroupPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "TargetGroupPolicy", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: serviceExport.Namespace, + Name: config.PolicyName, + }, + Spec: anv1alpha1.TargetGroupPolicySpec{ + TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{ + Kind: gwv1.Kind("ServiceExport"), + Name: gwv1.ObjectName(serviceExport.Name), + Group: gwv1alpha2.Group("application-networking.k8s.aws"), + }, + Protocol: config.Protocol, + ProtocolVersion: config.ProtocolVersion, + HealthCheck: config.HealthCheck, + }, + } +} + +var _ = Describe("TargetGroupPolicy ServiceExport with ExportedPorts Integration Tests", Ordered, func() { + var ( + deployment *appsv1.Deployment + service *corev1.Service + serviceExport *anv1alpha1.ServiceExport + policy *anv1alpha1.TargetGroupPolicy + ) + + BeforeAll(func() { + deployment, service = testFramework.NewNginxApp(test.ElasticSearchOptions{ + Name: "tgp-exportedports-test", + Namespace: k8snamespace, + }) + + // Create ServiceExport with ExportedPorts + serviceExport = &anv1alpha1.ServiceExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: service.Name, + Namespace: service.Namespace, + Annotations: map[string]string{ + "application-networking.k8s.aws/federation": "amazon-vpc-lattice", + }, + }, + Spec: anv1alpha1.ServiceExportSpec{ + ExportedPorts: []anv1alpha1.ExportedPort{ + { + Port: 80, + RouteType: "HTTP", + }, + }, + }, + } + + testFramework.ExpectCreated(ctx, deployment, service, serviceExport) + }) + + AfterAll(func() { + testFramework.ExpectDeleted(ctx, deployment, service, serviceExport) + }) + + Context("TargetGroupPolicy protocol override for ServiceExport with ExportedPorts", func() { + AfterEach(func() { + if policy != nil { + testFramework.ExpectDeleted(ctx, policy) + policy = nil + } + }) + + It("should apply HTTPS protocol override from TargetGroupPolicy when ServiceExport uses HTTP routeType", func() { + // This test verifies the fix for the bug where ServiceExport with ExportedPorts + // ignored protocol settings from TargetGroupPolicy + policy = createServiceExportTargetGroupPolicy(serviceExport, &ServiceTargetGroupPolicyConfig{ + PolicyName: "exportedports-https-override", + Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + ProtocolVersion: aws.String(vpclattice.TargetGroupProtocolVersionHttp2), + HealthCheck: &anv1alpha1.HealthCheckConfig{ + Enabled: aws.Bool(true), + Path: aws.String("/health"), + IntervalSeconds: aws.Int64(10), + TimeoutSeconds: aws.Int64(5), + StatusMatch: aws.String("200-299"), + Protocol: (*anv1alpha1.HealthCheckProtocol)(aws.String("HTTPS")), + ProtocolVersion: (*anv1alpha1.HealthCheckProtocolVersion)(aws.String("HTTP2")), + }, + }) + + testFramework.ExpectCreated(ctx, policy) + + // Verify that the target group uses HTTPS protocol from TargetGroupPolicy + // instead of defaulting to HTTP based on routeType + Eventually(func(g Gomega) { + // Use GetTargetGroupWithProtocol to find the HTTPS target group + tgSummary := testFramework.GetTargetGroupWithProtocol(ctx, service, vpclattice.TargetGroupProtocolHttps, vpclattice.TargetGroupProtocolVersionHttp2) + tg := testFramework.GetFullTargetGroupFromSummary(ctx, tgSummary) + + // Verify protocol override is applied + g.Expect(*tg.Config.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttps)) + g.Expect(*tg.Config.ProtocolVersion).To(Equal(vpclattice.TargetGroupProtocolVersionHttp2)) + + // Verify health check configuration is also applied + g.Expect(*tg.Config.HealthCheck.Path).To(Equal("/health")) + g.Expect(*tg.Config.HealthCheck.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttps)) + g.Expect(*tg.Config.HealthCheck.ProtocolVersion).To(Equal(vpclattice.TargetGroupProtocolVersionHttp2)) + }).Within(90 * time.Second).WithPolling(3 * time.Second).Should(Succeed()) + }) + + It("should use default HTTP protocol when no TargetGroupPolicy is applied", func() { + // Verify default behavior without policy + Eventually(func(g Gomega) { + tgSummary := testFramework.GetTargetGroup(ctx, service) + tg := testFramework.GetFullTargetGroupFromSummary(ctx, tgSummary) + + // Should use HTTP protocol based on routeType + g.Expect(*tg.Config.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttp)) + g.Expect(*tg.Config.ProtocolVersion).To(Equal(vpclattice.TargetGroupProtocolVersionHttp1)) + }).Within(60 * time.Second).WithPolling(2 * time.Second).Should(Succeed()) + }) + + It("should apply HTTPS with HTTP1 protocol version override", func() { + policy = createServiceExportTargetGroupPolicy(serviceExport, &ServiceTargetGroupPolicyConfig{ + PolicyName: "exportedports-https-http1", + Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + ProtocolVersion: aws.String(vpclattice.TargetGroupProtocolVersionHttp1), + HealthCheck: &anv1alpha1.HealthCheckConfig{ + Path: aws.String("/api/health"), + IntervalSeconds: aws.Int64(15), + Protocol: (*anv1alpha1.HealthCheckProtocol)(aws.String("HTTPS")), + ProtocolVersion: (*anv1alpha1.HealthCheckProtocolVersion)(aws.String("HTTP1")), + }, + }) + + testFramework.ExpectCreated(ctx, policy) + + Eventually(func(g Gomega) { + tgSummary := testFramework.GetTargetGroupWithProtocol(ctx, service, vpclattice.TargetGroupProtocolHttps, vpclattice.TargetGroupProtocolVersionHttp1) + tg := testFramework.GetFullTargetGroupFromSummary(ctx, tgSummary) + + g.Expect(*tg.Config.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttps)) + g.Expect(*tg.Config.ProtocolVersion).To(Equal(vpclattice.TargetGroupProtocolVersionHttp1)) + g.Expect(*tg.Config.HealthCheck.Path).To(Equal("/api/health")) + g.Expect(*tg.Config.HealthCheck.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttps)) + }).Within(90 * time.Second).WithPolling(3 * time.Second).Should(Succeed()) + }) + + It("should handle protocol updates correctly", func() { + // Start with HTTP protocol + policy = createServiceExportTargetGroupPolicy(serviceExport, &ServiceTargetGroupPolicyConfig{ + PolicyName: "exportedports-protocol-update", + Protocol: aws.String(vpclattice.TargetGroupProtocolHttp), + ProtocolVersion: aws.String(vpclattice.TargetGroupProtocolVersionHttp1), + }) + + testFramework.ExpectCreated(ctx, policy) + + // Verify initial HTTP configuration + Eventually(func(g Gomega) { + tgSummary := testFramework.GetTargetGroupWithProtocol(ctx, service, vpclattice.TargetGroupProtocolHttp, vpclattice.TargetGroupProtocolVersionHttp1) + tg := testFramework.GetFullTargetGroupFromSummary(ctx, tgSummary) + g.Expect(*tg.Config.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttp)) + g.Expect(*tg.Config.ProtocolVersion).To(Equal(vpclattice.TargetGroupProtocolVersionHttp1)) + }).Within(60 * time.Second).WithPolling(2 * time.Second).Should(Succeed()) + + // Note: Protocol changes require target group replacement in VPC Lattice + // So we delete and recreate the policy with new protocol + testFramework.ExpectDeleted(ctx, policy) + + // Wait for target group to return to default + Eventually(func(g Gomega) { + tgSummary := testFramework.GetTargetGroup(ctx, service) + tg := testFramework.GetFullTargetGroupFromSummary(ctx, tgSummary) + g.Expect(*tg.Config.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttp)) + }).Within(90 * time.Second).WithPolling(3 * time.Second).Should(Succeed()) + + // Create new policy with HTTPS + policy = createServiceExportTargetGroupPolicy(serviceExport, &ServiceTargetGroupPolicyConfig{ + PolicyName: "exportedports-protocol-update-https", + Protocol: aws.String(vpclattice.TargetGroupProtocolHttps), + ProtocolVersion: aws.String(vpclattice.TargetGroupProtocolVersionHttp2), + }) + + testFramework.ExpectCreated(ctx, policy) + + // Verify HTTPS configuration + Eventually(func(g Gomega) { + tgSummary := testFramework.GetTargetGroupWithProtocol(ctx, service, vpclattice.TargetGroupProtocolHttps, vpclattice.TargetGroupProtocolVersionHttp2) + tg := testFramework.GetFullTargetGroupFromSummary(ctx, tgSummary) + g.Expect(*tg.Config.Protocol).To(Equal(vpclattice.TargetGroupProtocolHttps)) + g.Expect(*tg.Config.ProtocolVersion).To(Equal(vpclattice.TargetGroupProtocolVersionHttp2)) + }).Within(90 * time.Second).WithPolling(3 * time.Second).Should(Succeed()) + }) + }) +})