forked from hswaw/hscloud
This gives us nearly everything required to run the admission controller. In addition to checking for allowed domains, we also do some nginx-inress-controller security checks. Change-Id: Ib187de6d2c06c58bd8c320503d4f850df2ec8abd
205 lines
5.9 KiB
Go
205 lines
5.9 KiB
Go
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")
|
|
|
|
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"},
|
|
},
|
|
},
|
|
}), ""},
|
|
} {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|