blob: 92b135791b749385fba497a79e4b03e02b197801 [file] [log] [blame]
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)
}
}
}
}