blob: 92b135791b749385fba497a79e4b03e02b197801 [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")
Serge Bazanskic1f37252023-06-19 21:56:29 +000097 f.anythingGoesNamespaces = []string{"opted-out"}
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +010098
99 mkReq := func(ns string, annotations map[string]string, is *networking.IngressSpec) *admission.AdmissionRequest {
100 i := &networking.Ingress{
101 Spec: *is,
102 }
103 i.Annotations = annotations
104 raw, err := json.Marshal(i)
105 if err != nil {
106 t.Fatalf("marshaling test ingress: %v", err)
107 }
108 return &admission.AdmissionRequest{
109 UID: "test",
110 Kind: meta.GroupVersionKind{
111 Group: "networking.k8s.io",
112 Version: "v1beta1",
113 Kind: "Ingress",
114 },
115 Namespace: ns,
116 Operation: "CREATE",
117 Object: runtime.RawExtension{
118 Raw: raw,
119 },
120 }
121 }
122
123 for i, el := range []struct {
124 req *admission.AdmissionRequest
125 err string
126 }{
127 // 0: unrelated domain, should be allowed
128 {mkReq("default", nil, &networking.IngressSpec{
129 Rules: []networking.IngressRule{
130 {Host: "example.com"},
131 },
132 TLS: []networking.IngressTLS{
133 {
134 Hosts: []string{"example.com"},
135 },
136 },
137 }), ""},
138 // 1: permitted restricted domain, should be allowed
139 {mkReq("matrix", nil, &networking.IngressSpec{
140 Rules: []networking.IngressRule{
141 {Host: "matrix.hackerspace.pl"},
142 },
143 TLS: []networking.IngressTLS{
144 {
145 Hosts: []string{"matrix.hackerspace.pl"},
146 },
147 },
148 }), ""},
149 // 2: forbidden restricted domain, should be rejected
150 {mkReq("personal-hacker", nil, &networking.IngressSpec{
151 Rules: []networking.IngressRule{
152 {Host: "matrix.hackerspace.pl"},
153 },
154 TLS: []networking.IngressTLS{
155 {
156 Hosts: []string{"matrix.hackerspace.pl"},
157 },
158 },
159 }), "not allowed in namespace"},
160 // 3: weird ingress but okay
161 {mkReq("personal-hacker", nil, &networking.IngressSpec{}), ""},
162 // 4: janky annotations, should be rejected
163 {mkReq("matrix", map[string]string{
164 "nginx.ingress.kubernetes.io/configuration-snippet": "omghax",
165 }, &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 }), "forbidden annotation"},
175 // 5: accepted annotations, should be allowed
176 {mkReq("matrix", map[string]string{
177 "nginx.ingress.kubernetes.io/proxy-body-size": "2137",
178 "foo.q3k.org/bar": "baz",
179 }, &networking.IngressSpec{
180 Rules: []networking.IngressRule{
181 {Host: "matrix.hackerspace.pl"},
182 },
183 TLS: []networking.IngressTLS{
184 {
185 Hosts: []string{"matrix.hackerspace.pl"},
186 },
187 },
188 }), ""},
Serge Bazanskic1f37252023-06-19 21:56:29 +0000189 // 6: janky annotations, should be allowed by exception
190 {mkReq("opted-out", map[string]string{
191 "nginx.ingress.kubernetes.io/configuration-snippet": "omghax",
192 }, &networking.IngressSpec{
193 Rules: []networking.IngressRule{
194 {Host: "matrix.hackerspace.pl"},
195 },
196 TLS: []networking.IngressTLS{
197 {
198 Hosts: []string{"matrix.hackerspace.pl"},
199 },
200 },
201 }), ""},
Serge Bazanski5d2c8fc2021-01-30 21:23:53 +0100202 } {
203 res, err := f.admit(el.req)
204 if err != nil {
205 t.Errorf("test %d: admit: %v", i, err)
206 }
207 if el.err == "" {
208 if !res.Allowed {
209 t.Errorf("test %d: wanted allow, got %q", i, res.Result.Message)
210 }
211 } else {
212 if res.Allowed {
213 t.Errorf("test %d: wanted %q, got allowed", i, el.err)
214 } else if !strings.Contains(res.Result.Message, el.err) {
215 t.Errorf("test %d: wanted %q, got %q", i, el.err, res.Result.Message)
216 }
217 }
218 }
219}