Skip to content

Commit

Permalink
connectivity: Make pod to world test configurable
Browse files Browse the repository at this point in the history
A configurable domain allows us to run the PodToWord test in
an environment where external traffic only allow for specific domains.
This also gives us the ability to swap the domain in test environments
to better handle flaky tests.

Signed-off-by: Birol Bilgin <[email protected]>
  • Loading branch information
brlbil authored and tklauser committed Jan 10, 2023
1 parent 7559660 commit 60ae1d5
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 30 deletions.
1 change: 1 addition & 0 deletions connectivity/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Parameters struct {
DNSTestServerImage string
Datapath bool
AgentPodSelector string
ExternalTarget string

K8sVersion string
HelmChartDirectory string
Expand Down
8 changes: 4 additions & 4 deletions connectivity/manifests/client-egress-l7-http-named-port.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
# client2 is allowed to contact one.one.one.one and the echo Pod
# client2 is allowed to contact {{.ExternalTarget}} and the echo Pod
# on port http-8080. HTTP introspection is enabled for client2.
# The toFQDNs section relies on DNS introspection being performed by
# the client-egress-only-dns policy.
Expand All @@ -8,7 +8,7 @@ kind: CiliumNetworkPolicy
metadata:
name: client-egress-l7-http-named-port
spec:
description: "Allow GET one.one.one.one:80/ and GET <echo>:<http-80>/ from client2"
description: "Allow GET {{.ExternalTarget}}:80/ and GET <echo>:<http-80>/ from client2"
endpointSelector:
matchLabels:
other: client
Expand All @@ -25,9 +25,9 @@ spec:
http:
- method: "GET"
path: "/"
# Allow GET / requests, only towards one.one.one.one.
# Allow GET / requests, only towards {{.ExternalTarget}}.
- toFQDNs:
- matchName: "one.one.one.one"
- matchName: "{{.ExternalTarget}}"
toPorts:
- ports:
- port: "80"
Expand Down
8 changes: 4 additions & 4 deletions connectivity/manifests/client-egress-l7-http.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
# client2 is allowed to contact one.one.one.one/ on port 80 and the echo Pod
# client2 is allowed to contact {{.ExternalTarget}}/ on port 80 and the echo Pod
# on port 8080. HTTP introspection is enabled for client2.
# The toFQDNs section relies on DNS introspection being performed by
# the client-egress-only-dns policy.
Expand All @@ -8,7 +8,7 @@ kind: CiliumNetworkPolicy
metadata:
name: client-egress-l7-http
spec:
description: "Allow GET one.one.one.one:80/ and GET <echo>:8080/ from client2"
description: "Allow GET {{.ExternalTarget}}:80/ and GET <echo>:8080/ from client2"
endpointSelector:
matchLabels:
other: client
Expand All @@ -25,9 +25,9 @@ spec:
http:
- method: "GET"
path: "/"
# Allow GET / requests, only towards one.one.one.one.
# Allow GET / requests, only towards {{.ExternalTarget}}.
- toFQDNs:
- matchName: "one.one.one.one"
- matchName: "{{.ExternalTarget}}"
toPorts:
- ports:
- port: "80"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: client-egress-to-fqdns-one-one-one-one
name: client-egress-to-fqdns-{{.ExternalTarget}}
spec:
endpointSelector:
matchLabels:
Expand All @@ -16,7 +16,7 @@ spec:
- method: "GET"
path: "/"
toFQDNs:
- matchName: "one.one.one.one"
- matchName: "{{.ExternalTarget}}"
- toPorts:
- ports:
- port: "53"
Expand Down
42 changes: 29 additions & 13 deletions connectivity/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package connectivity
import (
"context"
_ "embed"
"fmt"

"github.com/blang/semver/v4"

Expand Down Expand Up @@ -121,6 +122,21 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error {
return err
}

renderedTemplates := map[string]string{}

// render templates, if any problems fail early
for key, temp := range map[string]string{
"clientEgressL7HTTPPolicyYAML": clientEgressL7HTTPPolicyYAML,
"clientEgressL7HTTPNamedPortPolicyYAML": clientEgressL7HTTPNamedPortPolicyYAML,
"clientEgressToFQDNsCiliumIOPolicyYAML": clientEgressToFQDNsCiliumIOPolicyYAML,
} {
val, err := utils.RenderTemplate(temp, ct.Params())
if err != nil {
return err
}
renderedTemplates[key] = val
}

var v semver.Version
if assumeCiliumVersion := ct.Params().AssumeCiliumVersion; assumeCiliumVersion != "" {
ct.Warnf("Assuming Cilium version %s for connectivity tests", assumeCiliumVersion)
Expand Down Expand Up @@ -594,16 +610,16 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error {
// Test L7 HTTP introspection using an egress policy on the clients.
ct.NewTest("client-egress-l7").
WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureL7Proxy)).
WithPolicy(clientEgressOnlyDNSPolicyYAML). // DNS resolution only
WithPolicy(clientEgressL7HTTPPolicyYAML). // L7 allow policy with HTTP introspection
WithPolicy(clientEgressOnlyDNSPolicyYAML). // DNS resolution only
WithPolicy(renderedTemplates["clientEgressL7HTTPPolicyYAML"]). // L7 allow policy with HTTP introspection
WithScenarios(
tests.PodToPod(),
tests.PodToWorld(),
).
WithExpectations(func(a *check.Action) (egress, ingress check.Result) {
if a.Source().HasLabel("other", "client") && // Only client2 is allowed to make HTTP calls.
// Outbound HTTP to one.one.one.one is L7-introspected and allowed.
(a.Destination().Port() == 80 && a.Destination().Address() == "one.one.one.one" ||
// Outbound HTTP to set domain-name defaults to one.one.one.one is L7-introspected and allowed.
(a.Destination().Port() == 80 && a.Destination().Address() == ct.Params().ExternalTarget ||
a.Destination().Port() == 8080) { // 8080 is traffic to echo Pod.
if a.Destination().Path() == "/" || a.Destination().Path() == "" {
egress = check.ResultOK
Expand All @@ -622,16 +638,16 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error {
// Test L7 HTTP named port introspection using an egress policy on the clients.
ct.NewTest("client-egress-l7-named-port").
WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureL7Proxy)).
WithPolicy(clientEgressOnlyDNSPolicyYAML). // DNS resolution only
WithPolicy(clientEgressL7HTTPNamedPortPolicyYAML). // L7 allow policy with HTTP introspection (named port)
WithPolicy(clientEgressOnlyDNSPolicyYAML). // DNS resolution only
WithPolicy(renderedTemplates["clientEgressL7HTTPNamedPortPolicyYAML"]). // L7 allow policy with HTTP introspection (named port)
WithScenarios(
tests.PodToPod(),
tests.PodToWorld(),
).
WithExpectations(func(a *check.Action) (egress, ingress check.Result) {
if a.Source().HasLabel("other", "client") && // Only client2 is allowed to make HTTP calls.
// Outbound HTTP to one.one.one.one is L7-introspected and allowed.
(a.Destination().Port() == 80 && a.Destination().Address() == "one.one.one.one" ||
// Outbound HTTP to domain-name, default one.one.one.one, is L7-introspected and allowed.
(a.Destination().Port() == 80 && a.Destination().Address() == ct.Params().ExternalTarget ||
a.Destination().Port() == 8080) { // named port http-8080 is traffic to echo Pod.
if a.Destination().Path() == "/" || a.Destination().Path() == "" {
egress = check.ResultOK
Expand All @@ -652,14 +668,14 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error {
WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureL7Proxy)).
WithScenarios(
tests.PodToPod(), // connects to other Pods directly, no DNS
tests.PodToWorld(), // resolves one.one.one.one
tests.PodToWorld(), // resolves set domain-name defaults to one.one.one.one
).
WithExpectations(func(a *check.Action) (egress check.Result, ingress check.Result) {
return check.ResultDropCurlTimeout, check.ResultNone
})

// This policy only allows port 80 to "one.one.one.one". DNS proxy enabled.
ct.NewTest("to-fqdns").WithPolicy(clientEgressToFQDNsCiliumIOPolicyYAML).
// This policy only allows port 80 to domain-name, default one.one.one.one,. DNS proxy enabled.
ct.NewTest("to-fqdns").WithPolicy(renderedTemplates["clientEgressToFQDNsCiliumIOPolicyYAML"]).
WithFeatureRequirements(check.RequireFeatureEnabled(check.FeatureL7Proxy)).
WithScenarios(
tests.PodToWorld(),
Expand All @@ -680,12 +696,12 @@ func Run(ctx context.Context, ct *check.ConnectivityTest) error {
return check.ResultDNSOKDropCurlHTTPError, check.ResultNone
}

if a.Destination().Port() == 80 && a.Destination().Address() == "one.one.one.one" {
if a.Destination().Port() == 80 && a.Destination().Address() == ct.Params().ExternalTarget {
if a.Destination().Path() == "/" || a.Destination().Path() == "" {
egress = check.ResultDNSOK
egress.HTTP = check.HTTP{
Method: "GET",
URL: "http://one.one.one.one/",
URL: fmt.Sprintf("http://%s/", ct.Params().ExternalTarget),
}
return egress, check.ResultNone
}
Expand Down
16 changes: 9 additions & 7 deletions connectivity/tests/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ func PodToWorld() check.Scenario {
}

// podToWorld implements a Scenario.
type podToWorld struct{}
type podToWorld struct {
}

func (s *podToWorld) Name() string {
return "pod-to-world"
}

func (s *podToWorld) Run(ctx context.Context, t *check.Test) {
http := check.HTTPEndpoint("one-one-one-one-http", "http://one.one.one.one")
https := check.HTTPEndpoint("one-one-one-one-https", "https://one.one.one.one")
httpsindex := check.HTTPEndpoint("one-one-one-one-https-index", "https://one.one.one.one/index.html")
extTarget := t.Context().Params().ExternalTarget
http := check.HTTPEndpoint(extTarget+"-http", "http://"+extTarget)
https := check.HTTPEndpoint(extTarget+"-https", "https://"+extTarget)
httpsindex := check.HTTPEndpoint(extTarget+"-https-index", fmt.Sprintf("https://%s/index.html", extTarget))

fp := check.FlowParameters{
DNSRequired: true,
Expand All @@ -40,19 +42,19 @@ func (s *podToWorld) Run(ctx context.Context, t *check.Test) {
client := client // copy to avoid memory aliasing when using reference

// With http, over port 80.
t.NewAction(s, fmt.Sprintf("http-to-one-one-one-one-%d", i), &client, http).Run(func(a *check.Action) {
t.NewAction(s, fmt.Sprintf("http-to-%s-%d", extTarget, i), &client, http).Run(func(a *check.Action) {
a.ExecInPod(ctx, ct.CurlCommand(http))
a.ValidateFlows(ctx, client, a.GetEgressRequirements(fp))
})

// With https, over port 443.
t.NewAction(s, fmt.Sprintf("https-to-one-one-one-one-%d", i), &client, https).Run(func(a *check.Action) {
t.NewAction(s, fmt.Sprintf("https-to-%s-%d", extTarget, i), &client, https).Run(func(a *check.Action) {
a.ExecInPod(ctx, ct.CurlCommand(https))
a.ValidateFlows(ctx, client, a.GetEgressRequirements(fp))
})

// With https, over port 443, index.html.
t.NewAction(s, fmt.Sprintf("https-to-one-one-one-one-index-%d", i), &client, httpsindex).Run(func(a *check.Action) {
t.NewAction(s, fmt.Sprintf("https-to-%s-index-%d", extTarget, i), &client, httpsindex).Run(func(a *check.Action) {
a.ExecInPod(ctx, ct.CurlCommand(httpsindex))
a.ValidateFlows(ctx, client, a.GetEgressRequirements(fp))
})
Expand Down
1 change: 1 addition & 0 deletions internal/cli/cmd/connectivity.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func newCmdConnectivityTest() *cobra.Command {
cmd.Flags().BoolVarP(&params.Debug, "debug", "d", false, "Show debug messages")
cmd.Flags().BoolVarP(&params.Timestamp, "timestamp", "t", false, "Show timestamp in messages")
cmd.Flags().BoolVarP(&params.PauseOnFail, "pause-on-fail", "p", false, "Pause execution on test failure")
cmd.Flags().StringVar(&params.ExternalTarget, "external-target", "one.one.one.one", "Domain name to use as external target in connectivity tests")
cmd.Flags().BoolVar(&params.SkipIPCacheCheck, "skip-ip-cache-check", true, "Skip IPCache check")
cmd.Flags().MarkHidden("skip-ip-cache-check")
cmd.Flags().BoolVar(&params.Datapath, "datapath", false, "Run datapath conformance tests")
Expand Down
21 changes: 21 additions & 0 deletions internal/utils/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package utils

import (
"bytes"
"text/template"
)

// RenderTemplate executes temp with data and returns the result
func RenderTemplate(temp string, data any) (string, error) {
tm, err := template.New("template").Parse(temp)
if err != nil {
return "", err
}

buf := bytes.NewBuffer(nil)
if err := tm.Execute(buf, data); err != nil {
return "", err
}

return buf.String(), nil
}

2 comments on commit 60ae1d5

@danistrebel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tklauser I think this commit broke the sample at https://play.instruqt.com/embed/isovalent/tracks/cilium-service-mesh/challenges/l7-traffic-mgmt/assignment that directly applies the resource from the master

kubectl -n cilium-test apply -f https://raw.githubusercontent.com/cilium/cilium-cli/master/connectivity/manifests/client-egress-l7-http.yaml

Maybe there's already an issue tracked for this that I wasn't able to spot. In which case please ignore.

@tklauser
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the report @danistrebel. I wasn't aware the instruqt labs use these resources directly. I've created #1337 to track the issue.

Please sign in to comment.