Skip to content

Commit b344f11

Browse files
committed
Update service connect config validator
to validate: 1. fields consumed and proceeded by ECS Agent 2. fields with a global standard
1 parent 67240c1 commit b344f11

File tree

3 files changed

+48
-99
lines changed

3 files changed

+48
-99
lines changed

agent/api/serviceconnect/service_connect_validator.go

+46-57
Original file line numberDiff line numberDiff line change
@@ -24,40 +24,47 @@ import (
2424
)
2525

2626
const (
27-
BridgeNetworkMode = "bridge"
28-
AWSVPCNetworkMode = "awsvpc"
29-
invalidEgressConfigFormat = `no service connect %s in the egress config. %s`
30-
portCollisionFormat = `%s port collision detected in the ingress config with the %s port=%d, and listener name=%s`
31-
invalidIngressPortFormat = `the %s port=%d in the ingress config is not valid: %w`
32-
warningIngressPortFormat = `Service connect config validation: %s port should not exist for %s mode in the ingress config`
33-
invalidDnsEntryFormat = `no %s in the DNS config hostname=%s, address=%s`
27+
BridgeNetworkMode = "bridge"
28+
AWSVPCNetworkMode = "awsvpc"
29+
missingContainerInTaskFormat = `service connect container name=%s does not exist in the task`
30+
duplicateContainerInTaskFormat = `found %d duplicate service connect container name=%s in the task`
31+
invalidCidrFormat = `CIDR=%s is not a valid %s CIDR`
32+
portCollisionFormat = `%s port collision detected in the ingress config with %s port=%d, and listener name=%s`
33+
invalidDnsAddressFormat = `hostname=%s, address=%s in the DNS config is not valid: %w`
34+
noScSupportNetworkModeFormat = `service connect does not support for %s newtork mode`
35+
missingListenerInIngressFormat = `missing listener name in the ingress config with intercept port=%d`
36+
invalidPortRangeFormat = `port=%d is not a valid port. A valid port ranges from 1 through 65535`
37+
invalidIpAddressFormat = `address=%s is not a valid IP address`
38+
invalidIngressPortFormat = `%s port=%d in the ingress config is not valid: %w`
39+
warningIngressPortFormat = `Service connect config: %s port should not exist in the ingress config for %s network mode`
40+
missingDnsEntryFormat = `missing %s in the DNS config with hostname=%s, and address=%s`
3441
)
3542

36-
// validateContainerName validates the service connect container name.
43+
// validateContainerName validates the service connect container name exists in the task and no duplication.
3744
func validateContainerName(scContainerName string, taskContainers []*ecsacs.Container) error {
3845
// service connect container name is required
3946
if scContainerName == "" {
4047
return fmt.Errorf("missing service connect container name")
4148
}
4249

43-
// validate the specified service connect container name exists in the task definition
44-
numOfFoundSCContainer := 0
50+
// validate the specified service connect container name exists in the task
51+
numOfFoundScContainer := 0
4552
for _, container := range taskContainers {
4653
if aws.StringValue(container.Name) == scContainerName {
47-
numOfFoundSCContainer += 1
54+
numOfFoundScContainer += 1
4855
}
4956
}
5057

51-
if numOfFoundSCContainer == 0 {
52-
return fmt.Errorf("service connect container name=%s does not exist in the task", scContainerName)
53-
} else if numOfFoundSCContainer > 1 {
54-
return fmt.Errorf("found %d duplicate service connect container name=%s exist in the task", numOfFoundSCContainer, scContainerName)
58+
if numOfFoundScContainer == 0 {
59+
return fmt.Errorf(missingContainerInTaskFormat, scContainerName)
60+
} else if numOfFoundScContainer > 1 {
61+
return fmt.Errorf(duplicateContainerInTaskFormat, numOfFoundScContainer, scContainerName)
5562
}
5663

5764
return nil
5865
}
5966

60-
// validateEgressConfig validates the service connect egress config.
67+
// validateEgressConfig validates the listener name, IPv4 CIDR format and IPv6 CIDR format in the service connect egress config.
6168
func validateEgressConfig(scEgressConfig *EgressConfig, ipv6Enabled bool) error {
6269
// egress config can be empty for the first service since there are no other tasks that it can talk to
6370
if scEgressConfig == nil {
@@ -66,32 +73,21 @@ func validateEgressConfig(scEgressConfig *EgressConfig, ipv6Enabled bool) error
6673

6774
// ListenerName is required if the egress config exists
6875
if scEgressConfig.ListenerName == "" {
69-
return fmt.Errorf(invalidEgressConfigFormat, "listener name", "")
70-
}
71-
72-
// VIP is required if the egress config exists
73-
// IPV4CIDR should be always required because an IPv6-only mode is not supoorted at this moment
74-
if scEgressConfig.VIP.IPV4CIDR == "" {
75-
return fmt.Errorf(invalidEgressConfigFormat, "VIP IPv4CIDR", "")
76-
}
77-
78-
// IPV6CIDR is required when IPv6 is enabled
79-
if ipv6Enabled && scEgressConfig.VIP.IPV6CIDR == "" {
80-
return fmt.Errorf(invalidEgressConfigFormat, "VIP IPv6CIDR", "It must not be empty when the task is IPv6 enabled")
76+
return fmt.Errorf("missing listener name in the egress config")
8177
}
8278

8379
// validate IPV4CIDR if it exists
8480
if scEgressConfig.VIP.IPV4CIDR != "" {
85-
trimmedIpv4cidr := strings.TrimSpace(scEgressConfig.VIP.IPV4CIDR)
86-
if err := validateCIDR(trimmedIpv4cidr, "IPv4"); err != nil {
81+
trimmedIpv4Cidr := strings.TrimSpace(scEgressConfig.VIP.IPV4CIDR)
82+
if err := validateCIDR(trimmedIpv4Cidr, "IPv4"); err != nil {
8783
return err
8884
}
8985
}
9086

9187
// validate IPV6CIDR if it exists
9288
if scEgressConfig.VIP.IPV6CIDR != "" {
93-
trimmedIpv6cidr := strings.TrimSpace(scEgressConfig.VIP.IPV6CIDR)
94-
if err := validateCIDR(trimmedIpv6cidr, "IPv6"); err != nil {
89+
trimmedIpv6Cidr := strings.TrimSpace(scEgressConfig.VIP.IPV6CIDR)
90+
if err := validateCIDR(trimmedIpv6Cidr, "IPv6"); err != nil {
9591
return err
9692
}
9793
}
@@ -108,7 +104,7 @@ func validateCIDR(cidr, protocol string) error {
108104
}
109105
}
110106

111-
return fmt.Errorf("cidr=%s is not a valid %s CIDR", cidr, protocol)
107+
return fmt.Errorf(invalidCidrFormat, cidr, protocol)
112108
}
113109

114110
// getProtocol returns validity of the given IP based on the target protocol.
@@ -128,27 +124,22 @@ func getProtocol(ip net.IP, protocol string) bool {
128124
return false
129125
}
130126

131-
// validateDnsConfig validates the service connnect DNS config.
132-
func validateDnsConfig(scDnsConfligList []DNSConfigEntry, scEgressConfig *EgressConfig, ipv6Enabled bool) error {
133-
// DNS config associates to egress config
134-
if len(scDnsConfligList) == 0 && scEgressConfig != nil {
135-
return fmt.Errorf("no service connect DNS config. The DNS config is required when the egress config exists")
136-
}
137-
127+
// validateDnsConfig validates hostnames and addresses in the service connnect DNS config.
128+
func validateDnsConfig(scDnsConfligList []DNSConfigEntry) error {
138129
for _, dnsEntry := range scDnsConfligList {
139130
// HostName is required
140131
if dnsEntry.HostName == "" {
141-
return fmt.Errorf(invalidDnsEntryFormat, "hostname", dnsEntry.HostName, dnsEntry.Address)
132+
return fmt.Errorf(missingDnsEntryFormat, "hostname", dnsEntry.HostName, dnsEntry.Address)
142133
}
143134

144135
// Address is required
145136
if dnsEntry.Address == "" {
146-
return fmt.Errorf(invalidDnsEntryFormat, "address", dnsEntry.HostName, dnsEntry.Address)
137+
return fmt.Errorf(missingDnsEntryFormat, "address", dnsEntry.HostName, dnsEntry.Address)
147138
}
148139

149140
// validate the address is a valid IPv4/IPv6 address
150141
if err := validateAddress(dnsEntry.Address); err != nil {
151-
return fmt.Errorf("invalid address in the DNS config hostname=%s, address=%s: %w", dnsEntry.HostName, dnsEntry.Address, err)
142+
return fmt.Errorf(invalidDnsAddressFormat, dnsEntry.HostName, dnsEntry.Address, err)
152143
}
153144
}
154145

@@ -158,7 +149,7 @@ func validateDnsConfig(scDnsConfligList []DNSConfigEntry, scEgressConfig *Egress
158149
// validateAddress validates the passed address is a valid IPv4/IPv6 address.
159150
func validateAddress(address string) error {
160151
if ip := net.ParseIP(address); ip == nil {
161-
return fmt.Errorf("address=%s is not a valid IP address", address)
152+
return fmt.Errorf(invalidIpAddressFormat, address)
162153
}
163154
return nil
164155
}
@@ -176,7 +167,7 @@ func validateIngressConfig(scIngressConfigList []IngressConfigEntry, taskNetwork
176167
return err
177168
}
178169
default:
179-
return fmt.Errorf("service connect does not support for %s newtork mode", taskNetworkMode)
170+
return fmt.Errorf(noScSupportNetworkModeFormat, taskNetworkMode)
180171
}
181172

182173
return nil
@@ -215,7 +206,7 @@ func validateIngressConfigEntry(scIngressConfigList []IngressConfigEntry, networ
215206
if err := validateInterceptPort(interceptPortValue, entry.ListenerName, interceptAndListenerPortsMap); err != nil {
216207
return err
217208
}
218-
// Save the listener port value
209+
// save the listener port value
219210
interceptAndListenerPortsMap[interceptPortValue] = true
220211
}
221212

@@ -225,7 +216,7 @@ func validateIngressConfigEntry(scIngressConfigList []IngressConfigEntry, networ
225216
if err := validateListenerPort(listenerPortValue, entry.ListenerName, interceptAndListenerPortsMap); err != nil {
226217
return err
227218
}
228-
// Save the listener port value
219+
// save the listener port value
229220
interceptAndListenerPortsMap[listenerPortValue] = true
230221
}
231222

@@ -235,7 +226,7 @@ func validateIngressConfigEntry(scIngressConfigList []IngressConfigEntry, networ
235226
if err := validateHostPort(hostPortValue, entry.ListenerName, hostPortsMap); err != nil {
236227
return err
237228
}
238-
// Save the host port value
229+
// save the host port value
239230
hostPortsMap[hostPortValue] = true
240231
}
241232
}
@@ -250,7 +241,7 @@ func validateInterceptPort(interceptPortValue uint16, listenerName string, inter
250241
}
251242

252243
if listenerName == "" {
253-
return fmt.Errorf("no listener name in the ingress config with the intercept port=%d", interceptPortValue)
244+
return fmt.Errorf(missingListenerInIngressFormat, interceptPortValue)
254245
}
255246

256247
if present := interceptAndListenerPortsMap[interceptPortValue]; present {
@@ -293,10 +284,13 @@ func validatePort(port uint16) error {
293284
return nil
294285
}
295286

296-
return fmt.Errorf("the port=%d is an invalid port. A valid port ranges from 1 through 65535", port)
287+
return fmt.Errorf(invalidPortRangeFormat, port)
297288
}
298289

299-
// ValidateSCConfig validates service connect container name, config, egress config, and ingress config.
290+
// ValidateServiceConnectConfig validates service connect container name,
291+
// fields in egress config, dns config and ingress config when
292+
// 1) fields consumed and proceeded by ECS Agent
293+
// 2) fields with a global standard, e.g. CIDR format
300294
func ValidateServiceConnectConfig(scConfig *Config,
301295
taskContainers []*ecsacs.Container,
302296
taskNetworkMode string,
@@ -305,16 +299,11 @@ func ValidateServiceConnectConfig(scConfig *Config,
305299
return err
306300
}
307301

308-
// egress config and ingress config should not both be nil/empty
309-
if scConfig.EgressConfig == nil && len(scConfig.IngressConfig) == 0 {
310-
return fmt.Errorf("egress config and ingress config should not both be nil/empty")
311-
}
312-
313302
if err := validateEgressConfig(scConfig.EgressConfig, ipv6Enabled); err != nil {
314303
return err
315304
}
316305

317-
if err := validateDnsConfig(scConfig.DNSConfig, scConfig.EgressConfig, ipv6Enabled); err != nil {
306+
if err := validateDnsConfig(scConfig.DNSConfig); err != nil {
318307
return err
319308
}
320309

agent/api/serviceconnect/service_connect_validator_test.go

+1-41
Original file line numberDiff line numberDiff line change
@@ -253,25 +253,16 @@ func TestValidateServiceConnectConfigWithEmptyConfig(t *testing.T) {
253253
testName string
254254
testEgressConfigIsEmpty bool
255255
testIngressConfigIsEmpty bool
256-
shouldError bool
257256
}{
258257
{
259258
testName: "AWSVPC default case with the empty egress config and dns config",
260259
testEgressConfigIsEmpty: true,
261260
testIngressConfigIsEmpty: false,
262-
shouldError: false,
263261
},
264262
{
265263
testName: "AWSVPC default case with the empty ingress config",
266264
testEgressConfigIsEmpty: false,
267265
testIngressConfigIsEmpty: true,
268-
shouldError: false,
269-
},
270-
{
271-
testName: "AWSVPC default case with the empty ingress config and engress config",
272-
testEgressConfigIsEmpty: true,
273-
testIngressConfigIsEmpty: true,
274-
shouldError: true,
275266
},
276267
}
277268

@@ -298,11 +289,7 @@ func TestValidateServiceConnectConfigWithEmptyConfig(t *testing.T) {
298289
testIngressConfig,
299290
)
300291
err := ValidateServiceConnectConfig(testServiceConnectConfig, testTaskContainers, AWSVPCNetworkMode, false)
301-
if tc.shouldError {
302-
assert.Error(t, err)
303-
} else {
304-
assert.NoError(t, err)
305-
}
292+
assert.NoError(t, err)
306293
})
307294
}
308295
}
@@ -344,24 +331,6 @@ func TestValidateServiceConnectConfigWithError(t *testing.T) {
344331
testDnsConfigEntry: getTestDnsConfigEntry(testHostName, testIPv4Address),
345332
testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
346333
},
347-
{
348-
testName: "AWSVPC default case with no IPv4 CIDR in the egress config when Ipv6 is not enabled",
349-
testNetworkMode: AWSVPCNetworkMode,
350-
testIsIPv6Enabled: false,
351-
testContainerName: testServiceConnectContainerName,
352-
testEgressConfig: getTestEgressConfig(testOutboundListenerName, "", ""),
353-
testDnsConfigEntry: getTestDnsConfigEntry(testHostName, testIPv4Address),
354-
testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
355-
},
356-
{
357-
testName: "AWSVPC default case with no IPv6 CIDR in the egress config when IPv6 is enabled",
358-
testNetworkMode: AWSVPCNetworkMode,
359-
testIsIPv6Enabled: true,
360-
testContainerName: testServiceConnectContainerName,
361-
testEgressConfig: getTestEgressConfig(testOutboundListenerName, testIPv4Cidr, ""),
362-
testDnsConfigEntry: getTestDnsConfigEntry(testHostName, testIPv6Address),
363-
testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, testInboundListenerName, false, uint16(0), testAwsVpcDefaultInterceptPort, aws.Uint16(0)),
364-
},
365334
{
366335
testName: "AWSVPC override case with the invalid IPv4 CIDR in the egress config",
367336
testNetworkMode: AWSVPCNetworkMode,
@@ -380,15 +349,6 @@ func TestValidateServiceConnectConfigWithError(t *testing.T) {
380349
testDnsConfigEntry: getTestDnsConfigEntry(testHostName, testIPv6Address),
381350
testIngressConfigEntry: getTestIngressConfigEntry(AWSVPCNetworkMode, "", true, testListenerPort, aws.Uint16(0), aws.Uint16(0)),
382351
},
383-
{
384-
testName: "Bridge default case with the egress config but no dns config",
385-
testNetworkMode: BridgeNetworkMode,
386-
testIsIPv6Enabled: false,
387-
testContainerName: testServiceConnectContainerName,
388-
testEgressConfig: getTestEgressConfig(testOutboundListenerName, "", testIPv6Cidr),
389-
testDnsConfigEntry: DNSConfigEntry{},
390-
testIngressConfigEntry: getTestIngressConfigEntry(BridgeNetworkMode, "", false, testBridgeDefaultListenerPort, aws.Uint16(0), aws.Uint16(0)),
391-
},
392352
{
393353
testName: "Bridge override case with no the invalid address in dns config when IPv6 is enabled",
394354
testNetworkMode: BridgeNetworkMode,

agent/api/task/task_attachment_handler_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func TestHandleTaskAttachmentsWithServiceConnectAttachment(t *testing.T) {
115115
testName: "AWSVPC IPv6 enabled with error",
116116
testServiceConnectConfig: constructTestServiceConnectConfig(
117117
testIngressPort,
118-
testInboundListener,
118+
"",
119119
testOutboundListener,
120120
"",
121121
testIPv6CIDR,

0 commit comments

Comments
 (0)