package main import ( "encoding/json" "strings" "testing" admission "k8s.io/api/admission/v1beta1" networking "k8s.io/api/networking/v1beta1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) func TestPatterns(t *testing.T) { f := ingressFilter{} // Test that sane filters are allowed. for _, el := range []struct { ns string domain string }{ {"matrix", "matrix.hackerspace.pl"}, {"ceph-waw3", "*.hackerspace.pl"}, {"personal-q3k", "*.k0.q3k.org"}, {"personal-vuko", "shells.vuko.pl"}, {"minecraft", "*.k0.q3k.org"}, } { err := f.allow(el.ns, el.domain) if err != nil { t.Fatalf("allow(%q, %q): %v", el.ns, el.domain, err) } } // Test that broken patterns are rejected. if err := f.allow("borked", "*.hackerspace.*"); err == nil { t.Fatalf("allow(double star): wanted err, got nil") } if err := f.allow("borked", ""); err == nil { t.Fatalf("allow(empty): wanted err, got nil") } if err := f.allow("borked", "*foo.example.com"); err == nil { t.Fatalf("allow(partial wildcard): wanted err, got nil") } } func TestMatch(t *testing.T) { f := ingressFilter{} // Errors discarded, tested in TestPatterns. f.allow("matrix", "matrix.hackerspace.pl") f.allow("ceph-waw3", "*.hackerspace.pl") f.allow("personal-q3k", "*.k0.q3k.org") f.allow("personal-vuko", "shells.vuko.pl") f.allow("minecraft", "*.k0.q3k.org") for _, el := range []struct { ns string dns string expected bool }{ // Explicitly allowed. {"matrix", "matrix.hackerspace.pl", true}, // *.hackerspace.pl is explicitly mentioned in ceph-waw3, so this is // forbidden. {"matrix", "matrix2.hackerspace.pl", false}, // Hackers should not be able to take over critical domains. {"personal-hacker", "matrix.hackerspace.pl", false}, {"personal-hacker", "totallylegit.hackerspace.pl", false}, // q3k can do his thing, even nested.. {"personal-q3k", "foo.k0.q3k.org", true}, {"personal-q3k", "foo.bar.k0.q3k.org", true}, // counterintuitive: only *.k0.q3k.org is constrained, so k0.q3k.org // (as anything.q3k.org) is allowed everywhere. {"personal-hacker", "k0.q3k.org", true}, // vuko's shell service is only allowed in his NS. {"personal-vuko", "shells.vuko.pl", true}, // counterintuitive: vuko.pl is allowed everywhere else, too. This is // because there's no *.vuko.pl wildcard anywhere, so nothing would // block it. Solution: add an explicit *.vuko.pl wildcard to the // namespace, or just don't do a wildcard CNAME redirect to our // ingress. {"personal-hacker", "foobar.vuko.pl", true}, // Unknown domains are fine. {"personal-hacker", "www.github.com", true}, } { if want, got := el.expected, f.domainAllowed(el.ns, el.dns); got != want { t.Errorf("%q on %q is %v, wanted %v", el.dns, el.ns, got, want) } } } func TestIngressPermitted(t *testing.T) { f := ingressFilter{} // Errors discarded, tested in TestPatterns. f.allow("matrix", "matrix.hackerspace.pl") f.allow("ceph-waw3", "*.hackerspace.pl") f.allow("personal-q3k", "*.k0.q3k.org") f.allow("personal-vuko", "shells.vuko.pl") f.allow("minecraft", "*.k0.q3k.org") f.anythingGoesNamespaces = []string{"opted-out"} mkReq := func(ns string, annotations map[string]string, is *networking.IngressSpec) *admission.AdmissionRequest { i := &networking.Ingress{ Spec: *is, } i.Annotations = annotations raw, err := json.Marshal(i) if err != nil { t.Fatalf("marshaling test ingress: %v", err) } return &admission.AdmissionRequest{ UID: "test", Kind: meta.GroupVersionKind{ Group: "networking.k8s.io", Version: "v1beta1", Kind: "Ingress", }, Namespace: ns, Operation: "CREATE", Object: runtime.RawExtension{ Raw: raw, }, } } for i, el := range []struct { req *admission.AdmissionRequest err string }{ // 0: unrelated domain, should be allowed {mkReq("default", nil, &networking.IngressSpec{ Rules: []networking.IngressRule{ {Host: "example.com"}, }, TLS: []networking.IngressTLS{ { Hosts: []string{"example.com"}, }, }, }), ""}, // 1: permitted restricted domain, should be allowed {mkReq("matrix", nil, &networking.IngressSpec{ Rules: []networking.IngressRule{ {Host: "matrix.hackerspace.pl"}, }, TLS: []networking.IngressTLS{ { Hosts: []string{"matrix.hackerspace.pl"}, }, }, }), ""}, // 2: forbidden restricted domain, should be rejected {mkReq("personal-hacker", nil, &networking.IngressSpec{ Rules: []networking.IngressRule{ {Host: "matrix.hackerspace.pl"}, }, TLS: []networking.IngressTLS{ { Hosts: []string{"matrix.hackerspace.pl"}, }, }, }), "not allowed in namespace"}, // 3: weird ingress but okay {mkReq("personal-hacker", nil, &networking.IngressSpec{}), ""}, // 4: janky annotations, should be rejected {mkReq("matrix", map[string]string{ "nginx.ingress.kubernetes.io/configuration-snippet": "omghax", }, &networking.IngressSpec{ Rules: []networking.IngressRule{ {Host: "matrix.hackerspace.pl"}, }, TLS: []networking.IngressTLS{ { Hosts: []string{"matrix.hackerspace.pl"}, }, }, }), "forbidden annotation"}, // 5: accepted annotations, should be allowed {mkReq("matrix", map[string]string{ "nginx.ingress.kubernetes.io/proxy-body-size": "2137", "foo.q3k.org/bar": "baz", }, &networking.IngressSpec{ Rules: []networking.IngressRule{ {Host: "matrix.hackerspace.pl"}, }, TLS: []networking.IngressTLS{ { Hosts: []string{"matrix.hackerspace.pl"}, }, }, }), ""}, // 6: janky annotations, should be allowed by exception {mkReq("opted-out", map[string]string{ "nginx.ingress.kubernetes.io/configuration-snippet": "omghax", }, &networking.IngressSpec{ Rules: []networking.IngressRule{ {Host: "matrix.hackerspace.pl"}, }, TLS: []networking.IngressTLS{ { Hosts: []string{"matrix.hackerspace.pl"}, }, }, }), ""}, } { res, err := f.admit(el.req) if err != nil { t.Errorf("test %d: admit: %v", i, err) } if el.err == "" { if !res.Allowed { t.Errorf("test %d: wanted allow, got %q", i, res.Result.Message) } } else { if res.Allowed { t.Errorf("test %d: wanted %q, got allowed", i, el.err) } else if !strings.Contains(res.Result.Message, el.err) { t.Errorf("test %d: wanted %q, got %q", i, el.err, res.Result.Message) } } } }