Skip to content

Commit a98bedf

Browse files
committed
Correct HTTPS with ServiceExport
1 parent 200a087 commit a98bedf

File tree

5 files changed

+549
-3
lines changed

5 files changed

+549
-3
lines changed

docs/api-types/service-export.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ instead AWS Gateway API Controller uses its own version of the resource for the
1616

1717
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.
1818

19+
**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.
20+
1921
### Annotations (Legacy Method)
2022

2123
* `application-networking.k8s.aws/port`
@@ -115,3 +117,40 @@ spec:
115117
```
116118

117119
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.
120+
121+
### Protocol Override Example
122+
123+
The following example demonstrates how to use TargetGroupPolicy to override the protocol for a ServiceExport with exportedPorts:
124+
125+
```yaml
126+
# ServiceExport with HTTP routeType
127+
apiVersion: application-networking.k8s.aws/v1alpha1
128+
kind: ServiceExport
129+
metadata:
130+
name: secure-service
131+
spec:
132+
exportedPorts:
133+
- port: 443
134+
routeType: HTTP # Default would be HTTP/1
135+
---
136+
# TargetGroupPolicy overriding to HTTPS
137+
apiVersion: application-networking.k8s.aws/v1alpha1
138+
kind: TargetGroupPolicy
139+
metadata:
140+
name: https-override
141+
spec:
142+
targetRef:
143+
group: "application-networking.k8s.aws"
144+
kind: ServiceExport
145+
name: secure-service
146+
protocol: HTTPS # Overrides HTTP from routeType
147+
protocolVersion: HTTP2 # Overrides HTTP1 default
148+
healthCheck:
149+
enabled: true
150+
path: "/health"
151+
protocol: HTTPS
152+
protocolVersion: HTTP2
153+
statusMatch: "200-299"
154+
```
155+
156+
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.

docs/api-types/target-group-policy.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,42 @@ However, the policy will not take effect unless the target is valid.
3030

3131

3232

33+
### ServiceExport with ExportedPorts
34+
35+
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.
36+
37+
Example:
38+
39+
```yaml
40+
apiVersion: application-networking.k8s.aws/v1alpha1
41+
kind: ServiceExport
42+
metadata:
43+
name: my-service
44+
spec:
45+
exportedPorts:
46+
- port: 80
47+
routeType: HTTP # Default would be HTTP/1
48+
---
49+
apiVersion: application-networking.k8s.aws/v1alpha1
50+
kind: TargetGroupPolicy
51+
metadata:
52+
name: https-override
53+
spec:
54+
targetRef:
55+
group: "application-networking.k8s.aws"
56+
kind: ServiceExport
57+
name: my-service
58+
protocol: HTTPS # Overrides HTTP from routeType
59+
protocolVersion: HTTP2 # Overrides HTTP1 default
60+
healthCheck:
61+
enabled: true
62+
path: "/health"
63+
protocol: HTTPS
64+
protocolVersion: HTTP2
65+
```
66+
67+
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.
68+
3369
### Limitations and Considerations
3470

3571
- Attaching TargetGroupPolicy to an existing Service that is already referenced by a route will result in a replacement

pkg/gateway/model_build_targetgroup.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,13 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroupForExportedPort(ctx
188188
return nil, err
189189
}
190190

191-
// Get health check config from policy
192-
_, _, healthCheckConfig, err := parseTargetGroupConfig(tgp)
191+
// Get protocol, protocolVersion, and health check config from policy
192+
policyProtocol, policyProtocolVersion, healthCheckConfig, err := parseTargetGroupConfig(tgp)
193193
if err != nil {
194194
return nil, err
195195
}
196196

197-
// Set protocol and protocolVersion based on routeType
197+
// Set default protocol and protocolVersion based on routeType
198198
var protocol, protocolVersion string
199199
switch exportedPort.RouteType {
200200
case "HTTP":
@@ -210,6 +210,21 @@ func (t *svcExportTargetGroupModelBuildTask) buildTargetGroupForExportedPort(ctx
210210
return nil, fmt.Errorf("unsupported route type: %s", exportedPort.RouteType)
211211
}
212212

213+
// Override with TargetGroupPolicy settings if specified
214+
if tgp != nil {
215+
if tgp.Spec.Protocol != nil {
216+
protocol = policyProtocol
217+
}
218+
// Only override protocolVersion if protocol is not TCP
219+
if tgp.Spec.ProtocolVersion != nil && protocol != vpclattice.TargetGroupProtocolTcp {
220+
protocolVersion = policyProtocolVersion
221+
}
222+
// For TCP protocol, ensure protocolVersion is empty
223+
if protocol == vpclattice.TargetGroupProtocolTcp {
224+
protocolVersion = ""
225+
}
226+
}
227+
213228
spec := model.TargetGroupSpec{
214229
Type: model.TargetGroupTypeIP,
215230
Port: exportedPort.Port,

pkg/gateway/model_build_targetgroup_test.go

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
10+
gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
1011

1112
mock_client "github.com/aws/aws-application-networking-k8s/mocks/controller-runtime/client"
1213
"github.com/aws/aws-application-networking-k8s/pkg/config"
@@ -1274,3 +1275,253 @@ func Test_TGModelByHTTPRouteBuild_AdditionalTags(t *testing.T) {
12741275
})
12751276
}
12761277
}
1278+
1279+
func Test_TGModelByServiceExportWithExportedPorts_TargetGroupPolicy(t *testing.T) {
1280+
config.VpcID = "vpc-id"
1281+
config.ClusterName = "cluster-name"
1282+
1283+
tests := []struct {
1284+
name string
1285+
svcExport *anv1alpha1.ServiceExport
1286+
svc *corev1.Service
1287+
tgp *anv1alpha1.TargetGroupPolicy
1288+
wantErrIsNil bool
1289+
wantProtocol string
1290+
wantProtocolVersion string
1291+
wantHealthCheckProto *string
1292+
description string
1293+
}{
1294+
{
1295+
name: "ServiceExport with HTTP routeType and HTTPS protocol override from TargetGroupPolicy",
1296+
svcExport: &anv1alpha1.ServiceExport{
1297+
ObjectMeta: metav1.ObjectMeta{
1298+
Name: "https-override",
1299+
Namespace: "bar",
1300+
},
1301+
Spec: anv1alpha1.ServiceExportSpec{
1302+
ExportedPorts: []anv1alpha1.ExportedPort{
1303+
{
1304+
Port: 80,
1305+
RouteType: "HTTP",
1306+
},
1307+
},
1308+
},
1309+
},
1310+
svc: &corev1.Service{
1311+
ObjectMeta: metav1.ObjectMeta{
1312+
Name: "https-override",
1313+
Namespace: "bar",
1314+
},
1315+
Spec: corev1.ServiceSpec{
1316+
Ports: []corev1.ServicePort{
1317+
{Port: 80},
1318+
},
1319+
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
1320+
},
1321+
},
1322+
tgp: &anv1alpha1.TargetGroupPolicy{
1323+
ObjectMeta: metav1.ObjectMeta{
1324+
Name: "https-override",
1325+
Namespace: "bar",
1326+
},
1327+
Spec: anv1alpha1.TargetGroupPolicySpec{
1328+
TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{
1329+
Group: "application-networking.k8s.aws",
1330+
Kind: "ServiceExport",
1331+
Name: "https-override",
1332+
},
1333+
Protocol: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(),
1334+
ProtocolVersion: func() *string { s := vpclattice.TargetGroupProtocolVersionHttp2; return &s }(),
1335+
HealthCheck: &anv1alpha1.HealthCheckConfig{
1336+
Enabled: func() *bool { b := true; return &b }(),
1337+
Protocol: func() *anv1alpha1.HealthCheckProtocol { p := anv1alpha1.HealthCheckProtocolHTTPS; return &p }(),
1338+
ProtocolVersion: func() *anv1alpha1.HealthCheckProtocolVersion {
1339+
v := anv1alpha1.HealthCheckProtocolVersionHTTP2
1340+
return &v
1341+
}(),
1342+
Port: func() *int64 { p := int64(80); return &p }(),
1343+
Path: func() *string { s := "/env"; return &s }(),
1344+
IntervalSeconds: func() *int64 { i := int64(10); return &i }(),
1345+
TimeoutSeconds: func() *int64 { t := int64(5); return &t }(),
1346+
HealthyThresholdCount: func() *int64 { c := int64(2); return &c }(),
1347+
UnhealthyThresholdCount: func() *int64 { c := int64(3); return &c }(),
1348+
StatusMatch: func() *string { s := "200-299"; return &s }(),
1349+
},
1350+
},
1351+
},
1352+
wantErrIsNil: true,
1353+
wantProtocol: vpclattice.TargetGroupProtocolHttps,
1354+
wantProtocolVersion: vpclattice.TargetGroupProtocolVersionHttp2,
1355+
wantHealthCheckProto: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(),
1356+
description: "should use HTTPS protocol and HTTP2 from TargetGroupPolicy when specified",
1357+
},
1358+
{
1359+
name: "ServiceExport with GRPC routeType and no TargetGroupPolicy override",
1360+
svcExport: &anv1alpha1.ServiceExport{
1361+
ObjectMeta: metav1.ObjectMeta{
1362+
Name: "grpc-default",
1363+
Namespace: "bar",
1364+
},
1365+
Spec: anv1alpha1.ServiceExportSpec{
1366+
ExportedPorts: []anv1alpha1.ExportedPort{
1367+
{
1368+
Port: 8081,
1369+
RouteType: "GRPC",
1370+
},
1371+
},
1372+
},
1373+
},
1374+
svc: &corev1.Service{
1375+
ObjectMeta: metav1.ObjectMeta{
1376+
Name: "grpc-default",
1377+
Namespace: "bar",
1378+
},
1379+
Spec: corev1.ServiceSpec{
1380+
Ports: []corev1.ServicePort{
1381+
{Port: 8081},
1382+
},
1383+
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
1384+
},
1385+
},
1386+
wantErrIsNil: true,
1387+
wantProtocol: vpclattice.TargetGroupProtocolHttp,
1388+
wantProtocolVersion: vpclattice.TargetGroupProtocolVersionGrpc,
1389+
description: "should use default GRPC protocol when no TargetGroupPolicy",
1390+
},
1391+
{
1392+
name: "ServiceExport with TLS routeType and TCP protocol (no protocolVersion)",
1393+
svcExport: &anv1alpha1.ServiceExport{
1394+
ObjectMeta: metav1.ObjectMeta{
1395+
Name: "tls-tcp",
1396+
Namespace: "bar",
1397+
},
1398+
Spec: anv1alpha1.ServiceExportSpec{
1399+
ExportedPorts: []anv1alpha1.ExportedPort{
1400+
{
1401+
Port: 443,
1402+
RouteType: "TLS",
1403+
},
1404+
},
1405+
},
1406+
},
1407+
svc: &corev1.Service{
1408+
ObjectMeta: metav1.ObjectMeta{
1409+
Name: "tls-tcp",
1410+
Namespace: "bar",
1411+
},
1412+
Spec: corev1.ServiceSpec{
1413+
Ports: []corev1.ServicePort{
1414+
{Port: 443},
1415+
},
1416+
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
1417+
},
1418+
},
1419+
tgp: &anv1alpha1.TargetGroupPolicy{
1420+
ObjectMeta: metav1.ObjectMeta{
1421+
Name: "tls-tcp",
1422+
Namespace: "bar",
1423+
},
1424+
Spec: anv1alpha1.TargetGroupPolicySpec{
1425+
TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{
1426+
Group: "application-networking.k8s.aws",
1427+
Kind: "ServiceExport",
1428+
Name: "tls-tcp",
1429+
},
1430+
Protocol: func() *string { s := vpclattice.TargetGroupProtocolTcp; return &s }(),
1431+
},
1432+
},
1433+
wantErrIsNil: true,
1434+
wantProtocol: vpclattice.TargetGroupProtocolTcp,
1435+
wantProtocolVersion: "",
1436+
description: "should use TCP protocol with empty protocolVersion",
1437+
},
1438+
{
1439+
name: "ServiceExport with HTTP routeType and HTTPS protocol with HTTP1 override",
1440+
svcExport: &anv1alpha1.ServiceExport{
1441+
ObjectMeta: metav1.ObjectMeta{
1442+
Name: "https-http1",
1443+
Namespace: "bar",
1444+
},
1445+
Spec: anv1alpha1.ServiceExportSpec{
1446+
ExportedPorts: []anv1alpha1.ExportedPort{
1447+
{
1448+
Port: 80,
1449+
RouteType: "HTTP",
1450+
},
1451+
},
1452+
},
1453+
},
1454+
svc: &corev1.Service{
1455+
ObjectMeta: metav1.ObjectMeta{
1456+
Name: "https-http1",
1457+
Namespace: "bar",
1458+
},
1459+
Spec: corev1.ServiceSpec{
1460+
Ports: []corev1.ServicePort{
1461+
{Port: 80},
1462+
},
1463+
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
1464+
},
1465+
},
1466+
tgp: &anv1alpha1.TargetGroupPolicy{
1467+
ObjectMeta: metav1.ObjectMeta{
1468+
Name: "https-http1",
1469+
Namespace: "bar",
1470+
},
1471+
Spec: anv1alpha1.TargetGroupPolicySpec{
1472+
TargetRef: &gwv1alpha2.NamespacedPolicyTargetReference{
1473+
Group: "application-networking.k8s.aws",
1474+
Kind: "ServiceExport",
1475+
Name: "https-http1",
1476+
},
1477+
Protocol: func() *string { s := vpclattice.TargetGroupProtocolHttps; return &s }(),
1478+
ProtocolVersion: func() *string { s := vpclattice.TargetGroupProtocolVersionHttp1; return &s }(),
1479+
},
1480+
},
1481+
wantErrIsNil: true,
1482+
wantProtocol: vpclattice.TargetGroupProtocolHttps,
1483+
wantProtocolVersion: vpclattice.TargetGroupProtocolVersionHttp1,
1484+
description: "should use HTTPS protocol with HTTP1 from TargetGroupPolicy",
1485+
},
1486+
}
1487+
1488+
for _, tt := range tests {
1489+
t.Run(tt.name, func(t *testing.T) {
1490+
ctx := context.TODO()
1491+
1492+
k8sSchema := runtime.NewScheme()
1493+
clientgoscheme.AddToScheme(k8sSchema)
1494+
anv1alpha1.Install(k8sSchema)
1495+
k8sClient := testclient.NewClientBuilder().WithScheme(k8sSchema).Build()
1496+
1497+
assert.NoError(t, k8sClient.Create(ctx, tt.svc.DeepCopy()))
1498+
1499+
if tt.tgp != nil {
1500+
assert.NoError(t, k8sClient.Create(ctx, tt.tgp.DeepCopy()))
1501+
}
1502+
1503+
builder := NewSvcExportTargetGroupBuilder(gwlog.FallbackLogger, k8sClient)
1504+
1505+
stack, err := builder.Build(ctx, tt.svcExport)
1506+
if !tt.wantErrIsNil {
1507+
assert.NotNil(t, err, tt.description)
1508+
return
1509+
}
1510+
assert.Nil(t, err, tt.description)
1511+
1512+
var targetGroups []*model.TargetGroup
1513+
err = stack.ListResources(&targetGroups)
1514+
assert.Nil(t, err)
1515+
assert.Equal(t, 1, len(targetGroups))
1516+
1517+
tg := targetGroups[0]
1518+
assert.Equal(t, tt.wantProtocol, tg.Spec.Protocol, tt.description)
1519+
assert.Equal(t, tt.wantProtocolVersion, tg.Spec.ProtocolVersion, tt.description)
1520+
1521+
if tt.wantHealthCheckProto != nil {
1522+
assert.NotNil(t, tg.Spec.HealthCheckConfig, tt.description)
1523+
assert.Equal(t, *tt.wantHealthCheckProto, *tg.Spec.HealthCheckConfig.Protocol, tt.description)
1524+
}
1525+
})
1526+
}
1527+
}

0 commit comments

Comments
 (0)