Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

connectivity: extend node to node encryption tests #1870

Merged
Merged
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
21 changes: 17 additions & 4 deletions connectivity/check/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ func parseBoolStatus(s string) bool {
return false
}

// extractFeaturesFromConfigMap extracts features from the Cilium ConfigMap.
// Note that there is no rule regarding if the default value is reflected
// in the ConfigMap or not.
func (ct *ConnectivityTest) extractFeaturesFromConfigMap(ctx context.Context, client *k8s.Client, result FeatureSet) error {
cm, err := client.GetConfigMap(ctx, ct.params.CiliumNamespace, defaults.ConfigMapName, metav1.GetOptions{})
if err != nil {
Expand All @@ -212,7 +215,7 @@ func (ct *ConnectivityTest) extractFeaturesFromConfigMap(ctx context.Context, cl
}

if versioncheck.MustCompile("<1.14.0")(ct.CiliumVersion) {
mode = "disabled"
mode = "vxlan"
3u13r marked this conversation as resolved.
Show resolved Hide resolved
if v, ok := cm.Data["tunnel"]; ok {
mode = v
}
Expand All @@ -221,13 +224,16 @@ func (ct *ConnectivityTest) extractFeaturesFromConfigMap(ctx context.Context, cl
Mode: mode,
}
} else {
mode = "native"
mode = "tunnel"
if v, ok := cm.Data["routing-mode"]; ok {
mode = v
}
tunnelProto := ""

tunnelProto := "vxlan"
if mode != "native" {
tunnelProto = cm.Data["tunnel-protocol"]
if v, ok := cm.Data["tunnel-protocol"]; ok {
tunnelProto = v
}
}

result[FeatureTunnel] = FeatureStatus{
Expand Down Expand Up @@ -262,6 +268,10 @@ func (ct *ConnectivityTest) extractFeaturesFromConfigMap(ctx context.Context, cl
return nil
}

// extractFeaturesFromRuntimeConfig extracts features from the Cilium runtime config.
// The downside of this approach is that the `DaemonConfig` struct is not stable.
// If there are changes to it in the future, we will likely have to maintain
// version-specific copies of the struct in the Cilium-CLI.
func (ct *ConnectivityTest) extractFeaturesFromRuntimeConfig(ctx context.Context, ciliumPod Pod, result FeatureSet) error {
namespace := ciliumPod.Pod.Namespace

Expand Down Expand Up @@ -518,6 +528,9 @@ func (ct *ConnectivityTest) detectFeatures(ctx context.Context) error {
for _, ciliumPod := range ct.ciliumPods {
features := FeatureSet{}

// If unsure from which source to retrieve the information from,
// prefer "CiliumStatus" over "ConfigMap" over "RuntimeConfig".
// See the corresponding functions for more information.
err := ct.extractFeaturesFromConfigMap(ctx, ciliumPod.K8sClient, features)
if err != nil {
return err
Expand Down
83 changes: 63 additions & 20 deletions connectivity/tests/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"sync"
"time"

"github.com/cilium/cilium/pkg/defaults"

"github.com/cilium/cilium-cli/connectivity/check"
)

Expand All @@ -23,26 +25,59 @@ const (
requestICMPEcho
)

// getInterNodeIface determines on which netdev iface to capture pkts. In the
// case of tunneling, we don't expect to see unencrypted pkts on a corresponding
// tunneling iface, so the choice is obvious. In the native routing mode, we run
// "ip route get $DST_IP" from the client pod's node.
// getInterNodeIface determines on which netdev iface to capture pkts.
3u13r marked this conversation as resolved.
Show resolved Hide resolved
// We run "ip route get $DST_IP" from the client pod's node to see to
// which interface the traffic is routed to. Additionally, we translate
// the interface name to the tunneling interface name, if the route goes
// through "cilium_host" and tunneling is enabled.
func getInterNodeIface(ctx context.Context, t *check.Test, clientHost *check.Pod, dstIP string) string {
tunnelFeat, ok := t.Context().Feature(check.FeatureTunnel)
if ok && tunnelFeat.Enabled {
cmd := []string{
"/bin/sh", "-c",
fmt.Sprintf("ip -o route get %s | grep -oE 'dev [^ ]*' | cut -d' ' -f2",
dstIP),
}
t.Debugf("Running %s", strings.Join(cmd, " "))
dev, err := clientHost.K8sClient.ExecInPod(ctx, clientHost.Pod.Namespace,
clientHost.Pod.Name, "", cmd)
if err != nil {
t.Fatalf("Failed to get IP route: %s", err)
}

device := strings.TrimRight(dev.String(), "\n\r")

// When tunneling is enabled, and the traffic is routed to the cilium IP space
// we want to capture on the tunnel interface.
if tunnelFeat, ok := t.Context().Feature(check.FeatureTunnel); ok &&
tunnelFeat.Enabled && device == defaults.HostDevice {
return "cilium_" + tunnelFeat.Mode // E.g. cilium_vxlan
}

cmd := []string{"/bin/sh", "-c",
fmt.Sprintf("ip -o r g %s | grep -oE 'dev [^ ]*' | cut -d' ' -f2",
dstIP)}
return device
}

// getSourceAddress determines the source IP address we want to use for
// capturing packet. If direct routing is used, the source IP is the client IP.
func getSourceAddress(ctx context.Context, t *check.Test, client,
clientHost *check.Pod, ipFam check.IPFamily, dstIP string,
) string {
if tunnelStatus, ok := t.Context().Feature(check.FeatureTunnel); ok &&
!tunnelStatus.Enabled {
return client.Address(ipFam)
}

cmd := []string{
"/bin/sh", "-c",
fmt.Sprintf("ip -o route get %s | grep -oE 'src [^ ]*' | cut -d' ' -f2",
dstIP),
}
t.Debugf("Running %s", strings.Join(cmd, " "))
dev, err := clientHost.K8sClient.ExecInPod(ctx, clientHost.Pod.Namespace,
srcIP, err := clientHost.K8sClient.ExecInPod(ctx, clientHost.Pod.Namespace,
clientHost.Pod.Name, "", cmd)
if err != nil {
t.Fatalf("Failed to get IP route: %s", err)
}
return strings.TrimRight(dev.String(), "\n\r")

return strings.TrimRight(srcIP.String(), "\n\r")
}

// PodToPodEncryption is a test case which checks the following:
Expand Down Expand Up @@ -85,11 +120,11 @@ func (s *podToPodEncryption) Run(ctx context.Context, t *check.Test) {
}

func testNoTrafficLeak(ctx context.Context, t *check.Test, s check.Scenario,
client, server, clientHost *check.Pod, reqType requestType, ipFam check.IPFamily) {

client, server, clientHost *check.Pod, reqType requestType, ipFam check.IPFamily,
) {
dstAddr := server.Address(ipFam)
iface := getInterNodeIface(ctx, t, clientHost, dstAddr)
t.Debugf("Detected %s iface for communication among client and server nodes", iface)
srcAddr := getSourceAddress(ctx, t, client, clientHost, ipFam, dstAddr)

bgStdout := &safeBuffer{}
bgStderr := &safeBuffer{}
Expand All @@ -113,14 +148,12 @@ func testNoTrafficLeak(ctx context.Context, t *check.Test, s check.Scenario,
// its captures.
cmd := []string{
"tcpdump", "-i", iface, "--immediate-mode", "-w", fmt.Sprintf("/tmp/%s.pcap", t.Name()),
// Capture pod egress traffic.
// Capture egress traffic.
// Unfortunately, we cannot use "host %s and host %s" filter here,
// as IPsec recirculates replies to the iface netdev, which would
// make tcpdump to capture the pkts (false positive).
fmt.Sprintf("src host %s and dst host %s and %s", client.Address(ipFam), dstAddr, protoFilter),
// Only one pkt is enough, as we don't expect any unencrypted pkt
// to be captured
"-c", "1"}
fmt.Sprintf("src host %s and dst host %s and %s", srcAddr, dstAddr, protoFilter),
}
t.Debugf("Running in bg: %s", strings.Join(cmd, " "))
err := clientHost.K8sClient.ExecInPodWithWriters(ctx, killCmdCtx,
clientHost.Pod.Namespace, clientHost.Pod.Name, "", cmd, bgStdout, bgStderr)
Expand Down Expand Up @@ -177,7 +210,17 @@ func testNoTrafficLeak(ctx context.Context, t *check.Test, s check.Scenario,
t.Fatalf("Failed to retrieve tcpdump pkt count: %s", err)
}
if !strings.HasPrefix(count.String(), "0 packets") {
t.Fatalf("Captured unencrypted pkt (count=%s)", count.String())
t.Failf("Captured unencrypted pkt (count=%s)", strings.TrimRight(count.String(), "\n\r"))

// If debug mode is enabled, dump the captured pkts
if t.Context().Params().Debug {
cmd := []string{"/bin/sh", "-c", fmt.Sprintf("tcpdump -r /tmp/%s.pcap 2>/dev/null", t.Name())}
out, err := clientHost.K8sClient.ExecInPod(ctx, clientHost.Pod.Namespace, clientHost.Pod.Name, "", cmd)
if err != nil {
t.Fatalf("Failed to retrieve tcpdump output: %s", err)
}
t.Debugf("Captured pkts:\n%s", out.String())
}
}
}

Expand Down