148 lines
4.7 KiB
Go
148 lines
4.7 KiB
Go
|
|
package netpol
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
corev1 "k8s.io/api/core/v1"
|
||
|
|
netv1 "k8s.io/api/networking/v1"
|
||
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||
|
|
)
|
||
|
|
|
||
|
|
// FuzzTranslate_AndRender stitches the Translator and Renderer together
|
||
|
|
// against synthetic NetworkPolicies built from fuzzed bytes. We are not
|
||
|
|
// trying to produce *valid* policies — the goal is to confirm that:
|
||
|
|
//
|
||
|
|
// 1. Neither stage panics on weird input.
|
||
|
|
// 2. Render output is balanced (every "{" has a matching "}").
|
||
|
|
// 3. Rendering twice is byte-stable.
|
||
|
|
// 4. The Pods set in Output is consistent with Isolated (every isolated
|
||
|
|
// PodKey has a matching entry in Pods).
|
||
|
|
//
|
||
|
|
// The translator's warn callback is captured to ensure it never panics
|
||
|
|
// with unexpected message types either.
|
||
|
|
func FuzzTranslate_AndRender(f *testing.F) {
|
||
|
|
type seed struct {
|
||
|
|
policyNS, policyName string
|
||
|
|
podSelectorKey, podSelValue string
|
||
|
|
peerSelectorKey, peerSelV string
|
||
|
|
peerNS, peerName, peerIP string
|
||
|
|
port uint16
|
||
|
|
ipBlockCIDR, ipBlockExcept string
|
||
|
|
}
|
||
|
|
for _, s := range []seed{
|
||
|
|
{policyNS: "ns1", policyName: "p1", podSelectorKey: "app", podSelValue: "web", port: 80},
|
||
|
|
{policyNS: "ns1", policyName: "p1", peerSelectorKey: "app", peerSelV: "client", peerNS: "ns1", peerName: "c1", peerIP: "2001:db8::aa", port: 443},
|
||
|
|
{policyNS: "ns1", policyName: "p1", ipBlockCIDR: "10.0.0.0/8", ipBlockExcept: "10.99.0.0/16", port: 0},
|
||
|
|
{policyNS: "", policyName: ""}, // pathological
|
||
|
|
{policyNS: "ns1", policyName: "p1", podSelectorKey: "app\x00", podSelValue: "web\nnewline"},
|
||
|
|
{policyNS: "ns1", policyName: "p1", port: 65535},
|
||
|
|
{policyNS: "ns1", policyName: "p1", port: 1},
|
||
|
|
} {
|
||
|
|
f.Add(s.policyNS, s.policyName, s.podSelectorKey, s.podSelValue,
|
||
|
|
s.peerSelectorKey, s.peerSelV, s.peerNS, s.peerName, s.peerIP,
|
||
|
|
s.port, s.ipBlockCIDR, s.ipBlockExcept)
|
||
|
|
}
|
||
|
|
|
||
|
|
f.Fuzz(func(t *testing.T,
|
||
|
|
policyNS, policyName,
|
||
|
|
podSelectorKey, podSelValue,
|
||
|
|
peerSelectorKey, peerSelV,
|
||
|
|
peerNS, peerName, peerIP string,
|
||
|
|
port uint16,
|
||
|
|
ipBlockCIDR, ipBlockExcept string,
|
||
|
|
) {
|
||
|
|
// Build a synthetic policy.
|
||
|
|
policy := netv1.NetworkPolicy{
|
||
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: policyNS, Name: policyName},
|
||
|
|
Spec: netv1.NetworkPolicySpec{
|
||
|
|
PolicyTypes: []netv1.PolicyType{netv1.PolicyTypeIngress},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
if podSelectorKey != "" {
|
||
|
|
policy.Spec.PodSelector = metav1.LabelSelector{
|
||
|
|
MatchLabels: map[string]string{podSelectorKey: podSelValue},
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
policy.Spec.PodSelector = metav1.LabelSelector{}
|
||
|
|
}
|
||
|
|
ingress := netv1.NetworkPolicyIngressRule{}
|
||
|
|
if peerSelectorKey != "" {
|
||
|
|
ingress.From = append(ingress.From, netv1.NetworkPolicyPeer{
|
||
|
|
PodSelector: &metav1.LabelSelector{
|
||
|
|
MatchLabels: map[string]string{peerSelectorKey: peerSelV},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
if ipBlockCIDR != "" {
|
||
|
|
peer := netv1.NetworkPolicyPeer{
|
||
|
|
IPBlock: &netv1.IPBlock{CIDR: ipBlockCIDR},
|
||
|
|
}
|
||
|
|
if ipBlockExcept != "" {
|
||
|
|
peer.IPBlock.Except = []string{ipBlockExcept}
|
||
|
|
}
|
||
|
|
ingress.From = append(ingress.From, peer)
|
||
|
|
}
|
||
|
|
if port != 0 {
|
||
|
|
tcp := corev1.ProtocolTCP
|
||
|
|
p := intstr.FromInt32(int32(port))
|
||
|
|
ingress.Ports = append(ingress.Ports, netv1.NetworkPolicyPort{
|
||
|
|
Protocol: &tcp, Port: &p,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
policy.Spec.Ingress = append(policy.Spec.Ingress, ingress)
|
||
|
|
|
||
|
|
// Local pod, possibly matching the policy.
|
||
|
|
pod := Pod{
|
||
|
|
Namespace: "ns1", Name: "web",
|
||
|
|
Labels: map[string]string{podSelectorKey: podSelValue, "app": "web"},
|
||
|
|
HostIface: "flock00000001",
|
||
|
|
IPs: []net.IP{mustIP("2001:db8::1")},
|
||
|
|
}
|
||
|
|
// Peer pod, possibly matching the peer selector.
|
||
|
|
var peers []PeerPod
|
||
|
|
if peerName != "" {
|
||
|
|
peerIPParsed := net.ParseIP(peerIP)
|
||
|
|
if peerIPParsed != nil {
|
||
|
|
peers = append(peers, PeerPod{
|
||
|
|
Namespace: peerNS, Name: peerName,
|
||
|
|
Labels: map[string]string{peerSelectorKey: peerSelV},
|
||
|
|
IPs: []net.IP{peerIPParsed},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
out, err := Translate(Inputs{
|
||
|
|
LocalPods: []Pod{pod},
|
||
|
|
PeerPods: peers,
|
||
|
|
Namespaces: []Namespace{
|
||
|
|
{Name: "ns1", Labels: map[string]string{"kubernetes.io/metadata.name": "ns1"}},
|
||
|
|
},
|
||
|
|
Policies: []netv1.NetworkPolicy{policy},
|
||
|
|
}, func(string) {})
|
||
|
|
if err != nil {
|
||
|
|
return // any error is acceptable
|
||
|
|
}
|
||
|
|
|
||
|
|
// Property: every isolated PodKey appears in Output.Pods.
|
||
|
|
for iso := range out.Isolated {
|
||
|
|
if _, ok := out.Pods[iso.PodKey]; !ok {
|
||
|
|
t.Fatalf("isolated %s has no Pods entry", iso.PodKey)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
script := Render(out)
|
||
|
|
// Property: balanced braces.
|
||
|
|
if got := strings.Count(script, "{") - strings.Count(script, "}"); got != 0 {
|
||
|
|
t.Fatalf("unbalanced braces (%d):\n%s", got, script)
|
||
|
|
}
|
||
|
|
// Property: deterministic (run again, compare).
|
||
|
|
script2 := Render(out)
|
||
|
|
if script != script2 {
|
||
|
|
t.Fatalf("Render not deterministic")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|