Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ include_app_syslog_tcp
* `include_sso`: Flag to include the services tests that integrate with Single Sign On.
* `include_tasks`: Flag to include the v3 task tests. `include_v3` must also be set for tests to run. The CC API task_creation feature flag must be enabled for these tests to pass.
* `include_tcp_routing`: Flag to include the TCP Routing tests. These tests are equivalent to the [TCP Routing tests](https://github.com/cloudfoundry/routing-acceptance-tests/blob/master/tcp_routing/tcp_routing_test.go) from the Routing Acceptance Tests.
* `include_tcp_sni_routing`: Flag to include the SNI TCP Routing tests (TNZ-81099). These tests map multiple apps to the same external TCP port differentiated by SNI hostname (e.g. `cf map-route appB tcp.<domain> --port 18001 --hostname app-b`) and verify that a TLS client with the matching `ServerName` reaches the expected backend. Requires: (1) wildcard DNS so `*.<tcp_domain>` resolves to the TCP router load balancer; (2) `cf-tcp-router` deployed with frontend TLS termination enabled and a certificate that covers `*.<tcp_domain>` (self-signed is acceptable — the tests skip cert verification); (3) an org/space quota permitting at least two reserved route ports.
* `tcp_domain`: Domain that will be used for apps with TCP routes
* `include_user_provided_services`: Flag to include test for user-provided services.
* `include_v3`: Flag to include tests for the v3 API.
Expand Down
117 changes: 116 additions & 1 deletion cats_suite_helpers/cats_suite_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package cats_suite_helpers

import (
"bytes"
"crypto/tls"
"fmt"
"math/rand"
"net"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -202,6 +205,17 @@ func TCPRoutingDescribe(description string, callback func()) bool {
})
}

func TCPSNIRoutingDescribe(description string, callback func()) bool {
return Describe("[tcp sni routing]", func() {
BeforeEach(func() {
if !Config.GetIncludeTCPSNIRouting() {
Skip(skip_messages.SkipTCPSNIRoutingMessage)
}
})
Describe(description, callback)
})
}

func RoutingIsolationSegmentsDescribe(description string, callback func()) bool {
return Describe("[routing_isolation_segments]", func() {
BeforeEach(func() {
Expand Down Expand Up @@ -475,8 +489,22 @@ func GetNServerResponses(n int, domainName, externalPort1 string) ([]string, err
return responses, nil
}

func GetTCPPort() int {
start := Config.GetTCPPortRangeStart()
end := Config.GetTCPPortRangeEnd()
if start > 0 && end >= start {
return rand.Intn(end-start+1) + start
}
return 0
}

func MapTCPRoute(appName, domainName string) string {
createRouteSession := cf.Cf("map-route", appName, domainName).Wait()
port := GetTCPPort()
args := []string{"map-route", appName, domainName}
if port != 0 {
args = append(args, "--port", strconv.Itoa(port))
}
createRouteSession := cf.Cf(args...).Wait()
Expect(createRouteSession).To(Exit(0))

r := regexp.MustCompile(fmt.Sprintf(`.+%s:(\d+).+`, domainName))
Expand Down Expand Up @@ -529,3 +557,90 @@ func SendAndReceive(addr string, externalPort string) (string, error) {

return string(buff[:i]), nil
}

// MapTCPRouteWithHostname maps a TCP route on `domainName` with the given hostname.
// If port == 0, a new external port is allocated by the TCP router; the chosen port
// is parsed from the CLI output and returned.
// If port != 0, the supplied port is reused so callers can map multiple apps on the
// same external port, differentiated by SNI hostname.
func MapTCPRouteWithHostname(appName, domainName, hostname string, port int) int {
if port == 0 {
port = GetTCPPort()
}
args := []string{"map-route", appName, domainName, "--hostname", hostname}
if port != 0 {
args = append(args, "--port", strconv.Itoa(port))
}

session := cf.Cf(args...).Wait()
Expect(session).To(Exit(0))

if port != 0 {
return port
}

r := regexp.MustCompile(fmt.Sprintf(`.+%s:(\d+).+`, domainName))
matches := r.FindStringSubmatch(string(session.Out.Contents()))
Expect(matches).To(HaveLen(2), "expected map-route output to contain an external port")

chosen, err := strconv.Atoi(matches[1])
Expect(err).ToNot(HaveOccurred())

return chosen
}

// SendAndReceiveTLS opens a TLS connection to addr:port using the given SNI serverName,
// writes a small payload, and returns the server's response. insecureSkipVerify should
// typically be true in CATs since we only validate routing, not cert trust.
func SendAndReceiveTLS(addr string, port int, serverName string, insecureSkipVerify bool) (string, error) {
address := fmt.Sprintf("%s:%d", addr, port)

dialer := &net.Dialer{Timeout: 10 * time.Second}
conn, err := tls.DialWithDialer(dialer, "tcp", address, &tls.Config{
ServerName: serverName,
InsecureSkipVerify: insecureSkipVerify,
})
if err != nil {
return "", err
}
defer conn.Close()

if err := conn.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
return "", err
}

message := []byte(fmt.Sprintf("Time is %d", time.Now().Nanosecond()))
if _, err = conn.Write(message); err != nil {
return "", err
}

// mirror SendAndReceive: small pause to let the server respond before the read.
time.Sleep(100 * time.Millisecond)

buff := make([]byte, 1024)
n, err := conn.Read(buff)
if err != nil {
return "", err
}

i := n
if j := bytes.IndexByte(buff[:n], 0); j > 0 {
i = j
}

return string(buff[:i]), nil
}

// GetNTLSResponses performs n TLS SNI round-trips against addr:port using the given
// serverName and returns all responses in order.
func GetNTLSResponses(n int, addr string, port int, serverName string, insecureSkipVerify bool) ([]string, error) {
responses := make([]string, 0, n)
for i := 0; i < n; i++ {
resp, err := SendAndReceiveTLS(addr, port, serverName, insecureSkipVerify)
if err != nil {
return nil, err
}
responses = append(responses, resp)
}
return responses, nil
}
3 changes: 3 additions & 0 deletions helpers/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type CatsConfig interface {
GetIncludeTCPIsolationSegments() bool
GetIncludeHTTP2Routing() bool
GetIncludeTCPRouting() bool
GetIncludeTCPSNIRouting() bool
GetTCPPortRangeStart() int
GetTCPPortRangeEnd() int
GetIncludeWindows() bool
GetIncludeVolumeServices() bool
GetShouldKeepUser() bool
Expand Down
21 changes: 21 additions & 0 deletions helpers/config/config_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ type config struct {
IncludeTCPIsolationSegments *bool `json:"include_tcp_isolation_segments"`
IncludeHTTP2Routing *bool `json:"include_http2_routing"`
IncludeTCPRouting *bool `json:"include_tcp_routing"`
IncludeTCPSNIRouting *bool `json:"include_tcp_sni_routing"`
TCPPortRangeStart *int `json:"tcp_port_range_start"`
TCPPortRangeEnd *int `json:"tcp_port_range_end"`
IncludeTasks *bool `json:"include_tasks"`
IncludeV3 *bool `json:"include_v3"`
IncludeVolumeServices *bool `json:"include_volume_services"`
Expand Down Expand Up @@ -210,6 +213,9 @@ func getDefaults() config {
defaults.IncludeServiceCredentialBindingRotation = ptrToBool(false)
defaults.IncludeHTTP2Routing = ptrToBool(false)
defaults.IncludeTCPRouting = ptrToBool(false)
defaults.IncludeTCPSNIRouting = ptrToBool(false)
defaults.TCPPortRangeStart = ptrToInt(0)
defaults.TCPPortRangeEnd = ptrToInt(0)
defaults.IncludeVolumeServices = ptrToBool(false)
defaults.IncludeIPv6 = ptrToBool(false)

Expand Down Expand Up @@ -508,6 +514,9 @@ func validateConfig(config *config) error {
if config.IncludeTCPRouting == nil {
errs = errors.Join(errs, fmt.Errorf("* 'include_tcp_routing' must not be null"))
}
if config.IncludeTCPSNIRouting == nil {
errs = errors.Join(errs, fmt.Errorf("* 'include_tcp_sni_routing' must not be null"))
}
if config.IncludeV3 == nil {
errs = errors.Join(errs, fmt.Errorf("* 'include_v3' must not be null"))
}
Expand Down Expand Up @@ -1068,6 +1077,18 @@ func (c *config) GetIncludeTCPRouting() bool {
return *c.IncludeTCPRouting
}

func (c *config) GetIncludeTCPSNIRouting() bool {
return *c.IncludeTCPSNIRouting
}

func (c *config) GetTCPPortRangeStart() int {
return *c.TCPPortRangeStart
}

func (c *config) GetTCPPortRangeEnd() int {
return *c.TCPPortRangeEnd
}

func (c *config) GetIncludeV3() bool {
return *c.IncludeV3
}
Expand Down
6 changes: 6 additions & 0 deletions helpers/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type testConfig struct {
IncludeTCPIsolationSegments *bool `json:"include_tcp_isolation_segments,omitempty"`
IncludeHTTP2Routing *bool `json:"include_http2_routing,omitempty"`
IncludeTCPRouting *bool `json:"include_tcp_routing,omitempty"`
IncludeTCPSNIRouting *bool `json:"include_tcp_sni_routing,omitempty"`
IncludeTasks *bool `json:"include_tasks,omitempty"`
IncludeV3 *bool `json:"include_v3,omitempty"`
IncludeVolumeServices *bool `json:"include_volume_services,omitempty"`
Expand Down Expand Up @@ -175,6 +176,7 @@ type nullConfig struct {
IncludeRoutingIsolationSegments *bool `json:"include_routing_isolation_segments"`
IncludeHTTP2Routing *bool `json:"include_http2_routing"`
IncludeTCPRouting *bool `json:"include_tcp_routing"`
IncludeTCPSNIRouting *bool `json:"include_tcp_sni_routing"`
IncludeServiceDiscovery *bool `json:"include_service_discovery"`
IncludeVolumeServices *bool `json:"include_volume_services"`
IncludeTCPIsolationSegments *bool `json:"include_tcp_isolation_segments"`
Expand Down Expand Up @@ -307,6 +309,7 @@ var _ = Describe("Config", func() {
Expect(config.GetIncludeServiceInstanceSharing()).To(BeFalse())
Expect(config.GetIncludeHTTP2Routing()).To(BeFalse())
Expect(config.GetIncludeTCPRouting()).To(BeFalse())
Expect(config.GetIncludeTCPSNIRouting()).To(BeFalse())
Expect(config.GetIncludeVolumeServices()).To(BeFalse())

Expect(config.GetIncludeWindows()).To(BeFalse())
Expand Down Expand Up @@ -433,6 +436,7 @@ var _ = Describe("Config", func() {
Expect(err.Error()).To(ContainSubstring("'include_tasks' must not be null"))
Expect(err.Error()).To(ContainSubstring("'include_http2_routing' must not be null"))
Expect(err.Error()).To(ContainSubstring("'include_tcp_routing' must not be null"))
Expect(err.Error()).To(ContainSubstring("'include_tcp_sni_routing' must not be null"))
Expect(err.Error()).To(ContainSubstring("'include_v3' must not be null"))
Expect(err.Error()).To(ContainSubstring("'include_zipkin' must not be null"))
Expect(err.Error()).To(ContainSubstring("'include_isolation_segments' must not be null"))
Expand Down Expand Up @@ -496,6 +500,7 @@ var _ = Describe("Config", func() {
testCfg.IncludeTCPIsolationSegments = ptrToBool(true)
testCfg.IncludeHTTP2Routing = ptrToBool(true)
testCfg.IncludeTCPRouting = ptrToBool(true)
testCfg.IncludeTCPSNIRouting = ptrToBool(true)
testCfg.IncludeTasks = ptrToBool(true)
testCfg.IncludeV3 = ptrToBool(false)
testCfg.IncludeVolumeServices = ptrToBool(true)
Expand Down Expand Up @@ -561,6 +566,7 @@ var _ = Describe("Config", func() {
Expect(config.GetIncludeTCPIsolationSegments()).To(BeTrue())
Expect(config.GetIncludeHTTP2Routing()).To(BeTrue())
Expect(config.GetIncludeTCPRouting()).To(BeTrue())
Expect(config.GetIncludeTCPSNIRouting()).To(BeTrue())
Expect(config.GetIncludeTasks()).To(BeTrue())
Expect(config.GetIncludeV3()).To(BeFalse())
Expect(config.GetIncludeVolumeServices()).To(BeTrue())
Expand Down
2 changes: 2 additions & 0 deletions helpers/skip_messages/skip_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ NOTE: Ensure that route services are enabled on your platform before running thi
const SkipRoutingMessage = `Skipping this test because config.IncludeRouting is set to 'false'.`
const SkipHTTP2RoutingMessage = `Skipping this test because config.IncludeHTTP2Routing is set to 'false'.`
const SkipTCPRoutingMessage = `Skipping this test because config.IncludeTCPRouting is set to 'false'.`
const SkipTCPSNIRoutingMessage = `Skipping this test because config.IncludeTCPSNIRouting is set to 'false'.
NOTE: Ensure that TCP router frontend TLS termination is enabled and *.<tcp-domain> DNS resolves to the TCP router before enabling this test.`
const SkipSecurityGroupsMessage = `Skipping this test because config.IncludeSecurityGroups is set to 'false'.
NOTE: Ensure that your platform restricts internal network traffic by default in order to run this test.`
const SkipCommaDelimitedSecurityGroupsMessage = `Skipping this test because config.CommaDelimitedASGsEnabled is set to 'false'.
Expand Down
Loading