1 package injector
2
3 import (
4 "encoding/json"
5 "path/filepath"
6 "testing"
7
8 "github.com/go-test/deep"
9 "github.com/linkerd/linkerd2/controller/proxy-injector/fake"
10 "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
11 "github.com/linkerd/linkerd2/pkg/inject"
12 pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
13 admissionv1beta1 "k8s.io/api/admission/v1beta1"
14 corev1 "k8s.io/api/core/v1"
15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16 "k8s.io/apimachinery/pkg/runtime"
17 )
18
19 type unmarshalledPatch []map[string]interface{}
20
21 var (
22 values, _ = linkerd2.NewValues()
23 )
24
25 func confNsEnabled() *inject.ResourceConfig {
26 return inject.
27 NewResourceConfig(values, inject.OriginWebhook, "linkerd").
28 WithNsAnnotations(map[string]string{
29 pkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled,
30 })
31 }
32
33 func confNsDisabled() *inject.ResourceConfig {
34 return inject.NewResourceConfig(values, inject.OriginWebhook, "linkerd").
35 WithNsAnnotations(map[string]string{})
36 }
37
38 func confNsWithOpaquePorts() *inject.ResourceConfig {
39 return inject.
40 NewResourceConfig(values, inject.OriginWebhook, "linkerd").
41 WithNsAnnotations(map[string]string{
42 pkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled,
43 pkgK8s.ProxyOpaquePortsAnnotation: "3306",
44 })
45 }
46
47 func confNsWithoutOpaquePorts() *inject.ResourceConfig {
48 return inject.
49 NewResourceConfig(values, inject.OriginWebhook, "linkerd").
50 WithNsAnnotations(map[string]string{
51 pkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled,
52 })
53 }
54
55 func confNsWithConfigAnnotations() *inject.ResourceConfig {
56 return inject.
57 NewResourceConfig(values, inject.OriginWebhook, "linkerd").
58 WithNsAnnotations(map[string]string{
59 pkgK8s.ProxyInjectAnnotation: pkgK8s.ProxyInjectEnabled,
60 pkgK8s.ProxyIgnoreOutboundPortsAnnotation: "34567",
61 pkgK8s.ProxyWaitBeforeExitSecondsAnnotation: "300",
62 "config.linkerd.io/invalid-key": "invalid-value",
63 })
64 }
65 func TestGetPodPatch(t *testing.T) {
66
67 values.IdentityTrustAnchorsPEM = "IdentityTrustAnchorsPEM"
68
69 factory := fake.NewFactory(filepath.Join("fake", "data"))
70 nsEnabled, err := factory.Namespace("namespace-inject-enabled.yaml")
71 if err != nil {
72 t.Fatalf("Unexpected error: %s", err)
73 }
74
75 nsDisabled, err := factory.Namespace("namespace-inject-disabled.yaml")
76 if err != nil {
77 t.Fatalf("Unexpected error: %s", err)
78 }
79
80 t.Run("by checking annotations", func(t *testing.T) {
81 var testCases = []struct {
82 filename string
83 ns *corev1.Namespace
84 conf *inject.ResourceConfig
85 }{
86 {
87 filename: "pod-inject-empty.yaml",
88 ns: nsEnabled,
89 conf: confNsEnabled(),
90 },
91 {
92 filename: "pod-inject-enabled.yaml",
93 ns: nsEnabled,
94 conf: confNsEnabled(),
95 },
96 {
97 filename: "pod-inject-enabled.yaml",
98 ns: nsDisabled,
99 conf: confNsDisabled(),
100 },
101 {
102 filename: "pod-with-debug-disabled.yaml",
103 ns: nsDisabled,
104 conf: confNsDisabled(),
105 },
106 }
107
108 expectedPatchBytes, err := factory.FileContents("pod.patch.json")
109 if err != nil {
110 t.Fatalf("Unexpected error: %s", err)
111 }
112 expectedPatch, err := unmarshalPatch(expectedPatchBytes)
113 if err != nil {
114 t.Fatalf("Unexpected error: %s", err)
115 }
116
117 for _, testCase := range testCases {
118 testCase := testCase
119 t.Run(testCase.filename, func(t *testing.T) {
120 pod, err := factory.FileContents(testCase.filename)
121 if err != nil {
122 t.Fatalf("Unexpected error: %s", err)
123 }
124
125 fakeReq := getFakePodReq(pod)
126 fullConf := testCase.conf.
127 WithKind(fakeReq.Kind.Kind).
128 WithOwnerRetriever(ownerRetrieverFake)
129 _, err = fullConf.ParseMetaAndYAML(fakeReq.Object.Raw)
130 if err != nil {
131 t.Fatal(err)
132 }
133
134 patchJSON, err := fullConf.GetPodPatch(true)
135 if err != nil {
136 t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err)
137 }
138 actualPatch, err := unmarshalPatch(patchJSON)
139 if err != nil {
140 t.Fatalf("Unexpected error: %s", err)
141 }
142 if diff := deep.Equal(expectedPatch, actualPatch); diff != nil {
143 t.Fatalf("The actual patch didn't match what was expected.\n%+v", diff)
144 }
145 })
146 }
147 })
148
149 t.Run("by checking annotations with debug", func(t *testing.T) {
150 expectedPatchBytes, err := factory.FileContents("pod-with-debug.patch.json")
151 if err != nil {
152 t.Fatalf("Unexpected error: %s", err)
153 }
154
155 expectedPatch, err := unmarshalPatch(expectedPatchBytes)
156 if err != nil {
157 t.Fatalf("Unexpected error: %s", err)
158 }
159
160 pod, err := factory.FileContents("pod-with-debug-enabled.yaml")
161 if err != nil {
162 t.Fatalf("Unexpected error: %s", err)
163 }
164 fakeReq := getFakePodReq(pod)
165 conf := confNsEnabled().WithKind(fakeReq.Kind.Kind).WithOwnerRetriever(ownerRetrieverFake)
166 _, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)
167 if err != nil {
168 t.Fatal(err)
169 }
170
171 patchJSON, err := conf.GetPodPatch(true)
172 if err != nil {
173 t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err)
174 }
175 actualPatch, err := unmarshalPatch(patchJSON)
176 if err != nil {
177 t.Fatalf("Unexpected error: %s", err)
178 }
179 if diff := deep.Equal(expectedPatch, actualPatch); diff != nil {
180 t.Fatalf("The actual patch didn't match what was expected.\n%+v", diff)
181 }
182 })
183
184 t.Run("by checking pod inherits config annotations from namespace", func(t *testing.T) {
185 expectedPatchBytes, err := factory.FileContents("pod-with-ns-annotations.patch.json")
186 if err != nil {
187 t.Fatalf("Unexpected error: %s", err)
188 }
189 expectedPatch, err := unmarshalPatch(expectedPatchBytes)
190 if err != nil {
191 t.Fatalf("Unexpected error: %s", err)
192 }
193
194 pod, err := factory.FileContents("pod-inject-enabled.yaml")
195 if err != nil {
196 t.Fatalf("Unexpected error: %s", err)
197 }
198 fakeReq := getFakePodReq(pod)
199 conf := confNsWithConfigAnnotations().
200 WithKind(fakeReq.Kind.Kind).
201 WithOwnerRetriever(ownerRetrieverFake)
202 _, err = conf.ParseMetaAndYAML(fakeReq.Object.Raw)
203 if err != nil {
204 t.Fatal(err)
205 }
206
207
208
209 inject.AppendNamespaceAnnotations(conf.GetOverrideAnnotations(), conf.GetNsAnnotations(), conf.GetWorkloadAnnotations())
210 patchJSON, err := conf.GetPodPatch(true)
211 if err != nil {
212 t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err)
213 }
214 actualPatch, err := unmarshalPatch(patchJSON)
215 if err != nil {
216 t.Fatalf("Unexpected error: %s", err)
217 }
218 if diff := deep.Equal(expectedPatch, actualPatch); diff != nil {
219 t.Fatalf("The actual patch didn't match what was expected.\n+%v", diff)
220 }
221 })
222
223 t.Run("by checking container spec", func(t *testing.T) {
224 deployment, err := factory.FileContents("deployment-with-injected-proxy.yaml")
225 if err != nil {
226 t.Fatalf("Unexpected error: %s", err)
227 }
228
229 fakeReq := getFakePodReq(deployment)
230 conf := confNsDisabled().WithKind(fakeReq.Kind.Kind)
231 patchJSON, err := conf.GetPodPatch(true)
232 if err != nil {
233 t.Fatalf("Unexpected PatchForAdmissionRequest error: %s", err)
234 }
235
236 if len(patchJSON) == 0 {
237 t.Errorf("Expected empty patch")
238 }
239 })
240 }
241
242 func TestGetAnnotationPatch(t *testing.T) {
243 factory := fake.NewFactory(filepath.Join("fake", "data"))
244 nsWithOpaquePorts, err := factory.Namespace("namespace-with-opaque-ports.yaml")
245 if err != nil {
246 t.Fatalf("Unexpected error: %s", err)
247 }
248 nsWithoutOpaquePorts, err := factory.Namespace("namespace-inject-enabled.yaml")
249 if err != nil {
250 t.Fatalf("Unexpected error: %s", err)
251 }
252 t.Run("by checking patch annotations", func(t *testing.T) {
253 servicePatchBytes, err := factory.FileContents("annotation.patch.json")
254 if err != nil {
255 t.Fatalf("Unexpected error: %s", err)
256 }
257 servicePatch, err := unmarshalPatch(servicePatchBytes)
258 if err != nil {
259 t.Fatalf("Unexpected error: %s", err)
260 }
261 podPatchBytes, err := factory.FileContents("annotation.patch.json")
262 if err != nil {
263 t.Fatalf("Unexpected error: %s", err)
264 }
265 podPatch, err := unmarshalPatch(podPatchBytes)
266 if err != nil {
267 t.Fatalf("Unexpected error: %s", err)
268 }
269 filteredServiceBytes, err := factory.FileContents("filtered-service-opaque-ports.json")
270 if err != nil {
271 t.Fatalf("Unexpected error: %s", err)
272 }
273 filteredServicePatch, err := unmarshalPatch(filteredServiceBytes)
274 if err != nil {
275 t.Fatalf("Unexpected error: %s", err)
276 }
277 filteredPodBytes, err := factory.FileContents("filtered-pod-opaque-ports.json")
278 if err != nil {
279 t.Fatalf("Unexpected error: %s", err)
280 }
281 filteredPodPatch, err := unmarshalPatch(filteredPodBytes)
282 if err != nil {
283 t.Fatalf("Unexpected error: %s", err)
284 }
285 var testCases = []struct {
286 name string
287 filename string
288 ns *corev1.Namespace
289 conf *inject.ResourceConfig
290 expectedPatchBytes []byte
291 expectedPatch unmarshalledPatch
292 }{
293 {
294 name: "service without opaque ports and namespace with",
295 filename: "service-without-opaque-ports.yaml",
296 ns: nsWithOpaquePorts,
297 conf: confNsWithOpaquePorts(),
298 expectedPatchBytes: servicePatchBytes,
299 expectedPatch: servicePatch,
300 },
301 {
302 name: "service with opaque ports and namespace with",
303 filename: "service-with-opaque-ports.yaml",
304 ns: nsWithOpaquePorts,
305 conf: confNsWithOpaquePorts(),
306 },
307 {
308 name: "service with opaque ports and namespace without",
309 filename: "service-with-opaque-ports.yaml",
310 ns: nsWithoutOpaquePorts,
311 conf: confNsWithoutOpaquePorts(),
312 },
313 {
314 name: "service without opaque ports and namespace without",
315 filename: "service-without-opaque-ports.yaml",
316 ns: nsWithoutOpaquePorts,
317 conf: confNsWithoutOpaquePorts(),
318 },
319 {
320 name: "pod without opaque ports and namespace with",
321 filename: "pod-without-opaque-ports.yaml",
322 ns: nsWithOpaquePorts,
323 conf: confNsWithOpaquePorts(),
324 expectedPatchBytes: podPatchBytes,
325 expectedPatch: podPatch,
326 },
327 {
328 name: "pod with opaque ports and namespace with",
329 filename: "pod-with-opaque-ports.yaml",
330 ns: nsWithOpaquePorts,
331 conf: confNsWithOpaquePorts(),
332 },
333 {
334 name: "pod with opaque ports and namespace without",
335 filename: "pod-with-opaque-ports.yaml",
336 ns: nsWithoutOpaquePorts,
337 conf: confNsWithoutOpaquePorts(),
338 },
339 {
340 name: "pod without opaque ports and namespace without",
341 filename: "pod-without-opaque-ports.yaml",
342 ns: nsWithoutOpaquePorts,
343 conf: confNsWithoutOpaquePorts(),
344 },
345 {
346 name: "service opaque ports are filtered",
347 filename: "filter-service-opaque-ports.yaml",
348 ns: nsWithoutOpaquePorts,
349 conf: confNsWithoutOpaquePorts(),
350 expectedPatchBytes: filteredServiceBytes,
351 expectedPatch: filteredServicePatch,
352 },
353 {
354 name: "pod opaque ports are filtered",
355 filename: "filter-pod-opaque-ports.yaml",
356 ns: nsWithoutOpaquePorts,
357 conf: confNsWithoutOpaquePorts(),
358 expectedPatchBytes: filteredPodBytes,
359 expectedPatch: filteredPodPatch,
360 },
361 }
362 for _, testCase := range testCases {
363 testCase := testCase
364 t.Run(testCase.name, func(t *testing.T) {
365 service, err := factory.FileContents(testCase.filename)
366 if err != nil {
367 t.Fatalf("Unexpected error: %s", err)
368 }
369 fakeReq := getFakeServiceReq(service)
370 fullConf := testCase.conf.
371 WithKind(fakeReq.Kind.Kind).
372 WithOwnerRetriever(ownerRetrieverFake)
373 _, err = fullConf.ParseMetaAndYAML(fakeReq.Object.Raw)
374 if err != nil {
375 t.Fatal(err)
376 }
377 patchJSON, err := fullConf.CreateOpaquePortsPatch()
378 if err != nil {
379 t.Fatalf("Unexpected error creating default opaque ports patch: %s", err)
380 }
381 if len(testCase.expectedPatchBytes) != 0 && len(patchJSON) == 0 {
382 t.Fatalf("There was no patch, but one was expected: %s", testCase.expectedPatchBytes)
383 } else if len(testCase.expectedPatchBytes) == 0 && len(patchJSON) != 0 {
384 t.Fatalf("No patch was expected, but one was returned: %s", patchJSON)
385 }
386 if len(testCase.expectedPatchBytes) == 0 {
387 return
388 }
389 actualPatch, err := unmarshalPatch(patchJSON)
390 if err != nil {
391 t.Fatalf("Unexpected error: %s", err)
392 }
393 if diff := deep.Equal(testCase.expectedPatch, actualPatch); diff != nil {
394 t.Fatalf("The actual patch didn't match what was expected.\n%+v", diff)
395 }
396 })
397 }
398 })
399 }
400
401 func getFakePodReq(b []byte) *admissionv1beta1.AdmissionRequest {
402 return &admissionv1beta1.AdmissionRequest{
403 Kind: metav1.GroupVersionKind{Kind: "Pod"},
404 Name: "foobar",
405 Namespace: "linkerd",
406 Object: runtime.RawExtension{Raw: b},
407 }
408 }
409
410 func getFakeServiceReq(b []byte) *admissionv1beta1.AdmissionRequest {
411 return &admissionv1beta1.AdmissionRequest{
412 Kind: metav1.GroupVersionKind{Kind: "Service"},
413 Name: "foobar",
414 Namespace: "linkerd",
415 Object: runtime.RawExtension{Raw: b},
416 }
417 }
418
419 func ownerRetrieverFake(p *corev1.Pod) (string, string, error) {
420 return pkgK8s.Deployment, "owner-deployment", nil
421 }
422
423 func unmarshalPatch(patchJSON []byte) (unmarshalledPatch, error) {
424 var actualPatch unmarshalledPatch
425 err := json.Unmarshal(patchJSON, &actualPatch)
426 if err != nil {
427 return nil, err
428 }
429
430 return actualPatch, nil
431 }
432
View as plain text