Test for validating proxy can't be used as an open relay (#7884)

* Test for validating proxy can't be used as open relay

Closes #7697

We setup two injected bb servers and an uninjected curl client. One of
the servers is injected in ingress mode. The curl client tries to
connect to it to the port 4140 while setting the `l5d-dst-override` header
to attempt a relay to the other server.

The first test shows this doesn't result in a relay. The second test
does the same, but changing LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR in the
injected server proxy, making it listen on all addresses. In that case,
the relay succeeds.
This commit is contained in:
Alejandro Pedraza 2022-02-21 11:07:43 -05:00 committed by GitHub
parent e02ecb4ae6
commit 6d916dd80f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 237 additions and 0 deletions

View File

@ -0,0 +1,148 @@
package norelay
import (
"context"
"os"
"strings"
"testing"
"github.com/linkerd/linkerd2/testutil"
)
var TestHelper *testutil.TestHelper
func TestMain(m *testing.M) {
TestHelper = testutil.NewTestHelper()
// Block test execution until control plane is running
TestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)
os.Exit(m.Run())
}
// TestNoRelay verifies that hitting the proxy's outbound port doesn't result
// in an open relay, by trying to leverage the l5d-dst-override header in an
// ingress proxy.
func TestNoRelay(t *testing.T) {
ctx := context.Background()
deployments := getDeployments(t)
TestHelper.WithDataPlaneNamespace(ctx, "norelay-test", map[string]string{}, t, func(t *testing.T, ns string) {
for name, res := range deployments {
out, err := TestHelper.KubectlApply(res, ns)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error",
"unexpected error with deployment %s: %v output:\n%s",
name, err, out,
)
}
}
for name := range deployments {
err := TestHelper.CheckPods(ctx, ns, name, 1)
if err != nil {
//nolint:errorlint
if rce, ok := err.(*testutil.RestartCountError); ok {
testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
} else {
testutil.AnnotatedError(t, "CheckPods timed-out", err)
}
}
}
ip, err := TestHelper.Kubectl(
"", "-n", ns, "get", "po", "-l", "app=server-relay",
"-o", "jsonpath='{range .items[*]}{@.status.podIP}{end}'",
)
if err != nil {
testutil.AnnotatedFatalf(t, "failed to retrieve server-relay IP",
"failed to retrieve server-relay IP: %s\n%s", err, ip)
}
relayIP := strings.Trim(ip, "'")
o, err := TestHelper.Kubectl(
"", "-n", ns, "exec", "deploy/client",
"--", "curl", "-f", "-H", "l5d-dst-override: server-hello."+ns+".svc.cluster.local:8080", "http://"+relayIP+":4140",
)
if err == nil || err.Error() != "exit status 22" {
testutil.AnnotatedFatalf(t, "no error or unexpected error returned",
"no error or unexpected error returned: %s\n%s", o, err)
}
})
}
// TestRelay validates the previous test by running the same scenario but
// forcing an open relay by changing the value of
// LINKERD2_PROXY_OUTBOUND_LISTEN_ADDR from 127.0.0.1:4140 to 0.0.0.0:4140,
// which is not possible without manually changing the injected proxy yaml
func TestRelay(t *testing.T) {
ctx := context.Background()
deployments := getDeployments(t)
deployments["server-relay"] = strings.ReplaceAll(deployments["server-relay"], "127.0.0.1:4140", "0.0.0.0:4140")
TestHelper.WithDataPlaneNamespace(ctx, "relay-test", map[string]string{}, t, func(t *testing.T, ns string) {
for name, res := range deployments {
out, err := TestHelper.KubectlApply(res, ns)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error",
"unexpected error with deployment %s: %v output:\n%s",
name, err, out,
)
}
}
for name := range deployments {
err := TestHelper.CheckPods(ctx, ns, name, 1)
if err != nil {
//nolint:errorlint
if rce, ok := err.(*testutil.RestartCountError); ok {
testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
} else {
testutil.AnnotatedError(t, "CheckPods timed-out", err)
}
}
}
ip, err := TestHelper.Kubectl(
"", "-n", ns, "get", "po", "-l", "app=server-relay",
"-o", "jsonpath='{range .items[*]}{@.status.podIP}{end}'",
)
if err != nil {
testutil.AnnotatedFatalf(t, "failed to retrieve server-relay IP",
"failed to retrieve server-relay IP: %s\n%s", err, ip)
}
relayIP := strings.Trim(ip, "'")
o, err := TestHelper.Kubectl(
"", "-n", ns, "exec", "deploy/client",
"--", "curl", "-f", "-H", "l5d-dst-override: server-hello."+ns+".svc.cluster.local:8080", "http://"+relayIP+":4140",
)
if err != nil {
testutil.AnnotatedFatalf(t, "unexpected error returned",
"unexpected error returned: %s\n%s", o, err)
}
if !strings.Contains(o, "HELLO-FROM-SERVER") {
testutil.AnnotatedFatalf(t, "unexpected response returned",
"unexpected response returned: %s", o)
}
})
}
func getDeployments(t *testing.T) map[string]string {
deploys := make(map[string]string)
var err error
// server-hello is injected normally
deploys["server-hello"], err = TestHelper.LinkerdRun("inject", "testdata/server-hello.yml")
if err != nil {
testutil.AnnotatedFatal(t, "unexpected error", err)
}
// server-relay is injected in ingress mode, manually
deploys["server-relay"], err = TestHelper.LinkerdRun("inject", "--manual", "--ingress", "testdata/server-relay.yml")
if err != nil {
testutil.AnnotatedFatal(t, "unexpected error", err)
}
// client is not injected
deploys["client"], err = testutil.ReadFile("testdata/client.yml")
if err != nil {
testutil.AnnotatedFatalf(t, "failed to read 'client.yml'", "failed to read 'client.yml': %s", err)
}
return deploys
}

View File

@ -0,0 +1,18 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
spec:
selector:
matchLabels:
app: client
template:
metadata:
labels:
app: client
spec:
containers:
- name: curl
image: curlimages/curl
command: [ "sh", "-c", "--" ]
args: [ "while true; do sleep 10; done;" ]

View File

@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: server-hello
spec:
selector:
matchLabels:
app: server-hello
template:
metadata:
labels:
app: server-hello
spec:
containers:
- name: app
image: buoyantio/bb:v0.0.6
args:
- terminus
- "--h1-server-port=8080"
- "--response-text=HELLO-FROM-SERVER"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: server-hello
spec:
selector:
app: server-hello
ports:
- name: http
port: 8080
targetPort: 8080

View File

@ -0,0 +1,37 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: server-relay
labels:
app: server-relay
spec:
selector:
matchLabels:
app: server-relay
template:
metadata:
labels:
app: server-relay
spec:
containers:
- name: app
image: buoyantio/bb:v0.0.6
args:
- terminus
- "--h1-server-port=8080"
- "--response-text=HELLO-FROM-RELAY"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: server-relay
spec:
selector:
app: server-relay
ports:
- name: http
port: 8080
targetPort: 8080