open-consul/agent/structs/config_entry_routes_test.go
John Maguire 3d11e9b26a
APIGW: Routes with duplicate parents should be invalid (#16926)
* ensure route parents are unique when creating an http route

* Ensure tcp route parents are unique

* Added unit tests
2023-04-10 13:20:32 -04:00

440 lines
10 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package structs
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
)
func TestTCPRoute(t *testing.T) {
t.Parallel()
cases := map[string]configEntryTestcase{
"multiple services": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-one",
Services: []TCPService{{
Name: "foo",
}, {
Name: "bar",
}},
},
validateErr: "tcp-route currently only supports one service",
},
"normalize parent kind": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Services: []TCPService{{
Name: "foo",
}},
},
normalizeOnly: true,
check: func(t *testing.T, entry ConfigEntry) {
expectedParent := ResourceReference{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
}
route := entry.(*TCPRouteConfigEntry)
require.Len(t, route.Parents, 1)
require.Equal(t, expectedParent, route.Parents[0])
},
},
"invalid parent kind": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Kind: "route",
Name: "gateway",
}},
},
validateErr: "unsupported parent kind",
},
"duplicate parents with no listener specified": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
},
},
},
validateErr: "route parents must be unique",
},
"duplicate parents with listener specified": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
validateErr: "route parents must be unique",
},
"almost duplicate parents with one not specifying a listener": {
entry: &TCPRouteConfigEntry{
Kind: TCPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
check: func(t *testing.T, entry ConfigEntry) {
expectedParents := []ResourceReference{
{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
{
Kind: APIGateway,
Name: "gateway",
SectionName: "same",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
}
route := entry.(*TCPRouteConfigEntry)
require.Len(t, route.Parents, 2)
require.Equal(t, expectedParents[0], route.Parents[0])
require.Equal(t, expectedParents[1], route.Parents[1])
},
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}
func TestHTTPRoute(t *testing.T) {
t.Parallel()
cases := map[string]configEntryTestcase{
"normalize parent kind": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-one",
Parents: []ResourceReference{{
Name: "gateway",
}},
},
normalizeOnly: true,
check: func(t *testing.T, entry ConfigEntry) {
expectedParent := ResourceReference{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
}
route := entry.(*HTTPRouteConfigEntry)
require.Len(t, route.Parents, 1)
require.Equal(t, expectedParent, route.Parents[0])
},
},
"invalid parent kind": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Kind: "route",
Name: "gateway",
}},
},
validateErr: "unsupported parent kind",
},
"wildcard hostnames": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Hostnames: []string{"*"},
},
validateErr: "host \"*\" must not be a wildcard",
},
"wildcard subdomain": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Hostnames: []string{"*.consul.example"},
},
validateErr: "host \"*.consul.example\" must not be a wildcard",
},
"valid dns hostname": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Hostnames: []string{"...not legal"},
},
validateErr: "host \"...not legal\" must be a valid DNS hostname",
},
"rule matches invalid header match type": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Headers: []HTTPHeaderMatch{{
Match: HTTPHeaderMatchType("foo"),
Name: "foo",
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Headers[0], match type should be one of present, exact, prefix, suffix, or regex",
},
"rule matches invalid header match name": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Headers: []HTTPHeaderMatch{{
Match: HTTPHeaderMatchPresent,
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Headers[0], missing required Name field",
},
"rule matches invalid query match type": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Query: []HTTPQueryMatch{{
Match: HTTPQueryMatchType("foo"),
Name: "foo",
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Query[0], match type should be one of present, exact, or regex",
},
"rule matches invalid query match name": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Query: []HTTPQueryMatch{{
Match: HTTPQueryMatchPresent,
}},
}},
}},
},
validateErr: "Rule[0], Match[0], Query[0], missing required Name field",
},
"rule matches invalid path match type": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Path: HTTPPathMatch{
Match: HTTPPathMatchType("foo"),
},
}},
}},
},
validateErr: "Rule[0], Match[0], Path, match type should be one of exact, prefix, or regex",
},
"rule matches invalid path match prefix": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Path: HTTPPathMatch{
Match: HTTPPathMatchPrefix,
},
}},
}},
},
validateErr: "Rule[0], Match[0], Path, prefix type match doesn't start with '/': \"\"",
},
"rule matches invalid method": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Method: HTTPMatchMethod("foo"),
}},
}},
},
validateErr: "Rule[0], Match[0], Method contains an invalid method \"FOO\"",
},
"rule normalizes method casing and path matches": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{{
Name: "gateway",
}},
Rules: []HTTPRouteRule{{
Matches: []HTTPMatch{{
Method: HTTPMatchMethod("trace"),
}},
}},
},
},
"rule normalizes service weight": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-one",
Rules: []HTTPRouteRule{{
Services: []HTTPService{
{
Name: "test",
Weight: 0,
},
{
Name: "test2",
Weight: -1,
},
},
}},
},
check: func(t *testing.T, entry ConfigEntry) {
route := entry.(*HTTPRouteConfigEntry)
require.Equal(t, 1, route.Rules[0].Services[0].Weight)
require.Equal(t, 1, route.Rules[0].Services[1].Weight)
},
},
"duplicate parents with no listener specified": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
},
},
},
validateErr: "route parents must be unique",
},
"duplicate parents with listener specified": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
validateErr: "route parents must be unique",
},
"almost duplicate parents with one not specifying a listener": {
entry: &HTTPRouteConfigEntry{
Kind: HTTPRoute,
Name: "route-two",
Parents: []ResourceReference{
{
Kind: "api-gateway",
Name: "gateway",
},
{
Kind: "api-gateway",
Name: "gateway",
SectionName: "same",
},
},
},
check: func(t *testing.T, entry ConfigEntry) {
expectedParents := []ResourceReference{
{
Kind: APIGateway,
Name: "gateway",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
{
Kind: APIGateway,
Name: "gateway",
SectionName: "same",
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
}
route := entry.(*HTTPRouteConfigEntry)
require.Len(t, route.Parents, 2)
require.Equal(t, expectedParents[0], route.Parents[0])
require.Equal(t, expectedParents[1], route.Parents[1])
},
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}