blob: 8544fabf78cf49962efbdd9f39595a35664f871c [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 }
radexe36beba2023-10-11 00:41:48 +020042 if err := f.allowRegexp("borked", "(.*"); err == nil {
43 t.Fatalf("allowRegexp(bad regexp): wanted err, got nil")
44 }
Serge Bazanski64956532021-01-30 19:19:32 +010045}
46
47func TestMatch(t *testing.T) {
48 f := ingressFilter{}
49 // Errors discarded, tested in TestPatterns.
50 f.allow("matrix", "matrix.hackerspace.pl")
51 f.allow("ceph-waw3", "*.hackerspace.pl")
52 f.allow("personal-q3k", "*.k0.q3k.org")
53 f.allow("personal-vuko", "shells.vuko.pl")
54 f.allow("minecraft", "*.k0.q3k.org")
radexe36beba2023-10-11 00:41:48 +020055 f.allow("hscloud-ovh-root", "hscloud.ovh")
56 f.allowRegexp("personal-$2", `(.*\.)?([^.]+)\.hscloud\.ovh`)
Serge Bazanski64956532021-01-30 19:19:32 +010057
58 for _, el := range []struct {
59 ns string
60 dns string
61 expected bool
62 }{
63 // Explicitly allowed.
64 {"matrix", "matrix.hackerspace.pl", true},
65 // *.hackerspace.pl is explicitly mentioned in ceph-waw3, so this is
66 // forbidden.
67 {"matrix", "matrix2.hackerspace.pl", false},
68 // Hackers should not be able to take over critical domains.
69 {"personal-hacker", "matrix.hackerspace.pl", false},
70 {"personal-hacker", "totallylegit.hackerspace.pl", false},
71 // q3k can do his thing, even nested..
72 {"personal-q3k", "foo.k0.q3k.org", true},
73 {"personal-q3k", "foo.bar.k0.q3k.org", true},
74 // counterintuitive: only *.k0.q3k.org is constrained, so k0.q3k.org
75 // (as anything.q3k.org) is allowed everywhere.
76 {"personal-hacker", "k0.q3k.org", true},
77 // vuko's shell service is only allowed in his NS.
78 {"personal-vuko", "shells.vuko.pl", true},
79 // counterintuitive: vuko.pl is allowed everywhere else, too. This is
80 // because there's no *.vuko.pl wildcard anywhere, so nothing would
81 // block it. Solution: add an explicit *.vuko.pl wildcard to the
82 // namespace, or just don't do a wildcard CNAME redirect to our
83 // ingress.
84 {"personal-hacker", "foobar.vuko.pl", true},
85 // Unknown domains are fine.
86 {"personal-hacker", "www.github.com", true},
radexe36beba2023-10-11 00:41:48 +020087 // Regexp matching for auto-namespaced domains
88 {"personal-radex", "radex.hscloud.ovh", true},
89 {"personal-radex", "foo.bar.radex.hscloud.ovh", true},
90 // Disallowed for other namespaces
91 {"personal-hacker", "radex.hscloud.ovh", false},
92 {"personal-hacker", "foo.bar.radex.hscloud.ovh", false},
93 {"matrix", "radex.hscloud.ovh", false},
94 // Check auto-namespaced domain's root
95 {"hscloud-ovh-root", "hscloud.ovh", true},
96 {"personal-hacker", "hscloud.ovh", false},
Serge Bazanski64956532021-01-30 19:19:32 +010097 } {
98 if want, got := el.expected, f.domainAllowed(el.ns, el.dns); got != want {
99 t.Errorf("%q on %q is %v, wanted %v", el.dns, el.ns, got, want)
100 }
101 }
102}
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +0100103
104func TestIngressPermitted(t *testing.T) {
105 f := ingressFilter{}
106 // Errors discarded, tested in TestPatterns.
107 f.allow("matrix", "matrix.hackerspace.pl")
108 f.allow("ceph-waw3", "*.hackerspace.pl")
109 f.allow("personal-q3k", "*.k0.q3k.org")
110 f.allow("personal-vuko", "shells.vuko.pl")
111 f.allow("minecraft", "*.k0.q3k.org")
Serge Bazanskic1f37252023-06-19 21:56:29 +0000112 f.anythingGoesNamespaces = []string{"opted-out"}
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +0100113
114 mkReq := func(ns string, annotations map[string]string, is *networking.IngressSpec) *admission.AdmissionRequest {
115 i := &networking.Ingress{
116 Spec: *is,
117 }
118 i.Annotations = annotations
119 raw, err := json.Marshal(i)
120 if err != nil {
121 t.Fatalf("marshaling test ingress: %v", err)
122 }
123 return &admission.AdmissionRequest{
124 UID: "test",
125 Kind: meta.GroupVersionKind{
126 Group: "networking.k8s.io",
127 Version: "v1beta1",
128 Kind: "Ingress",
129 },
130 Namespace: ns,
131 Operation: "CREATE",
132 Object: runtime.RawExtension{
133 Raw: raw,
134 },
135 }
136 }
137
138 for i, el := range []struct {
139 req *admission.AdmissionRequest
140 err string
141 }{
142 // 0: unrelated domain, should be allowed
143 {mkReq("default", nil, &networking.IngressSpec{
144 Rules: []networking.IngressRule{
145 {Host: "example.com"},
146 },
147 TLS: []networking.IngressTLS{
148 {
149 Hosts: []string{"example.com"},
150 },
151 },
152 }), ""},
153 // 1: permitted restricted domain, should be allowed
154 {mkReq("matrix", nil, &networking.IngressSpec{
155 Rules: []networking.IngressRule{
156 {Host: "matrix.hackerspace.pl"},
157 },
158 TLS: []networking.IngressTLS{
159 {
160 Hosts: []string{"matrix.hackerspace.pl"},
161 },
162 },
163 }), ""},
164 // 2: forbidden restricted domain, should be rejected
165 {mkReq("personal-hacker", nil, &networking.IngressSpec{
166 Rules: []networking.IngressRule{
167 {Host: "matrix.hackerspace.pl"},
168 },
169 TLS: []networking.IngressTLS{
170 {
171 Hosts: []string{"matrix.hackerspace.pl"},
172 },
173 },
174 }), "not allowed in namespace"},
175 // 3: weird ingress but okay
176 {mkReq("personal-hacker", nil, &networking.IngressSpec{}), ""},
177 // 4: janky annotations, should be rejected
178 {mkReq("matrix", map[string]string{
179 "nginx.ingress.kubernetes.io/configuration-snippet": "omghax",
180 }, &networking.IngressSpec{
181 Rules: []networking.IngressRule{
182 {Host: "matrix.hackerspace.pl"},
183 },
184 TLS: []networking.IngressTLS{
185 {
186 Hosts: []string{"matrix.hackerspace.pl"},
187 },
188 },
189 }), "forbidden annotation"},
190 // 5: accepted annotations, should be allowed
191 {mkReq("matrix", map[string]string{
192 "nginx.ingress.kubernetes.io/proxy-body-size": "2137",
193 "foo.q3k.org/bar": "baz",
194 }, &networking.IngressSpec{
195 Rules: []networking.IngressRule{
196 {Host: "matrix.hackerspace.pl"},
197 },
198 TLS: []networking.IngressTLS{
199 {
200 Hosts: []string{"matrix.hackerspace.pl"},
201 },
202 },
203 }), ""},
Serge Bazanskic1f37252023-06-19 21:56:29 +0000204 // 6: janky annotations, should be allowed by exception
205 {mkReq("opted-out", map[string]string{
206 "nginx.ingress.kubernetes.io/configuration-snippet": "omghax",
207 }, &networking.IngressSpec{
208 Rules: []networking.IngressRule{
209 {Host: "matrix.hackerspace.pl"},
210 },
211 TLS: []networking.IngressTLS{
212 {
213 Hosts: []string{"matrix.hackerspace.pl"},
214 },
215 },
216 }), ""},
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +0100217 } {
218 res, err := f.admit(el.req)
219 if err != nil {
220 t.Errorf("test %d: admit: %v", i, err)
221 }
222 if el.err == "" {
223 if !res.Allowed {
224 t.Errorf("test %d: wanted allow, got %q", i, res.Result.Message)
225 }
226 } else {
227 if res.Allowed {
228 t.Errorf("test %d: wanted %q, got allowed", i, el.err)
229 } else if !strings.Contains(res.Result.Message, el.err) {
230 t.Errorf("test %d: wanted %q, got %q", i, el.err, res.Result.Message)
231 }
232 }
233 }
234}