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
17 changes: 17 additions & 0 deletions internal/e2e/atenet_dataplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ type AtenetDataplane interface {
IsRetryableParkingBudgetExhaustion(status int, body string) bool
ParkingBudgetStatus() int
IsEgressPolicyDenied(status int, body string) bool
IsWebSocketEgressDenied(status int, body string) bool
PlatformMetricPrefixes([]string) []string
RouteDurationSeen(context.Context, string) (bool, error)
ScrapeMetrics(context.Context) (string, error)
EgressConnectStats(before, after string) EgressConnectStats
}

// webSocketEgressDeniedBody is the body both dataplanes are configured to
// answer a WebSocket upgrade from an actor with.
const webSocketEgressDeniedBody = "WebSocket egress is not supported"

// EgressConnectStats holds the delta of egress CONNECT connections and requests
// observed between two metric scrapes. A value of -1 indicates that the
// dataplane does not report the counter.
Expand Down Expand Up @@ -85,6 +90,10 @@ func isEgressPolicyDenied(status int, body string) bool {
(status == http.StatusBadGateway && strings.Contains(body, "request failed"))
}

func (envoyAtenetDataplane) IsWebSocketEgressDenied(status int, body string) bool {
return status == http.StatusForbidden && body == webSocketEgressDeniedBody
}

func (envoyAtenetDataplane) PlatformMetricPrefixes(prefixes []string) []string { return prefixes }

func (envoyAtenetDataplane) RouteDurationSeen(_ context.Context, collectorScrape string) (bool, error) {
Expand Down Expand Up @@ -149,6 +158,14 @@ func (agentGatewayAtenetDataplane) IsEgressPolicyDenied(status int, body string)
return isEgressPolicyDenied(status, body)
}

// IsWebSocketEgressDenied also accepts agentgateway's own denial: it applies
// its request policies before the route's direct response, so an upgrade
// without a substrate egress policy is refused with this body instead.
func (agentGatewayAtenetDataplane) IsWebSocketEgressDenied(status int, body string) bool {
return status == http.StatusForbidden &&
(body == webSocketEgressDeniedBody || strings.Contains(body, "missing substrate egress request policy"))
}

func (agentGatewayAtenetDataplane) PlatformMetricPrefixes(prefixes []string) []string {
filtered := make([]string, 0, len(prefixes))
for _, prefix := range prefixes {
Expand Down
29 changes: 29 additions & 0 deletions internal/e2e/atenet_dataplane_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package e2e

import (
"fmt"
"net/http"
"testing"
)
Expand All @@ -37,6 +38,34 @@ func TestAtenetDataplaneEgressPolicyDenial(t *testing.T) {
})
}

func TestAtenetDataplaneWebSocketEgressDenial(t *testing.T) {
const (
standard = "WebSocket egress is not supported"
agw = "missing substrate egress request policy"
)
for _, tt := range []struct {
dataplane string
status int
body string
want bool
}{
{"", http.StatusForbidden, standard, true},
{"", http.StatusForbidden, agw, false},
{"", http.StatusOK, standard, false},
{"agentgateway", http.StatusForbidden, standard, true},
{"agentgateway", http.StatusForbidden, agw, true},
{"agentgateway", http.StatusForbidden, "other", false},
{"agentgateway", http.StatusBadGateway, agw, false},
} {
t.Run(fmt.Sprintf("%q/%d/%s", tt.dataplane, tt.status, tt.body), func(t *testing.T) {
t.Setenv(AtenetDataplaneEnv, tt.dataplane)
if got := CurrentAtenetDataplane().IsWebSocketEgressDenied(tt.status, tt.body); got != tt.want {
t.Errorf("IsWebSocketEgressDenied(%d, %q) = %v, want %v", tt.status, tt.body, got, tt.want)
}
})
}
}

func TestAtenetDataplaneParkingBudget(t *testing.T) {
for _, dataplane := range []string{"envoy", "agentgateway"} {
t.Run(dataplane, func(t *testing.T) {
Expand Down
31 changes: 12 additions & 19 deletions internal/e2e/suites/networking/websocketegress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,7 @@ func TestActorEgressWebSocket(t *testing.T) {
if err := json.Unmarshal(raw, &got); err != nil {
t.Fatalf("decoding WebSocket observation: %v", err)
}
if got.StatusCode != http.StatusForbidden {
t.Errorf("WebSocket handshake status = %d, want %d", got.StatusCode, http.StatusForbidden)
}
if got.HandshakeBody != "WebSocket egress is not supported" {
t.Errorf("WebSocket handshake body = %q, want %q", got.HandshakeBody, "WebSocket egress is not supported")
}
if got.Error == "" {
t.Error("WebSocket handshake error is empty")
}
if len(got.Messages) != 0 {
t.Errorf("WebSocket messages = %+v, want no echoes", got.Messages)
}
requireEgressWebSocketDenied(t, "WebSocket", got)
}

func TestActorEgressSecureWebSocket(t *testing.T) {
Expand All @@ -80,16 +69,20 @@ func TestActorEgressSecureWebSocket(t *testing.T) {
if err := json.Unmarshal(raw, &got); err != nil {
t.Fatalf("decoding secure WebSocket observation: %v", err)
}
if got.StatusCode != http.StatusForbidden {
t.Errorf("secure WebSocket handshake status = %d, want %d", got.StatusCode, http.StatusForbidden)
}
if got.HandshakeBody != "WebSocket egress is not supported" {
t.Errorf("secure WebSocket handshake body = %q, want %q", got.HandshakeBody, "WebSocket egress is not supported")
requireEgressWebSocketDenied(t, "secure WebSocket", got)
}

// requireEgressWebSocketDenied checks that the dataplane refused the handshake
// and that nothing was echoed.
func requireEgressWebSocketDenied(t *testing.T, name string, got egressWebSocketResponse) {
t.Helper()
if !e2e.CurrentAtenetDataplane().IsWebSocketEgressDenied(got.StatusCode, got.HandshakeBody) {
t.Errorf("%s handshake = %d %q, want a WebSocket egress denial", name, got.StatusCode, got.HandshakeBody)
}
if got.Error == "" {
t.Error("secure WebSocket handshake error is empty")
t.Errorf("%s handshake error is empty", name)
}
if len(got.Messages) != 0 {
t.Errorf("secure WebSocket messages = %+v, want no echoes", got.Messages)
t.Errorf("%s messages = %+v, want no echoes", name, got.Messages)
}
}
Loading