From 7f9ec7893218bf6868aba1aebd2caaf19b2d7b10 Mon Sep 17 00:00:00 2001 From: Andrew Stucki Date: Tue, 21 Feb 2023 14:12:19 -0500 Subject: [PATCH] [API Gateway] Validate listener name is not empty (#16340) * [API Gateway] Validate listener name is not empty * Update docstrings and test --- agent/structs/config_entry_gateways.go | 11 ++++-- agent/structs/config_entry_gateways_test.go | 35 +++++++++++++++++++ api/config_entry_gateways.go | 5 ++- .../case-api-gateway-tcp-conflicted/setup.sh | 1 + 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index c757dce984..885a301fc4 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -2,6 +2,7 @@ package structs import ( "fmt" + "regexp" "sort" "strings" @@ -778,9 +779,14 @@ func (e *APIGatewayConfigEntry) Validate() error { return e.validateListeners() } +var listenerNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) + func (e *APIGatewayConfigEntry) validateListenerNames() error { listeners := make(map[string]struct{}) for _, listener := range e.Listeners { + if len(listener.Name) < 1 || !listenerNameRegex.MatchString(listener.Name) { + return fmt.Errorf("listener name %q is invalid, must be at least 1 character and contain only letters, numbers, or dashes", listener.Name) + } if _, found := listeners[listener.Name]; found { return fmt.Errorf("found multiple listeners with the name %q", listener.Name) } @@ -861,9 +867,8 @@ const ( // APIGatewayListener represents an individual listener for an APIGateway type APIGatewayListener struct { - // Name is the optional name of the listener in a given gateway. This is - // optional but must be unique within a gateway; therefore, if a gateway - // has more than a single listener, all but one must specify a Name. + // Name is the name of the listener in a given gateway. This must be + // unique within a gateway. Name string // Hostname is the host name that a listener should be bound to. If // unspecified, the listener accepts requests for all hostnames. diff --git a/agent/structs/config_entry_gateways_test.go b/agent/structs/config_entry_gateways_test.go index dd1f624eca..ca68ea4f40 100644 --- a/agent/structs/config_entry_gateways_test.go +++ b/agent/structs/config_entry_gateways_test.go @@ -1143,12 +1143,40 @@ func TestAPIGateway_Listeners(t *testing.T) { }, validateErr: "multiple listeners with the name", }, + "empty listener name": { + entry: &APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "api-gw-one", + Listeners: []APIGatewayListener{ + { + Port: 80, + Protocol: "tcp", + }, + }, + }, + validateErr: "listener name \"\" is invalid, must be at least 1 character and contain only letters, numbers, or dashes", + }, + "invalid listener name": { + entry: &APIGatewayConfigEntry{ + Kind: "api-gateway", + Name: "api-gw-one", + Listeners: []APIGatewayListener{ + { + Port: 80, + Protocol: "tcp", + Name: "/", + }, + }, + }, + validateErr: "listener name \"/\" is invalid, must be at least 1 character and contain only letters, numbers, or dashes", + }, "merged listener protocol conflict": { entry: &APIGatewayConfigEntry{ Kind: "api-gateway", Name: "api-gw-two", Listeners: []APIGatewayListener{ { + Name: "listener-one", Port: 80, Protocol: ListenerProtocolHTTP, }, @@ -1167,6 +1195,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-three", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", }, @@ -1185,6 +1214,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-four", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("UDP"), @@ -1199,6 +1229,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-five", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("tcp"), @@ -1213,6 +1244,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-six", Listeners: []APIGatewayListener{ { + Name: "listener", Port: -1, Protocol: APIGatewayListenerProtocol("tcp"), }, @@ -1226,6 +1258,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-seven", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "*.*.host.one", Protocol: APIGatewayListenerProtocol("http"), @@ -1240,6 +1273,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-eight", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("http"), @@ -1259,6 +1293,7 @@ func TestAPIGateway_Listeners(t *testing.T) { Name: "api-gw-nine", Listeners: []APIGatewayListener{ { + Name: "listener", Port: 80, Hostname: "host.one", Protocol: APIGatewayListenerProtocol("http"), diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 209c7ae0df..05e43832c1 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -268,9 +268,8 @@ func (g *APIGatewayConfigEntry) GetModifyIndex() uint64 { return g.ModifyInd // APIGatewayListener represents an individual listener for an APIGateway type APIGatewayListener struct { - // Name is the optional name of the listener in a given gateway. This is - // optional, however, it must be unique. Therefore, if a gateway has more - // than a single listener, all but one must specify a Name. + // Name is the name of the listener in a given gateway. This must be + // unique within a gateway. Name string // Hostname is the host name that a listener should be bound to, if // unspecified, the listener accepts requests for all hostnames. diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh index bb9baacbb0..76b656ac0d 100644 --- a/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh +++ b/test/integration/connect/envoy/case-api-gateway-tcp-conflicted/setup.sh @@ -9,6 +9,7 @@ listeners = [ { port = 9999 protocol = "tcp" + name = "listener" } ] '