blob: 15a604928bd147ff8e6cfd91af9c77eb46fd9edd [file] [log] [blame]
Serge Bazanski64956532021-01-30 19:19:32 +01001package main
2
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +01003import (
4 "encoding/json"
5 "strings"
6 "testing"
7
8 admission "k8s.io/api/admission/v1beta1"
9 networking "k8s.io/api/networking/v1beta1"
10 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
11 runtime "k8s.io/apimachinery/pkg/runtime"
12)
Serge Bazanski64956532021-01-30 19:19:32 +010013
14func TestPatterns(t *testing.T) {
15 f := ingressFilter{}
16 // Test that sane filters are allowed.
17 for _, el := range []struct {
18 ns string
19 domain string
20 }{
21 {"matrix", "matrix.hackerspace.pl"},
22 {"ceph-waw3", "*.hackerspace.pl"},
23 {"personal-q3k", "*.k0.q3k.org"},
24 {"personal-vuko", "shells.vuko.pl"},
25 {"minecraft", "*.k0.q3k.org"},
26 } {
27 err := f.allow(el.ns, el.domain)
28 if err != nil {
29 t.Fatalf("allow(%q, %q): %v", el.ns, el.domain, err)
30 }
31 }
32 // Test that broken patterns are rejected.
33 if err := f.allow("borked", "*.hackerspace.*"); err == nil {
34 t.Fatalf("allow(double star): wanted err, got nil")
35 }
36 if err := f.allow("borked", ""); err == nil {
37 t.Fatalf("allow(empty): wanted err, got nil")
38 }
39 if err := f.allow("borked", "*foo.example.com"); err == nil {
40 t.Fatalf("allow(partial wildcard): wanted err, got nil")
41 }
42}
43
44func TestMatch(t *testing.T) {
45 f := ingressFilter{}
46 // Errors discarded, tested in TestPatterns.
47 f.allow("matrix", "matrix.hackerspace.pl")
48 f.allow("ceph-waw3", "*.hackerspace.pl")
49 f.allow("personal-q3k", "*.k0.q3k.org")
50 f.allow("personal-vuko", "shells.vuko.pl")
51 f.allow("minecraft", "*.k0.q3k.org")
52
53 for _, el := range []struct {
54 ns string
55 dns string
56 expected bool
57 }{
58 // Explicitly allowed.
59 {"matrix", "matrix.hackerspace.pl", true},
60 // *.hackerspace.pl is explicitly mentioned in ceph-waw3, so this is
61 // forbidden.
62 {"matrix", "matrix2.hackerspace.pl", false},
63 // Hackers should not be able to take over critical domains.
64 {"personal-hacker", "matrix.hackerspace.pl", false},
65 {"personal-hacker", "totallylegit.hackerspace.pl", false},
66 // q3k can do his thing, even nested..
67 {"personal-q3k", "foo.k0.q3k.org", true},
68 {"personal-q3k", "foo.bar.k0.q3k.org", true},
69 // counterintuitive: only *.k0.q3k.org is constrained, so k0.q3k.org
70 // (as anything.q3k.org) is allowed everywhere.
71 {"personal-hacker", "k0.q3k.org", true},
72 // vuko's shell service is only allowed in his NS.
73 {"personal-vuko", "shells.vuko.pl", true},
74 // counterintuitive: vuko.pl is allowed everywhere else, too. This is
75 // because there's no *.vuko.pl wildcard anywhere, so nothing would
76 // block it. Solution: add an explicit *.vuko.pl wildcard to the
77 // namespace, or just don't do a wildcard CNAME redirect to our
78 // ingress.
79 {"personal-hacker", "foobar.vuko.pl", true},
80 // Unknown domains are fine.
81 {"personal-hacker", "www.github.com", true},
82 } {
83 if want, got := el.expected, f.domainAllowed(el.ns, el.dns); got != want {
84 t.Errorf("%q on %q is %v, wanted %v", el.dns, el.ns, got, want)
85 }
86 }
87}
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +010088
89func TestIngressPermitted(t *testing.T) {
90 f := ingressFilter{}
91 // Errors discarded, tested in TestPatterns.
92 f.allow("matrix", "matrix.hackerspace.pl")
93 f.allow("ceph-waw3", "*.hackerspace.pl")
94 f.allow("personal-q3k", "*.k0.q3k.org")
95 f.allow("personal-vuko", "shells.vuko.pl")
96 f.allow("minecraft", "*.k0.q3k.org")
97
98 mkReq := func(ns string, annotations map[string]string, is *networking.IngressSpec) *admission.AdmissionRequest {
99 i := &networking.Ingress{
100 Spec: *is,
101 }
102 i.Annotations = annotations
103 raw, err := json.Marshal(i)
104 if err != nil {
105 t.Fatalf("marshaling test ingress: %v", err)
106 }
107 return &admission.AdmissionRequest{
108 UID: "test",
109 Kind: meta.GroupVersionKind{
110 Group: "networking.k8s.io",
111 Version: "v1beta1",
112 Kind: "Ingress",
113 },
114 Namespace: ns,
115 Operation: "CREATE",
116 Object: runtime.RawExtension{
117 Raw: raw,
118 },
119 }
120 }
121
122 for i, el := range []struct {
123 req *admission.AdmissionRequest
124 err string
125 }{
126 // 0: unrelated domain, should be allowed
127 {mkReq("default", nil, &networking.IngressSpec{
128 Rules: []networking.IngressRule{
129 {Host: "example.com"},
130 },
131 TLS: []networking.IngressTLS{
132 {
133 Hosts: []string{"example.com"},
134 },
135 },
136 }), ""},
137 // 1: permitted restricted domain, should be allowed
138 {mkReq("matrix", nil, &networking.IngressSpec{
139 Rules: []networking.IngressRule{
140 {Host: "matrix.hackerspace.pl"},
141 },
142 TLS: []networking.IngressTLS{
143 {
144 Hosts: []string{"matrix.hackerspace.pl"},
145 },
146 },
147 }), ""},
148 // 2: forbidden restricted domain, should be rejected
149 {mkReq("personal-hacker", nil, &networking.IngressSpec{
150 Rules: []networking.IngressRule{
151 {Host: "matrix.hackerspace.pl"},
152 },
153 TLS: []networking.IngressTLS{
154 {
155 Hosts: []string{"matrix.hackerspace.pl"},
156 },
157 },
158 }), "not allowed in namespace"},
159 // 3: weird ingress but okay
160 {mkReq("personal-hacker", nil, &networking.IngressSpec{}), ""},
161 // 4: janky annotations, should be rejected
162 {mkReq("matrix", map[string]string{
163 "nginx.ingress.kubernetes.io/configuration-snippet": "omghax",
164 }, &networking.IngressSpec{
165 Rules: []networking.IngressRule{
166 {Host: "matrix.hackerspace.pl"},
167 },
168 TLS: []networking.IngressTLS{
169 {
170 Hosts: []string{"matrix.hackerspace.pl"},
171 },
172 },
173 }), "forbidden annotation"},
174 // 5: accepted annotations, should be allowed
175 {mkReq("matrix", map[string]string{
176 "nginx.ingress.kubernetes.io/proxy-body-size": "2137",
177 "foo.q3k.org/bar": "baz",
178 }, &networking.IngressSpec{
179 Rules: []networking.IngressRule{
180 {Host: "matrix.hackerspace.pl"},
181 },
182 TLS: []networking.IngressTLS{
183 {
184 Hosts: []string{"matrix.hackerspace.pl"},
185 },
186 },
187 }), ""},
188 } {
189 res, err := f.admit(el.req)
190 if err != nil {
191 t.Errorf("test %d: admit: %v", i, err)
192 }
193 if el.err == "" {
194 if !res.Allowed {
195 t.Errorf("test %d: wanted allow, got %q", i, res.Result.Message)
196 }
197 } else {
198 if res.Allowed {
199 t.Errorf("test %d: wanted %q, got allowed", i, el.err)
200 } else if !strings.Contains(res.Result.Message, el.err) {
201 t.Errorf("test %d: wanted %q, got %q", i, el.err, res.Result.Message)
202 }
203 }
204 }
205}