| 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") |
| } |
| if err := f.allowRegexp("borked", "(.*"); err == nil { |
| t.Fatalf("allowRegexp(bad regexp): 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") |
| f.allow("hscloud-ovh-root", "hscloud.ovh") |
| f.allowRegexp("personal-$2", `(.*\.)?([^.]+)\.hscloud\.ovh`) |
| |
| 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}, |
| // Regexp matching for auto-namespaced domains |
| {"personal-radex", "radex.hscloud.ovh", true}, |
| {"personal-radex", "foo.bar.radex.hscloud.ovh", true}, |
| // Disallowed for other namespaces |
| {"personal-hacker", "radex.hscloud.ovh", false}, |
| {"personal-hacker", "foo.bar.radex.hscloud.ovh", false}, |
| {"matrix", "radex.hscloud.ovh", false}, |
| // Check auto-namespaced domain's root |
| {"hscloud-ovh-root", "hscloud.ovh", true}, |
| {"personal-hacker", "hscloud.ovh", false}, |
| } { |
| 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) |
| } |
| } |
| } |
| } |